@internetarchive/collection-browser 0.2.19 → 0.2.20-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analytics/analytics-handler-interface.d.ts +25 -0
- package/dist/src/analytics/analytics-handler-interface.js +2 -0
- package/dist/src/analytics/analytics-handler-interface.js.map +1 -0
- package/dist/src/app-root.d.ts +5 -0
- package/dist/src/app-root.js +69 -0
- package/dist/src/app-root.js.map +1 -1
- package/dist/src/collection-browser.d.ts +3 -0
- package/dist/src/collection-browser.js +63 -19
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/collection-facets.d.ts +3 -0
- package/dist/src/collection-facets.js +12 -0
- package/dist/src/collection-facets.js.map +1 -1
- package/dist/src/models.d.ts +1 -0
- package/dist/src/models.js.map +1 -1
- package/dist/src/tiles/grid/item-tile.d.ts +5 -2
- package/dist/src/tiles/grid/item-tile.js +28 -2
- package/dist/src/tiles/grid/item-tile.js.map +1 -1
- package/dist/src/tiles/image-block.js +1 -1
- package/dist/src/tiles/image-block.js.map +1 -1
- package/dist/src/tiles/list/tile-list.d.ts +2 -0
- package/dist/src/tiles/list/tile-list.js +14 -1
- package/dist/src/tiles/list/tile-list.js.map +1 -1
- package/dist/src/tiles/text-snippet-block.d.ts +29 -0
- package/dist/src/tiles/text-snippet-block.js +128 -0
- package/dist/src/tiles/text-snippet-block.js.map +1 -0
- package/dist/src/utils/analytics-category-event.d.ts +14 -0
- package/dist/src/utils/analytics-category-event.js +15 -0
- package/dist/src/utils/analytics-category-event.js.map +1 -0
- package/dist/test/text-snippet-block.test.d.ts +1 -0
- package/dist/test/text-snippet-block.test.js +52 -0
- package/dist/test/text-snippet-block.test.js.map +1 -0
- package/dist/test/tiles/grid/item-tile.test.js +15 -0
- package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
- package/dist/test/tiles/list/tile-list.test.d.ts +1 -0
- package/dist/test/tiles/list/tile-list.test.js +42 -0
- package/dist/test/tiles/list/tile-list.test.js.map +1 -0
- package/package.json +4 -3
- package/src/analytics/analytics-handler-interface.ts +28 -0
- package/src/app-root.ts +76 -0
- package/src/collection-browser.ts +65 -13
- package/src/collection-facets.ts +19 -0
- package/src/models.ts +1 -0
- package/src/tiles/grid/item-tile.ts +35 -2
- package/src/tiles/image-block.ts +1 -1
- package/src/tiles/list/tile-list.ts +14 -1
- package/src/tiles/text-snippet-block.ts +130 -0
- package/src/utils/analytics-category-event.ts +15 -0
- package/test/text-snippet-block.test.ts +69 -0
- package/test/tiles/grid/item-tile.test.ts +19 -0
- package/test/tiles/list/tile-list.test.ts +51 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
css,
|
|
3
|
+
CSSResultGroup,
|
|
4
|
+
html,
|
|
5
|
+
LitElement,
|
|
6
|
+
nothing,
|
|
7
|
+
TemplateResult,
|
|
8
|
+
} from 'lit';
|
|
9
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
10
|
+
import { join } from 'lit/directives/join.js';
|
|
11
|
+
|
|
12
|
+
@customElement('text-snippet-block')
|
|
13
|
+
export class TextSnippetBlock extends LitElement {
|
|
14
|
+
@property({ type: Array }) snippets?: string[] = [];
|
|
15
|
+
|
|
16
|
+
@property({ type: String }) viewSize: string = 'desktop';
|
|
17
|
+
|
|
18
|
+
render() {
|
|
19
|
+
if (!this.snippets?.length) return html`${nothing}`;
|
|
20
|
+
|
|
21
|
+
return html`
|
|
22
|
+
<div class="${this.containerClasses}">${this.ellipsisJoinedSnippets}</div>
|
|
23
|
+
|
|
24
|
+
${this.viewSize === 'grid'
|
|
25
|
+
? html`<div class="separator"></div>`
|
|
26
|
+
: nothing}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private get viewSizeClass() {
|
|
31
|
+
return this.viewSize === 'grid' ? 'grid' : 'list';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private get containerClasses() {
|
|
35
|
+
return `container ${this.viewSizeClass}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An array of HTML templates derived from the snippets, with ellipses inserted
|
|
40
|
+
* at the beginning, end, and between each pair of snippets.
|
|
41
|
+
*/
|
|
42
|
+
private get ellipsisJoinedSnippets(): TemplateResult {
|
|
43
|
+
return html`
|
|
44
|
+
… ${join(this.snippetTemplates, html` … `)} …
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns an array of HTML span templates containing this item's snippets with all of
|
|
50
|
+
* their `{{{triple-brace-delimited}}}` matches replaced by `<mark>HTML mark tags</mark>`.
|
|
51
|
+
*
|
|
52
|
+
* This approach safely avoids the use of `unsafeHTML` and leaves any existing HTML tags
|
|
53
|
+
* in the snippets intact (as inert text), rather than stripping them away with DOMPurify.
|
|
54
|
+
*
|
|
55
|
+
* Note on `<em>` vs. `<mark>`:
|
|
56
|
+
* The old search page snippets had search keywords demarcated with `<em>` tags.
|
|
57
|
+
* The `<mark>` tag is semantically more accurate for this use case than `<em>`,
|
|
58
|
+
* but screen-reader behavior may be different. `<em>` will likely be read in a
|
|
59
|
+
* different tone, while `<mark>` is often read no differently than ordinary text
|
|
60
|
+
* in many screen-readers (though there are ways to work around this if needed).
|
|
61
|
+
*/
|
|
62
|
+
private get snippetTemplates(): TemplateResult[] | undefined {
|
|
63
|
+
return this.snippets?.map(s => {
|
|
64
|
+
const matches = s.matchAll(/{{{(.+?)}}}/g);
|
|
65
|
+
const templates: TemplateResult[] = [];
|
|
66
|
+
|
|
67
|
+
// Convert each match into an HTML template that includes:
|
|
68
|
+
// - Everything from the end of the previous match (or the beginning of the
|
|
69
|
+
// string) up to the current match, as raw text.
|
|
70
|
+
// - The current match (excluding the curly braces) wrapped in a `<mark>` tag.
|
|
71
|
+
let index = 0;
|
|
72
|
+
for (const match of matches) {
|
|
73
|
+
if (match.index != null) {
|
|
74
|
+
templates.push(html`
|
|
75
|
+
${s.slice(index, match.index)}
|
|
76
|
+
<mark>${match[1]}</mark>
|
|
77
|
+
`);
|
|
78
|
+
index = match.index + match[0].length;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Include any text from the last match to the end
|
|
83
|
+
templates.push(html`${s.slice(index)}`);
|
|
84
|
+
|
|
85
|
+
// Squash everything into a single span template
|
|
86
|
+
return html`<span>${templates}</span>`;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static get styles(): CSSResultGroup {
|
|
91
|
+
return css`
|
|
92
|
+
.container {
|
|
93
|
+
display: -webkit-box;
|
|
94
|
+
font-family: 'Times New Roman', serif;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
overflow-wrap: break-word;
|
|
97
|
+
-webkit-line-clamp: var(--maxLines, 3);
|
|
98
|
+
-webkit-box-orient: vertical;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.separator {
|
|
102
|
+
/* Border line should extend to the edges of the tile */
|
|
103
|
+
margin: 0 -5px;
|
|
104
|
+
border-bottom: 1px solid #bbb;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.grid {
|
|
108
|
+
/* Bottom margin reduced by 1px to account for the separator */
|
|
109
|
+
margin: 0.5rem 0 0.4rem;
|
|
110
|
+
font-size: 1.2rem;
|
|
111
|
+
line-height: 1.5rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.list {
|
|
115
|
+
margin: 1rem 0 0;
|
|
116
|
+
padding: 0 0 0 1.5rem;
|
|
117
|
+
border-left: 0.5rem solid #194880;
|
|
118
|
+
border-radius: 3px;
|
|
119
|
+
font-size: 1.4rem;
|
|
120
|
+
line-height: 2rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
mark {
|
|
124
|
+
/* blue, 20% transparency */
|
|
125
|
+
background-color: #0000ff33;
|
|
126
|
+
color: inherit;
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics categories and events. Used when building actions in
|
|
3
|
+
*/
|
|
4
|
+
export const analyticsCategories = {
|
|
5
|
+
default: 'collection-browser',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const analyticsActions = {
|
|
9
|
+
sortBy: 'sortBy',
|
|
10
|
+
sortByCreator: 'sortByCreator',
|
|
11
|
+
sortByTitle: 'sortByTitle',
|
|
12
|
+
displayMode: 'displayMode',
|
|
13
|
+
facetsChanged: 'facetsChanged', // group + value
|
|
14
|
+
histogramChanged: 'histogramChanged',
|
|
15
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* eslint-disable import/no-duplicates */
|
|
2
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
3
|
+
import { html } from 'lit';
|
|
4
|
+
import type { TextSnippetBlock } from '../src/tiles/text-snippet-block';
|
|
5
|
+
import '../src/tiles/text-snippet-block';
|
|
6
|
+
|
|
7
|
+
describe('TextSnippetBlock component', () => {
|
|
8
|
+
it('should render initial component', async () => {
|
|
9
|
+
const el = await fixture<TextSnippetBlock>(
|
|
10
|
+
html`<text-snippet-block></text-snippet-block>`
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
// No content if no snippets
|
|
14
|
+
expect(el.shadowRoot?.children.length).to.equal(0);
|
|
15
|
+
|
|
16
|
+
// Content appears if there are snippets
|
|
17
|
+
el.snippets = ['text'];
|
|
18
|
+
await el.updateComplete;
|
|
19
|
+
const container = el.shadowRoot?.querySelector('.container');
|
|
20
|
+
expect(container).to.exist;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render marked snippets', async () => {
|
|
24
|
+
const snippets = [
|
|
25
|
+
'some {{{snippet}}} text',
|
|
26
|
+
'some {{{other}}} {{{snippet}}} text',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const el = await fixture<TextSnippetBlock>(
|
|
30
|
+
html`<text-snippet-block .snippets=${snippets}></text-snippet-block>`
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const container = el.shadowRoot?.querySelector('.container');
|
|
34
|
+
|
|
35
|
+
// Has the correct number of snippets and highlights
|
|
36
|
+
expect(container?.children.length).to.equal(snippets.length);
|
|
37
|
+
expect(container?.querySelectorAll('mark').length).to.equal(3);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should render correctly in grid mode', async () => {
|
|
41
|
+
const el = await fixture<TextSnippetBlock>(
|
|
42
|
+
html`<text-snippet-block
|
|
43
|
+
viewsize="grid"
|
|
44
|
+
.snippets=${['text']}
|
|
45
|
+
></text-snippet-block>`
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const container = el.shadowRoot?.querySelector('.container');
|
|
49
|
+
|
|
50
|
+
// Applies the right container classes
|
|
51
|
+
expect(container?.classList.contains('grid')).to.be.true;
|
|
52
|
+
expect(container?.classList.contains('list')).to.be.false;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should render correctly in list mode', async () => {
|
|
56
|
+
const el = await fixture<TextSnippetBlock>(
|
|
57
|
+
html`<text-snippet-block
|
|
58
|
+
viewsize="list"
|
|
59
|
+
.snippets=${['text']}
|
|
60
|
+
></text-snippet-block>`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const container = el.shadowRoot?.querySelector('.container');
|
|
64
|
+
|
|
65
|
+
// Applies the right container classes
|
|
66
|
+
expect(container?.classList.contains('list')).to.be.true;
|
|
67
|
+
expect(container?.classList.contains('grid')).to.be.false;
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -113,4 +113,23 @@ describe('Item Tile', () => {
|
|
|
113
113
|
expect(dateSortedBy).to.not.exist; // it should be exist because this is not related to date sort
|
|
114
114
|
expect(createdBy).to.exist;
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
it('should render with snippet block when it has snippets', async () => {
|
|
118
|
+
const el = await fixture<ItemTile>(html`
|
|
119
|
+
<item-tile .model=${{ snippets: ['some {{{snippet}}} text'] }}>
|
|
120
|
+
</item-tile>
|
|
121
|
+
`);
|
|
122
|
+
|
|
123
|
+
const snippetBlock = el.shadowRoot?.querySelector('text-snippet-block');
|
|
124
|
+
|
|
125
|
+
expect(snippetBlock).to.exist;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should not render snippet block when no snippets are present', async () => {
|
|
129
|
+
const el = await fixture<ItemTile>(html`<item-tile></item-tile>`);
|
|
130
|
+
|
|
131
|
+
const snippetBlock = el.shadowRoot?.querySelector('text-snippet-block');
|
|
132
|
+
|
|
133
|
+
expect(snippetBlock).to.not.exist;
|
|
134
|
+
});
|
|
116
135
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* eslint-disable import/no-duplicates */
|
|
2
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
3
|
+
import { html } from 'lit';
|
|
4
|
+
import type { TileList } from '../../../src/tiles/list/tile-list';
|
|
5
|
+
|
|
6
|
+
import '../../../src/tiles/list/tile-list';
|
|
7
|
+
|
|
8
|
+
describe('List Tile', () => {
|
|
9
|
+
it('should render initial component', async () => {
|
|
10
|
+
const el = await fixture<TileList>(html`<tile-list></tile-list>`);
|
|
11
|
+
|
|
12
|
+
const listContainer = el.shadowRoot?.querySelector('#list-line');
|
|
13
|
+
const itemTitle = el.shadowRoot?.querySelector('#title');
|
|
14
|
+
const imageBlock = el.shadowRoot?.querySelector('image-block');
|
|
15
|
+
|
|
16
|
+
expect(listContainer).to.exist;
|
|
17
|
+
expect(itemTitle).to.exist;
|
|
18
|
+
expect(imageBlock).to.exist;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should render with creator element but not dates', async () => {
|
|
22
|
+
const el = await fixture<TileList>(html`
|
|
23
|
+
<tile-list .model=${{ creators: ['someone'] }}></tile-list>
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
const creator = el.shadowRoot?.querySelector('#creator');
|
|
27
|
+
const datesLine = el.shadowRoot?.querySelector('#dates-line');
|
|
28
|
+
|
|
29
|
+
expect(creator).to.exist;
|
|
30
|
+
expect(datesLine?.children.length).to.equal(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render with snippet block when it has snippets', async () => {
|
|
34
|
+
const el = await fixture<TileList>(html`
|
|
35
|
+
<tile-list .model=${{ snippets: ['some {{{snippet}}} text'] }}>
|
|
36
|
+
</tile-list>
|
|
37
|
+
`);
|
|
38
|
+
|
|
39
|
+
const snippetBlock = el.shadowRoot?.querySelector('text-snippet-block');
|
|
40
|
+
|
|
41
|
+
expect(snippetBlock).to.exist;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not render snippet block when no snippets are present', async () => {
|
|
45
|
+
const el = await fixture<TileList>(html`<tile-list></tile-list>`);
|
|
46
|
+
|
|
47
|
+
const snippetBlock = el.shadowRoot?.querySelector('text-snippet-block');
|
|
48
|
+
|
|
49
|
+
expect(snippetBlock).to.not.exist;
|
|
50
|
+
});
|
|
51
|
+
});
|