-
{{#if osfID}}
{{#link-to-external 'registration' osfID data-test-result-title-id=result.id}}
- {{unescape-xml-entities result.title}}
+ {{math result.title}}
{{/link-to-external}}
{{else}}
- {{unescape-xml-entities result.title}}
+ {{math result.title}}
{{/if}}
@@ -21,9 +20,17 @@
{{/if}}
- {{#comma-list result.contributors filter=contribFilter as |contrib|}}
- {{contrib.users.citedAs}}
- {{/comma-list}}
+
+ {{#each this.contributors as |contrib|}}
+
+ {{#if contrib.link}}
+ {{contrib.name}}
+ {{else}}
+ {{contrib.name}}
+ {{/if}}
+
+ {{/each}}
+
{{#if result.dateUpdated}}
@@ -31,12 +38,8 @@
{{/if}}
-
- {{#if expanded}}
- {{unescape-xml-entities result.description}}
- {{else}}
- {{clip (unescape-xml-entities result.description) 300}}
- {{/if}}
+
+ {{math result.description}}
@@ -51,12 +54,12 @@
- {{#if expanded}}
+
{{#each result.tags as |tag|}}
-
{{tag}}
+
{{math tag}}
{{/each}}
- {{/if}}
+
+
-
+
{{fa-icon footerIcon size=1 ariaHidden=false}}
diff --git a/lib/registries/addon/discover/controller.ts b/lib/registries/addon/discover/controller.ts
index d8e95ef6ef9..113932c4147 100644
--- a/lib/registries/addon/discover/controller.ts
+++ b/lib/registries/addon/discover/controller.ts
@@ -19,6 +19,11 @@ import ShareSearch, {
} from 'registries/services/share-search';
import styles from './styles';
+interface DoSearchOptions {
+ scrollTop?: boolean;
+ noPageReset?: boolean;
+}
+
interface DiscoverQueryParams {
page: number;
query: string;
@@ -129,6 +134,9 @@ export default class Discover extends Controller.extend(discoverQueryParams.Mixi
@service analytics!: Analytics;
@service shareSearch!: ShareSearch;
+ // List of keys that, when changed, reset the page to 1
+ pageResetKeys = ['query', 'order', 'filter'];
+
results: EmberArray
= A([]);
searchable!: number;
totalResults: number = 0;
@@ -173,7 +181,7 @@ export default class Discover extends Controller.extend(discoverQueryParams.Mixi
return max;
}
- doSearch = task(function *(this: Discover, opts: SearchOptions, scrollTop?: boolean) {
+ doSearch = task(function *(this: Discover, opts: SearchOptions, { scrollTop, noPageReset }: DoSearchOptions = {}) {
let options = opts;
// Unless OSF is the only source, registration_type filters must be cleared
@@ -182,6 +190,23 @@ export default class Discover extends Controller.extend(discoverQueryParams.Mixi
options = options.set('filters', options.filters.filter(filter => filter.key !== 'registration_type'));
}
+ // If there is no query, no filters, and no sort, default to -date_modified rather
+ // than relevance.
+ if (!options.order.key && (!options.query || options.query === '') && options.filters.size === 0) {
+ options = options.set('order', new SearchOrder({
+ display: 'registries.discover.order.relevance',
+ ascending: false,
+ key: 'date_modified',
+ }));
+ }
+
+ // Latter bit is just a janky array intersection
+ // If any of the specified keys have changed, reset to the first page
+ if ((this.searchOptions && !noPageReset) &&
+ this.searchOptions.differingKeys(options).filter(k => this.pageResetKeys.includes(k)).length > 0) {
+ options = options.set('page', 1);
+ }
+
this.set('searchOptions', options);
this.setQueryParams(options);
@@ -193,16 +218,6 @@ export default class Discover extends Controller.extend(discoverQueryParams.Mixi
yield timeout(250);
- // If there is no query, no filters, and no sort, default to -date_modified rather
- // than relevance.
- if (!options.order.key && (!options.query || options.query === '') && options.filters.size === 0) {
- options = options.set('order', new SearchOrder({
- display: '',
- ascending: false,
- key: 'date_modified',
- }));
- }
-
const results: SearchResults = yield this.shareSearch.registrations(options);
this.set('results', A(results.results));
@@ -243,14 +258,18 @@ export default class Discover extends Controller.extend(discoverQueryParams.Mixi
page: event.queryParams.page,
order: order || this.sortOptions[0],
filters: OrderedSet(filters),
- }));
+ }), { noPageReset: true });
}
setQueryParams(this: Discover, options: SearchOptions) {
this.set('page', options.page);
this.set('size', options.size);
this.set('query', options.query || '');
- this.set('sort', `${options.order.ascending ? '' : '-'}${options.order.key || ''}`);
+
+ // date_modified is a weird case, so don't save it in a query param.
+ if (options.order.key !== 'date_modified') {
+ this.set('sort', `${options.order.ascending ? '' : '-'}${options.order.key || ''}`);
+ }
const providers: string[] = [];
const registrationTypes: string[] = [];
@@ -277,7 +296,7 @@ export default class Discover extends Controller.extend(discoverQueryParams.Mixi
changePage(this: Discover, page: number) {
this.get('doSearch').perform(
this.searchOptions.set('page', page),
- true, // Scroll to top
+ { scrollTop: true },
);
}
diff --git a/lib/registries/addon/discover/styles.scss b/lib/registries/addon/discover/styles.scss
index c12b2f6cebe..f3b4bb31c45 100644
--- a/lib/registries/addon/discover/styles.scss
+++ b/lib/registries/addon/discover/styles.scss
@@ -33,3 +33,8 @@
color: #eee;
}
}
+
+.Pagination {
+ display: inline-flex;
+ justify-content: flex-end;
+}
diff --git a/lib/registries/addon/discover/template.hbs b/lib/registries/addon/discover/template.hbs
index dd9d1fee3ca..75d154301c0 100644
--- a/lib/registries/addon/discover/template.hbs
+++ b/lib/registries/addon/discover/template.hbs
@@ -65,14 +65,12 @@
{{/unless}}
{{#if (gt maxPage 1) }}
-
-
- {{search-paginator
- current=searchOptions.page
- maximum=maxPage
- pageChanged=(action 'changePage')
- }}
-
+
+ {{search-paginator
+ current=searchOptions.page
+ maximum=maxPage
+ pageChanged=(action 'changePage')
+ }}
{{/if}}
diff --git a/lib/registries/addon/services/search.ts b/lib/registries/addon/services/search.ts
index e7724189a35..5a66e231004 100644
--- a/lib/registries/addon/services/search.ts
+++ b/lib/registries/addon/services/search.ts
@@ -1,5 +1,5 @@
import Service from '@ember/service';
-import { Map, OrderedSet, ValueObject } from 'immutable';
+import { is, Map, OrderedSet, ValueObject } from 'immutable';
import $ from 'jquery';
// Used later on for slightly stricter typing of immutable Maps
@@ -28,6 +28,12 @@ export class MapProxy
implements ValueObject {
hashCode(): number {
return this.internal.hashCode();
}
+
+ differingKeys(other: MapProxy): Array {
+ return Array.from(this.internal.filter(
+ (val, key) => !is(val, other.internal.get(key)),
+ ).keys());
+ }
}
// Doesn't matter for types but useful for keeping all interfaces together.
diff --git a/lib/registries/addon/services/share-search.ts b/lib/registries/addon/services/share-search.ts
index bce9b36060e..6e681b55e18 100644
--- a/lib/registries/addon/services/share-search.ts
+++ b/lib/registries/addon/services/share-search.ts
@@ -1,3 +1,4 @@
+import unescapeXMLEntities from 'ember-osf-web/utils/fix-special-char';
import { Map } from 'immutable';
import config from 'registries/config/environment';
import Search, {
@@ -75,13 +76,19 @@ export class ShareTermsAggregation implements SearchModifier {
}
}
+export interface ShareContributor {
+ id: string;
+ bibliographic: boolean;
+ citedAs: string;
+ identifiers: string[];
+ name: string;
+ orderCited: number;
+}
+
export interface ShareRegistration {
id: string;
description: string;
- contributors: Array<{
- citedAs: string;
- bibilographic: boolean;
- }>;
+ contributors: ShareContributor[];
dateCreated?: Date;
dateModified?: Date;
datePublished?: Date;
@@ -158,16 +165,15 @@ export default class ShareSearch extends Search {
_postProcessRegistrations(registrations: any): ShareRegistration[] {
return registrations.hits.hits.map((r: any): ShareRegistration => {
const contributors = (r._source.lists.contributors || [])
- .sort((a: any, b: any) => (a.order_cited || -1) - (b.order_cited || -1))
- .map((contrib: any) => ({
- users: Object.keys(contrib).reduce(
- (acc: {[k: string]: any}, key: string) => ({
- ...acc,
- [(key as any).camelize()]: contrib[key],
- }),
- { bibliographic: contrib.relation !== 'contributor' },
- ),
- }));
+ .map((contrib: any): ShareContributor => ({
+ id: contrib.id as string,
+ bibliographic: contrib.relation !== 'contributor',
+ citedAs: unescapeXMLEntities(contrib.cited_as),
+ identifiers: contrib.identifiers as string[],
+ name: unescapeXMLEntities(contrib.name),
+ orderCited: contrib.order_cited || -1,
+ } as ShareContributor))
+ .sort((a: ShareContributor, b: ShareContributor) => a.orderCited - b.orderCited);
let mainLink: string | undefined;
const infoLinks: Array<{ type: string; uri: string; }> = [];
@@ -206,15 +212,15 @@ export default class ShareSearch extends Search {
),
mainLink: mainLink || hyperLinks[0],
registrationType: r._source.registration_type,
- title: r._source.title,
+ title: unescapeXMLEntities(r._source.title),
subjects: r._source.subjects,
subjectSynonyms: r._source.subject_synonyms,
- description: r._source.description,
+ description: unescapeXMLEntities(r._source.description),
dateUpdated: r._source.date_updated ? new Date(r._source.date_updated) : undefined,
dateCreated: r._source.date_created ? new Date(r._source.date_created) : undefined,
dateModified: r._source.date_modified ? new Date(r._source.date_modified) : undefined,
datePublished: r._source.date_published ? new Date(r._source.date_published) : undefined,
- tags: r._source.tags,
+ tags: r._source.tags.map(unescapeXMLEntities),
withdrawn: r._source.withdrawn,
};
});
diff --git a/package.json b/package.json
index 32e3a1d9e82..5e398e752c9 100644
--- a/package.json
+++ b/package.json
@@ -153,6 +153,7 @@
"immutable": "4.0.0-rc.9",
"js-md5": "^0.7.3",
"jsonapi-typescript": "^0.0.9",
+ "katex": "^0.10.0-rc.1",
"keen-analysis": "^2.0.0",
"keen-dataviz": "^2.0.4",
"keen-tracking": "^2.0.9",
diff --git a/tests/engines/registries/integration/discover/discover-test.ts b/tests/engines/registries/integration/discover/discover-test.ts
index 0e1fd4af6ee..cbb379e6536 100644
--- a/tests/engines/registries/integration/discover/discover-test.ts
+++ b/tests/engines/registries/integration/discover/discover-test.ts
@@ -42,7 +42,7 @@ const QueryParamTestCases: Array<{
expected: {
query: '',
order: new SearchOrder({
- display: '',
+ display: 'registries.discover.order.relevance',
ascending: false,
key: 'date_modified',
}),
diff --git a/tests/engines/registries/unit/services/search.ts b/tests/engines/registries/unit/services/search-test.ts
similarity index 66%
rename from tests/engines/registries/unit/services/search.ts
rename to tests/engines/registries/unit/services/search-test.ts
index 014b6cff6ff..b212c5d1e19 100644
--- a/tests/engines/registries/unit/services/search.ts
+++ b/tests/engines/registries/unit/services/search-test.ts
@@ -1,5 +1,5 @@
import { setupEngineTest } from 'ember-osf-web/tests/helpers/engines';
-import { OrderedSet } from 'immutable';
+import { is, OrderedSet } from 'immutable';
import { module, test } from 'qunit';
import { SearchFilter, SearchOptions } from 'registries/services/search';
@@ -20,17 +20,17 @@ class TestSearchFilter extends SearchFilter {
}
}
-module('Unit | Registries | Service | Search', hooks => {
+module('Registries | Unit | Service | search', hooks => {
setupEngineTest(hooks, 'registries');
// Replace this with your real tests.
test('it exists', function(assert) {
- const service = this.owner.lookup('service:');
+ const service = this.owner.lookup('service:search');
assert.ok(service);
});
test('SearchOptions immutable', assert => {
- const options = new SearchOptions({});
+ const options = new SearchOptions({ page: 10 });
const changed = options.set('page', 420);
assert.equal(options.page, 10);
@@ -38,17 +38,30 @@ module('Unit | Registries | Service | Search', hooks => {
});
test('SearchOptions equality', assert => {
- assert.equal(
+ assert.ok(is(
new SearchOptions({ query: 'Foo Bar' }),
new SearchOptions({ query: 'Foo Bar' }),
- );
+ ));
- assert.notEqual(
+ assert.notOk(is(
new SearchOptions({ query: 'Foo Bar' }),
- new SearchOptions({ query: 'Foo Bar' }),
- );
+ new SearchOptions({ query: 'Foo bar' }),
+ ));
+
+ assert.ok(is(
+ new SearchOptions({
+ filters: OrderedSet([
+ new TestSearchFilter('Foo', 'foo', 43),
+ ]),
+ }),
+ new SearchOptions({
+ filters: OrderedSet([
+ new TestSearchFilter('Foo', 'foo', 43),
+ ]),
+ }),
+ ));
- assert.equal(
+ assert.ok(is(
new SearchOptions({
filters: OrderedSet([
new TestSearchFilter('Foo', 'foo', 43),
@@ -60,6 +73,6 @@ module('Unit | Registries | Service | Search', hooks => {
new TestSearchFilter('Foo', 'foo', 43),
]),
}),
- );
+ ));
});
});
diff --git a/tests/engines/registries/unit/services/share-search-test.ts b/tests/engines/registries/unit/services/share-search-test.ts
new file mode 100644
index 00000000000..b24197f3597
--- /dev/null
+++ b/tests/engines/registries/unit/services/share-search-test.ts
@@ -0,0 +1,83 @@
+import { setupEngineTest } from 'ember-osf-web/tests/helpers/engines';
+import { TestContext } from 'ember-test-helpers';
+import { module, test } from 'qunit';
+import ShareSearch from 'registries/services/share-search';
+
+const ES_RESPONSE = {
+ hits: {
+ hits: [{
+ _index: 'share_customtax_1',
+ _type: 'creativeworks',
+ _id: '46218-570-0DA',
+ _score: null,
+ _source: {
+ id: '46218-570-0DA',
+ type: 'registration',
+ title: 'Pod Assignment &',
+ description: '<><',
+ language: null,
+ date_created: '2018-10-04T18:37:42.820512+00:00',
+ date_modified: '2018-10-04T18:37:42.82049+00:00',
+ date_updated: null,
+ date_published: '2018-10-04T16:22:11.995933+00:00',
+ registration_type: 'AsPredicted Preregistration',
+ withdrawn: false,
+ justification: null,
+ tags: ['&', 'Foo'],
+ identifiers: ['http://osf.io/w4yhb/'],
+ sources: ['OSF'],
+ subjects: [],
+ subject_synonyms: [],
+ lists: {
+ contributors: [{
+ id: '6402D-242-421',
+ type: 'person',
+ name: 'Graham > Berlin',
+ given_name: 'Graham',
+ family_name: 'Berlin',
+ identifiers: ['http://osf.io/6j8ub/'],
+ order_cited: 0,
+ cited_as: 'Graham Berlin',
+ affiliations: [],
+ awards: [],
+ relation: 'creator',
+ }, {
+ id: '6415A-84F-065',
+ type: 'person',
+ name: 'Nicole < Grant',
+ given_name: 'Nicole',
+ family_name: 'Grant',
+ identifiers: ['http://osf.io/8zrkb/'],
+ order_cited: 4,
+ cited_as: 'Nicole Grant',
+ affiliations: [],
+ relation: 'creator',
+ }],
+ },
+ },
+ }],
+ },
+};
+
+module('Registries | Unit | Service | share-search', hooks => {
+ setupEngineTest(hooks, 'registries');
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ const service = this.owner.lookup('service:share-search');
+ assert.ok(service);
+ });
+
+ test('_postProcessRegistrations', function(this: TestContext, assert) {
+ const service = this.owner.lookup('service:share-search') as ShareSearch;
+
+ const registrations = service._postProcessRegistrations(ES_RESPONSE);
+
+ assert.equal(registrations.length, 1);
+ assert.equal(registrations[0].title, 'Pod Assignment &');
+ assert.equal(registrations[0].description, '<><');
+ assert.equal(registrations[0].tags[0], '&');
+ assert.equal(registrations[0].contributors[0].name, 'Graham > Berlin');
+ assert.equal(registrations[0].contributors[1].name, 'Nicole < Grant');
+ });
+});
diff --git a/tests/integration/components/search-dropdown/component-test.ts b/tests/integration/components/search-dropdown/component-test.ts
deleted file mode 100644
index 8f61ae2d6e8..00000000000
--- a/tests/integration/components/search-dropdown/component-test.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { render } from '@ember/test-helpers';
-import { setupRenderingTest } from 'ember-qunit';
-import hbs from 'htmlbars-inline-precompile';
-import { module, test } from 'qunit';
-
-module('Integration | Component | search-dropdown', hooks => {
- setupRenderingTest(hooks);
-
- test('it renders', async function(assert) {
- await render(hbs`{{search-dropdown}}`);
- assert.dom(this.element).hasText('Search');
- });
-});
diff --git a/tests/unit/helpers/math-test.ts b/tests/unit/helpers/math-test.ts
new file mode 100644
index 00000000000..6201d69093f
--- /dev/null
+++ b/tests/unit/helpers/math-test.ts
@@ -0,0 +1,49 @@
+import { Delimiter, DELIMITERS, replace } from 'app-components/helpers/math';
+import { TestContext } from 'ember-test-helpers';
+import { module, test } from 'qunit';
+
+const TEX_EXPRS = [
+ 'x^2 + y^2 = 1',
+
+ 'f(x) = \\int_{-\\infty}^\\infty\\hat f(\\xi)\\,e^{2 \\pi i \\xi x}\\,d\\xi',
+
+ ` % \\f is defined as f(#1) using the macro
+ \\f{x} = \\int_{-\\infty}^\\infty
+ \\hat \\f\\xi\\,e^{2 \\pi \\$ \\$ i \\xi x}
+ \\,d\\xi`,
+];
+
+const TEMPLATES = [
+ (expr: string) => `This is TeX: ${expr}`,
+ (expr: string) => `${expr} is some TeX`,
+ (expr: string) => `${expr} TeX is leading and ending here ${expr}`,
+ (expr: string) => `${expr} ${expr} Double Trouble!`,
+ (expr: string) => `Some TeX ${expr} surrounded by text`,
+ (expr: string) => expr,
+ (_: string) => 'Haha, No Tex here!',
+];
+
+const TEST_CASES: Array<{ input: string, output: string }> = [];
+
+for (const template of TEMPLATES) {
+ for (const texExpression of TEX_EXPRS) {
+ for (const delimitter of DELIMITERS) {
+ TEST_CASES.push({
+ input: template(`${delimitter.start}${texExpression}${delimitter.end}`),
+ output: template(``),
+ });
+ }
+ }
+}
+
+module('Unit | Helper | math', _ => {
+ test('it parses', function(this: TestContext, assert) {
+ for (const testCase of TEST_CASES) {
+ const result = replace(testCase.input, DELIMITERS, (expr: string, delim: Delimiter) => {
+ return ``;
+ });
+
+ assert.equal(result, testCase.output, `Properly parsed ${testCase.input}`);
+ }
+ });
+});
diff --git a/types/katex/index.d.ts b/types/katex/index.d.ts
new file mode 100644
index 00000000000..5c9e02d60a0
--- /dev/null
+++ b/types/katex/index.d.ts
@@ -0,0 +1,10 @@
+declare module 'katex' {
+ export interface KatexRenderOptions {
+ displayMode?: boolean;
+ throwOnError?: boolean;
+ allowedProtocols?: string[];
+ }
+
+ export function renderToString(expression: string, options?: KatexRenderOptions): string;
+ export function render(expression: string, element: HTMLElement, options?: KatexOptions): void;
+}
diff --git a/yarn.lock b/yarn.lock
index f7be07fee1c..abb5d3bdd6f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3962,6 +3962,10 @@ commander@^2.12.1, commander@^2.9.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==
+commander@^2.16.0:
+ version "2.18.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970"
+
commander@~2.17.1:
version "2.17.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
@@ -10343,6 +10347,12 @@ just-extend@^1.1.27:
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
integrity sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==
+katex@^0.10.0-rc.1:
+ version "0.10.0-rc.1"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.0-rc.1.tgz#bd46155281f61c4855a064e2b7a89d137ee04b41"
+ dependencies:
+ commander "^2.16.0"
+
keen-analysis@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/keen-analysis/-/keen-analysis-2.0.0.tgz#a66cda9b5e996d8bf4dbe3a515579d4a868375dd"