@internetarchive/collection-browser 4.3.2-rc-webdev-8334.3 → 4.3.2-rc-webdev-8334.4
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/app-root.js +4 -0
- package/dist/src/app-root.js.map +1 -1
- package/dist/src/collection-browser.d.ts +41 -0
- package/dist/src/collection-browser.js +129 -36
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/tiles/item-image.d.ts +9 -1
- package/dist/src/tiles/item-image.js +22 -2
- package/dist/src/tiles/item-image.js.map +1 -1
- package/dist/src/tiles/tile-dispatcher.js +19 -1
- package/dist/src/tiles/tile-dispatcher.js.map +1 -1
- package/dist/src/tiles/tile-display-value-provider.js +2 -1
- package/dist/src/tiles/tile-display-value-provider.js.map +1 -1
- package/dist/test/tiles/grid/item-tile.test.js +2 -2
- package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
- package/dist/test/tiles/list/tile-list.test.js +2 -2
- package/dist/test/tiles/list/tile-list.test.js.map +1 -1
- package/dist/test/tiles/tile-dispatcher.test.js +42 -0
- package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
- package/package.json +3 -3
- package/src/app-root.ts +3 -0
- package/src/collection-browser.ts +147 -35
- package/src/tiles/item-image.ts +28 -1
- package/src/tiles/tile-dispatcher.ts +20 -1
- package/src/tiles/tile-display-value-provider.ts +2 -3
- package/test/tiles/grid/item-tile.test.ts +2 -2
- package/test/tiles/list/tile-list.test.ts +2 -2
- package/test/tiles/tile-dispatcher.test.ts +59 -0
package/src/tiles/item-image.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
css,
|
|
3
|
+
CSSResultGroup,
|
|
4
|
+
html,
|
|
5
|
+
LitElement,
|
|
6
|
+
nothing,
|
|
7
|
+
PropertyValues,
|
|
8
|
+
} from 'lit';
|
|
2
9
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
3
10
|
import { ClassInfo, classMap } from 'lit/directives/class-map.js';
|
|
4
11
|
|
|
@@ -12,6 +19,14 @@ import { searchIcon } from '../assets/img/icons/mediatype/search';
|
|
|
12
19
|
|
|
13
20
|
@customElement('item-image')
|
|
14
21
|
export class ItemImage extends LitElement {
|
|
22
|
+
/**
|
|
23
|
+
* Map to cache which identifiers have waveform-style thumbnails, so that
|
|
24
|
+
* they can have their waveform styling applied immediately, rather than
|
|
25
|
+
* waiting for the image content to load before applying it (which can
|
|
26
|
+
* cause noticeable flicker when such tiles refresh).
|
|
27
|
+
*/
|
|
28
|
+
private static readonly waveformByIdentifier = new Map<string, boolean>();
|
|
29
|
+
|
|
15
30
|
@property({ type: Object }) model?: TileModel;
|
|
16
31
|
|
|
17
32
|
@property({ type: String }) baseImageUrl?: string;
|
|
@@ -30,6 +45,15 @@ export class ItemImage extends LitElement {
|
|
|
30
45
|
|
|
31
46
|
@query('img') private baseImage!: HTMLImageElement;
|
|
32
47
|
|
|
48
|
+
protected willUpdate(changed: PropertyValues): void {
|
|
49
|
+
if (changed.has('model')) {
|
|
50
|
+
// If this identifier is known to have a waveform image, then set isWaveform upfront
|
|
51
|
+
const identifier = this.model?.identifier;
|
|
52
|
+
this.isWaveform =
|
|
53
|
+
ItemImage.waveformByIdentifier.get(identifier as string) === true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
render() {
|
|
34
58
|
return html`
|
|
35
59
|
<div class=${classMap(this.itemBaseClass)}>${this.imageTemplate}</div>
|
|
@@ -149,6 +173,9 @@ export class ItemImage extends LitElement {
|
|
|
149
173
|
this.baseImage.naturalWidth / this.baseImage.naturalHeight === 4
|
|
150
174
|
) {
|
|
151
175
|
this.isWaveform = true;
|
|
176
|
+
if (this.model?.identifier) {
|
|
177
|
+
ItemImage.waveformByIdentifier.set(this.model.identifier, true);
|
|
178
|
+
}
|
|
152
179
|
}
|
|
153
180
|
}
|
|
154
181
|
|
|
@@ -176,7 +176,26 @@ export class TileDispatcher
|
|
|
176
176
|
// Use the server-specified href if available.
|
|
177
177
|
// Otherwise, construct a details page URL from the item identifier.
|
|
178
178
|
if (this.model.href) {
|
|
179
|
-
|
|
179
|
+
// Backstop for legacy Wayback Machine URLs where the target URL was
|
|
180
|
+
// erroneously percent-encoded by encodeURIComponent (the root cause in
|
|
181
|
+
// tile-display-value-provider is fixed, but old stored data may still
|
|
182
|
+
// carry encoded URLs). Decode only the target-URL segment after the
|
|
183
|
+
// /web/{timestamp}/ prefix — decoding the full href risks corrupting the
|
|
184
|
+
// Wayback prefix or intentional encoding elsewhere in the path.
|
|
185
|
+
// %3A%2F%2F (encoded "://") is an unambiguous indicator the entire URL
|
|
186
|
+
// was over-encoded; legitimate paths with %3A never appear next to %2F%2F.
|
|
187
|
+
const href = this.model.href.replace(
|
|
188
|
+
/(\/web\/\d+\/)(.+)/,
|
|
189
|
+
(_, prefix, target) => {
|
|
190
|
+
if (!/%3A%2F%2F/i.test(target)) return `${prefix}${target}`;
|
|
191
|
+
try {
|
|
192
|
+
return `${prefix}${decodeURIComponent(target)}`;
|
|
193
|
+
} catch {
|
|
194
|
+
return `${prefix}${target}`;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
return `${this.baseNavigationUrl}${href}`;
|
|
180
199
|
}
|
|
181
200
|
|
|
182
201
|
return this.displayValueProvider.itemPageUrl(
|
|
@@ -124,9 +124,8 @@ export class TileDisplayValueProvider {
|
|
|
124
124
|
.toISOString()
|
|
125
125
|
.replace(/[TZ:-]/g, '')
|
|
126
126
|
.replace(/\..*/, '');
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)}`;
|
|
127
|
+
// url must not be percent-encoded — Wayback Machine matches on the raw URL
|
|
128
|
+
const captureHref = `https://web.archive.org/web/${captureDateStr}/${url}`;
|
|
130
129
|
const captureText = formatDate(date, 'long');
|
|
131
130
|
|
|
132
131
|
return html` <a href=${captureHref}> ${captureText} </a> `;
|
|
@@ -453,7 +453,7 @@ describe('Item Tile', () => {
|
|
|
453
453
|
const firstDateLink = captureDatesUl?.children[0]?.querySelector('a[href]');
|
|
454
454
|
expect(firstDateLink, 'first date link').to.exist;
|
|
455
455
|
expect(firstDateLink?.getAttribute('href')).to.equal(
|
|
456
|
-
'https://web.archive.org/web/20100102123456/https
|
|
456
|
+
'https://web.archive.org/web/20100102123456/https://example.com/',
|
|
457
457
|
);
|
|
458
458
|
expect(firstDateLink?.textContent?.trim()).to.equal('Jan 02, 2010');
|
|
459
459
|
|
|
@@ -461,7 +461,7 @@ describe('Item Tile', () => {
|
|
|
461
461
|
captureDatesUl?.children[1]?.querySelector('a[href]');
|
|
462
462
|
expect(secondDateLink, 'second date link').to.exist;
|
|
463
463
|
expect(secondDateLink?.getAttribute('href')).to.equal(
|
|
464
|
-
'https://web.archive.org/web/20110203124321/https
|
|
464
|
+
'https://web.archive.org/web/20110203124321/https://example.com/',
|
|
465
465
|
);
|
|
466
466
|
expect(secondDateLink?.textContent?.trim()).to.equal('Feb 03, 2011');
|
|
467
467
|
});
|
|
@@ -509,7 +509,7 @@ describe('List Tile', () => {
|
|
|
509
509
|
const firstDateLink = captureDatesUl?.children[0]?.querySelector('a[href]');
|
|
510
510
|
expect(firstDateLink, 'first date link').to.exist;
|
|
511
511
|
expect(firstDateLink?.getAttribute('href')).to.equal(
|
|
512
|
-
'https://web.archive.org/web/20100102123456/https
|
|
512
|
+
'https://web.archive.org/web/20100102123456/https://example.com/',
|
|
513
513
|
);
|
|
514
514
|
expect(firstDateLink?.textContent?.trim()).to.equal('Jan 02, 2010');
|
|
515
515
|
|
|
@@ -517,7 +517,7 @@ describe('List Tile', () => {
|
|
|
517
517
|
captureDatesUl?.children[1]?.querySelector('a[href]');
|
|
518
518
|
expect(secondDateLink, 'second date link').to.exist;
|
|
519
519
|
expect(secondDateLink?.getAttribute('href')).to.equal(
|
|
520
|
-
'https://web.archive.org/web/20110203124321/https
|
|
520
|
+
'https://web.archive.org/web/20110203124321/https://example.com/',
|
|
521
521
|
);
|
|
522
522
|
expect(secondDateLink?.textContent?.trim()).to.equal('Feb 03, 2011');
|
|
523
523
|
});
|
|
@@ -110,6 +110,65 @@ describe('Tile Dispatcher', () => {
|
|
|
110
110
|
window.open = oldWindowOpen;
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
it('should use model href as-is when not percent-encoded', async () => {
|
|
114
|
+
const el = await fixture<TileDispatcher>(html`
|
|
115
|
+
<tile-dispatcher
|
|
116
|
+
.model=${{
|
|
117
|
+
identifier: 'foo',
|
|
118
|
+
href: 'https://web.archive.org/web/20180613065659/http://www.sankei.com/',
|
|
119
|
+
}}
|
|
120
|
+
.baseNavigationUrl=${''}
|
|
121
|
+
></tile-dispatcher>
|
|
122
|
+
`);
|
|
123
|
+
|
|
124
|
+
const tileLink = el.shadowRoot?.querySelector(
|
|
125
|
+
'a[href]',
|
|
126
|
+
) as HTMLAnchorElement;
|
|
127
|
+
expect(tileLink).to.exist;
|
|
128
|
+
expect(tileLink.getAttribute('href')).to.equal(
|
|
129
|
+
'https://web.archive.org/web/20180613065659/http://www.sankei.com/',
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should decode percent-encoded model href before use', async () => {
|
|
134
|
+
const el = await fixture<TileDispatcher>(html`
|
|
135
|
+
<tile-dispatcher
|
|
136
|
+
.model=${{
|
|
137
|
+
identifier: 'foo',
|
|
138
|
+
href: 'https://web.archive.org/web/20180613065659/http%3A%2F%2Fwww.sankei.com%2F',
|
|
139
|
+
}}
|
|
140
|
+
.baseNavigationUrl=${''}
|
|
141
|
+
></tile-dispatcher>
|
|
142
|
+
`);
|
|
143
|
+
|
|
144
|
+
const tileLink = el.shadowRoot?.querySelector(
|
|
145
|
+
'a[href]',
|
|
146
|
+
) as HTMLAnchorElement;
|
|
147
|
+
expect(tileLink).to.exist;
|
|
148
|
+
expect(tileLink.getAttribute('href')).to.equal(
|
|
149
|
+
'https://web.archive.org/web/20180613065659/http://www.sankei.com/',
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should not decode %3A in Wayback URL path when not an encoded scheme', async () => {
|
|
154
|
+
// %3A in the path (e.g. /path/%3Asomething) is not a sign of over-encoding;
|
|
155
|
+
// only %3A%2F%2F (encoded "://") is.
|
|
156
|
+
const href =
|
|
157
|
+
'https://web.archive.org/web/20180613065659/https://example.com/path/%3Asomething';
|
|
158
|
+
const el = await fixture<TileDispatcher>(html`
|
|
159
|
+
<tile-dispatcher
|
|
160
|
+
.model=${{ identifier: 'foo', href }}
|
|
161
|
+
.baseNavigationUrl=${''}
|
|
162
|
+
></tile-dispatcher>
|
|
163
|
+
`);
|
|
164
|
+
|
|
165
|
+
const tileLink = el.shadowRoot?.querySelector(
|
|
166
|
+
'a[href]',
|
|
167
|
+
) as HTMLAnchorElement;
|
|
168
|
+
expect(tileLink).to.exist;
|
|
169
|
+
expect(tileLink.getAttribute('href')).to.equal(href);
|
|
170
|
+
});
|
|
171
|
+
|
|
113
172
|
it('should toggle model checked state when manage check clicked', async () => {
|
|
114
173
|
const el = await fixture<TileDispatcher>(html`
|
|
115
174
|
<tile-dispatcher
|