text search and ui fixes:

- text support via <coll>/textIndex api, fallback to existing pageList text data
- page sorting: add score column for page search results with query
- safari: add min-height: fit-content for safari
- refresh: ensure replay refresh triggers refresh icon
- embeds: ensure deep linking working for search view pages (map view and query params)
This commit is contained in:
Ilya Kreymer
2020-08-07 16:48:02 -07:00
parent 69e7982d31
commit 4efce70a39
10 changed files with 303 additions and 193 deletions
+1
View File
@@ -11,6 +11,7 @@
"@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-free": "^5.13.0",
"@webrecorder/wabac": "github:webrecorder/wabac.js", "@webrecorder/wabac": "github:webrecorder/wabac.js",
"bulma": "^0.8.2", "bulma": "^0.8.2",
"fetch-ndjson": "^1.1.0",
"flexsearch": "^0.6.32", "flexsearch": "^0.6.32",
"keyword-mark-element": "^0.1.2", "keyword-mark-element": "^0.1.2",
"lit-element": "^2.3.1", "lit-element": "^2.3.1",
+9 -3
View File
@@ -31,6 +31,7 @@ class Coll extends LitElement
super(); super();
this.sourceUrl = null; this.sourceUrl = null;
this.inited = false; this.inited = false;
this.isLoading = false;
this.baseApiPrefix = "./wabac/api"; this.baseApiPrefix = "./wabac/api";
this.baseReplayPrefix = "./wabac"; this.baseReplayPrefix = "./wabac";
@@ -68,7 +69,9 @@ class Coll extends LitElement
collInfo: { type: Object, attribute: false }, collInfo: { type: Object, attribute: false },
coll: { type: String }, coll: { type: String },
hasStory: { type: Boolean }, hasStory: { type: Boolean },
isLoading: { type: Boolean },
tabData: { type: Object, attribute: false }, tabData: { type: Object, attribute: false },
@@ -129,6 +132,9 @@ class Coll extends LitElement
} else { } else {
window.location.hash = this._locationHash; window.location.hash = this._locationHash;
} }
if (this.embed && window.parent !== window) {
window.parent.postMessage(this.tabData, '*');
}
} }
this._locUpdateNeeded = false; this._locUpdateNeeded = false;
} }
@@ -372,7 +378,7 @@ class Coll extends LitElement
} }
renderTabHeader() { renderTabHeader() {
if (this.tabData.view === "replay" || (this.embed && this.embed !== "full")) { if (this.tabData.view === "replay") {
return ""; return "";
} }
@@ -548,10 +554,10 @@ class Coll extends LitElement
${isReplay ? html` ${isReplay ? html`
<wr-coll-replay .collInfo="${this.collInfo}" <wr-coll-replay .collInfo="${this.collInfo}"
sourceUrl="${this.sourceUrl}" sourceUrl="${this.sourceUrl}"
embed="${this.embed}"
url="${this.tabData.url || ""}" url="${this.tabData.url || ""}"
ts="${this.tabData.ts || ""}" ts="${this.tabData.ts || ""}"
@coll-tab-nav="${this.onCollTabNav}" id="replay" @coll-tab-nav="${this.onCollTabNav}" id="replay"
@replay-loading="${(e) => this.isLoading = e.detail.loading}"
class="${isReplay ? '' : 'is-hidden'}"> class="${isReplay ? '' : 'is-hidden'}">
</wr-coll-replay> </wr-coll-replay>
` : ``} ` : ``}
@@ -560,7 +566,7 @@ class Coll extends LitElement
onKeyDown(event) { onKeyDown(event) {
if (event.key === "Esc" || event.key === "Escape") { if (event.key === "Esc" || event.key === "Escape") {
event.target.value = this.replayUrl; event.target.value = this.url;
} }
} }
+13 -10
View File
@@ -18,6 +18,7 @@ class Embed extends LitElement
this.view = "replay"; this.view = "replay";
this.ts = ""; this.ts = "";
this.url = ""; this.url = "";
this.query = "";
this.config = ""; this.config = "";
this.coll = ""; this.coll = "";
this.paramString = null; this.paramString = null;
@@ -30,6 +31,8 @@ class Embed extends LitElement
return { return {
url: { type: String }, url: { type: String },
ts: { type: String }, ts: { type: String },
query: { type: String },
source: { type: String }, source: { type: String },
view: { type: String }, view: { type: String },
embed: { type: String }, embed: { type: String },
@@ -74,16 +77,11 @@ class Embed extends LitElement
this.title = event.data.title; this.title = event.data.title;
} }
console.log(event.data);
if (!this.deepLink) { if (!this.deepLink) {
return; return;
} }
const currHash = new URLSearchParams({ const currHash = new URLSearchParams(event.data);
url: event.data.url,
ts: event.data.ts,
});
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.hash = "#" + currHash.toString(); url.hash = "#" + currHash.toString();
window.history.replaceState({}, "", url); window.history.replaceState({}, "", url);
@@ -105,11 +103,19 @@ class Embed extends LitElement
if (qs.has("ts")) { if (qs.has("ts")) {
this.ts = qs.get("ts"); this.ts = qs.get("ts");
} }
if (qs.has("query")) {
this.query = qs.get("query");
}
if (qs.has("view")) {
this.view = qs.get("view");
}
} }
updated(changedProperties) { updated(changedProperties) {
if (changedProperties.has("url") || if (changedProperties.has("url") ||
changedProperties.has("ts") || changedProperties.has("ts") ||
changedProperties.has("query") ||
changedProperties.has("view") ||
changedProperties.has("source")) { changedProperties.has("source")) {
if (this.embed === null) { if (this.embed === null) {
@@ -127,6 +133,7 @@ class Embed extends LitElement
this.hashString = new URLSearchParams({ this.hashString = new URLSearchParams({
url: this.url, url: this.url,
ts: this.ts, ts: this.ts,
query: this.query,
view: this.view view: this.view
}).toString(); }).toString();
} }
@@ -180,9 +187,6 @@ Please try a different browser.\n
} }
onLoad(event) { onLoad(event) {
if (this.injected) {
return;
}
const win = event.target.contentWindow; const win = event.target.contentWindow;
const doc = event.target.contentDocument; const doc = event.target.contentDocument;
@@ -199,7 +203,6 @@ Please try a different browser.\n
//const script = event.target.contentDocument.querySelector("script"); //const script = event.target.contentDocument.querySelector("script");
script.src = scriptSrc; script.src = scriptSrc;
doc.head.appendChild(script); doc.head.appendChild(script);
this.injected = true;
} }
} }
+102 -21
View File
@@ -2,6 +2,7 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc'; import { wrapCss } from './misc';
import ndjson from 'fetch-ndjson';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
@@ -25,6 +26,7 @@ class Pages extends LitElement
this.flex = null; this.flex = null;
this.newQuery = null; this.newQuery = null;
this.loading = false; this.loading = false;
this.updatingSearch = false;
this.currList = 0; this.currList = 0;
@@ -42,6 +44,10 @@ class Pages extends LitElement
static get sortKeys() { static get sortKeys() {
return [ return [
{
"key": "",
"name": "Score",
},
{ {
"key": "title", "key": "title",
"name": "Title" "name": "Title"
@@ -64,6 +70,7 @@ class Pages extends LitElement
query: { type: String }, query: { type: String },
loading: { type: Boolean }, loading: { type: Boolean },
updatingSearch: { type: Boolean },
editable: { type: Boolean }, editable: { type: Boolean },
selectedPages: { type: Set }, selectedPages: { type: Set },
@@ -87,8 +94,14 @@ class Pages extends LitElement
updated(changedProperties) { updated(changedProperties) {
if (changedProperties.has("collInfo")) { if (changedProperties.has("collInfo")) {
this.updateTextSearch(); this.updateTextSearch();
} else if (changedProperties.has("query")) {
this.filter(); this.filter();
} else if (changedProperties.has("query") || changedProperties.has("currList")) {
// default sort to score if has query, otherwise timestamp
this.sortKey = this.query ? "" : "ts";
this.sortDesc = this.query ? false : true;
} else if (changedProperties.has("currList")) {
this.filter(); this.filter();
} }
if (changedProperties.has("active") && this.active) { if (changedProperties.has("active") && this.active) {
@@ -116,7 +129,8 @@ class Pages extends LitElement
} }
this.loading = true; this.loading = true;
if (this.flex && this.query) { if (this.flex && this.query) {
const result = await this.flex.search(this.query); const result = await this.flex.search(this.query, {limit: 25});
this.filteredPages = result; this.filteredPages = result;
} else { } else {
this.filteredPages = [...this.collInfo.pages]; this.filteredPages = [...this.collInfo.pages];
@@ -152,16 +166,71 @@ class Pages extends LitElement
this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {data}})); this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {data}}));
} }
updateTextSearch() { async updateTextSearch() {
this.flex = new FlexSearch({ if (this.updatingSearch) {
doc: { return;
id: "id", }
field: ["url", "title", "text"],
},
async: true
});
this.flex.add(this.collInfo.pages); this.updatingSearch = true;
let count = 0;
try {
const flex = new FlexSearch({
doc: {
id: "id",
field: ["url", "title", "text"],
},
async: true
});
this.flex = flex;
const cache = await caches.open("cache:" + this.collInfo.coll);
const indexUrl = `${this.collInfo.apiPrefix}/textIndex`;
let resp = await cache.match(indexUrl);
if (!resp) {
resp = await fetch(`${this.collInfo.apiPrefix}/textIndex`);
if (resp.status === 200) {
cache.put(indexUrl, resp.clone());
}
}
let lines = [];
async function flush() {
let curr = lines;
lines = [];
await flex.add(curr);
console.log("added " + count + " " + curr.length);
console.log(flex.info());
}
for await (const line of ndjson(resp.body.getReader())) {
if (!line.text) {
continue;
}
line.id = ++count;
lines.push(line);
if ((count % 100) === 0) {
await flush();
}
}
await flush();
} finally {
if (count === 0) {
this.flex.add(this.collInfo.pages);
}
this.updatingSearch = false;
}
await this.filter();
} }
static get styles() { static get styles() {
@@ -313,6 +382,7 @@ class Pages extends LitElement
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
min-height: fit-content;
margin-bottom: 1.0em; margin-bottom: 1.0em;
border-bottom: 3px solid rgb(237, 237, 237); border-bottom: 3px solid rgb(237, 237, 237);
@@ -428,8 +498,11 @@ class Pages extends LitElement
</div>` : html``} </div>` : html``}
<div class="header columns is-hidden-mobile"> <div class="header columns is-hidden-mobile">
<a @click="${this.onSort}" data-key="title" class="column is-2 ${this.sortKey === "title" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a> ${this.query ? html`
<a @click="${this.onSort}" data-key="ts" class="column is-8 pagetitle ${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Page Title</a> <a @click="${this.onSort}" data-key="" class="column is-1 ${this.sortKey === "" ? (this.sortDesc ? "desc" : "asc") : ''}">Score</a>` : ``}
<a @click="${this.onSort}" data-key="ts" class="column is-2 ${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a>
<a @click="${this.onSort}" data-key="title" class="column is-6 pagetitle ${this.sortKey === "title" ? (this.sortDesc ? "desc" : "asc") : ''}">Page Title</a>
</div> </div>
<div class="is-hidden-tablet mobile-header"> <div class="is-hidden-tablet mobile-header">
@@ -453,9 +526,9 @@ class Pages extends LitElement
${this.renderPageHeader()} ${this.renderPageHeader()}
</div> </div>
<div class="scroller" @scroll="${this.onScroll}"> <div class="scroller" @scroll="${this.onScroll}">
${this.sortedPages.map((p) => html` ${this.sortedPages.map((p, i) => html`
<div class="content ${this.selectedPages.has(p.id) ? 'selected' : ''}"> <div class="content ${this.selectedPages.has(p.id) ? 'selected' : ''}">
<wr-page-entry .editable="${this.editable}" .selected="${this.selectedPages.has(p.id)}" @sel-page="${this.onSelectToggle}" @delete-page="${this.onDeletePage}" replayPrefix="${this.collInfo.replayPrefix}" query="${this.query}" .page="${p}"> <wr-page-entry .index="${this.query ? (this.sortDesc ? this.sortedPages.length - i : i + 1) : 0}" .editable="${this.editable}" .selected="${this.selectedPages.has(p.id)}" @sel-page="${this.onSelectToggle}" @delete-page="${this.onDeletePage}" replayPrefix="${this.collInfo.replayPrefix}" query="${this.query}" .page="${p}">
</wr-page-entry> </wr-page-entry>
</div>`)} </div>`)}
</div> </div>
@@ -476,7 +549,7 @@ class Pages extends LitElement
onSort(event) { onSort(event) {
event.preventDefault(); event.preventDefault();
const key = event.currentTarget.getAttribute("data-key"); const key = event.currentTarget.getAttribute("data-key") || "";
if (key === this.sortKey) { if (key === this.sortKey) {
this.sortDesc = !this.sortDesc; this.sortDesc = !this.sortDesc;
} else { } else {
@@ -546,7 +619,6 @@ class Pages extends LitElement
//} //}
this.updateTextSearch(); this.updateTextSearch();
this.filter();
this.requestUpdate(); this.requestUpdate();
} }
@@ -555,6 +627,10 @@ class Pages extends LitElement
return html`No Pages defined this archive. Check out&nbsp;<a href="#view=resources">Page Resources</a>&nbsp;to search by URL.`; return html`No Pages defined this archive. Check out&nbsp;<a href="#view=resources">Page Resources</a>&nbsp;to search by URL.`;
} }
if (this.updatingSearch) {
return "Initializing Search..."
}
if (this.loading) { if (this.loading) {
return "Searching..."; return "Searching...";
} }
@@ -592,6 +668,7 @@ class PageEntry extends LitElement
this.deleting = false; this.deleting = false;
this.editable = false; this.editable = false;
this.iconValid = false; this.iconValid = false;
this.index = 0;
} }
static get properties() { static get properties() {
@@ -603,7 +680,8 @@ class PageEntry extends LitElement
deleting: { type: Boolean }, deleting: { type: Boolean },
selected: { type: Boolean }, selected: { type: Boolean },
editable: { type: Boolean }, editable: { type: Boolean },
iconValid: { type: Boolean } iconValid: { type: Boolean },
index: { type: Number },
} }
} }
@@ -705,11 +783,14 @@ class PageEntry extends LitElement
</div>` : ``} </div>` : ``}
<div class="columns"> <div class="columns">
${this.index ? html`
<div class="column col-index is-1">${this.index}.</div>
` : ``}
<div class="column col-date is-2"> <div class="column col-date is-2">
<div>${date ? date.toLocaleDateString() : ""}</div> <div>${date ? date.toLocaleDateString() : ""}</div>
<div>${date ? date.toLocaleTimeString() : ""}</div> <div>${date ? date.toLocaleTimeString() : ""}</div>
</div> </div>
<div class="column is-9"> <div class="column">
<article class="media"> <article class="media">
<figure class="media-left"> <figure class="media-left">
<p class=""> <p class="">
+8 -17
View File
@@ -8,7 +8,6 @@ class Replay extends LitElement
{ {
constructor() { constructor() {
super(); super();
this.isLoading = false;
this.replayUrl = ""; this.replayUrl = "";
this.replayTS = ""; this.replayTS = "";
this.url = ""; this.url = "";
@@ -34,7 +33,6 @@ class Replay extends LitElement
title: { type: String }, title: { type: String },
iframeUrl: { type: String }, iframeUrl: { type: String },
isLoading: { type: Boolean },
showAuth: { type: Boolean }, showAuth: { type: Boolean },
} }
@@ -86,14 +84,6 @@ class Replay extends LitElement
this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {replaceLoc: true, data}})); this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {replaceLoc: true, data}}));
} }
if (this.embed && window.parent !== window && changedProperties.has("title")) {
window.parent.postMessage({
title: this.title,
url: this.replayUrl,
ts: this.replayTS
}, '*');
}
} }
onReplayMessage(event) { onReplayMessage(event) {
@@ -117,7 +107,7 @@ class Replay extends LitElement
} }
waitForLoad() { waitForLoad() {
this.isLoading = true; this.setLoading();
this._loadPoll = window.setInterval(() => { this._loadPoll = window.setInterval(() => {
const iframe = this.renderRoot.querySelector("iframe"); const iframe = this.renderRoot.querySelector("iframe");
if (!iframe || !iframe.contentDocument || !iframe.contentWindow || if (!iframe || !iframe.contentDocument || !iframe.contentWindow ||
@@ -128,7 +118,8 @@ class Replay extends LitElement
} }
clearLoading(iframeWin) { clearLoading(iframeWin) {
this.isLoading = false; this.dispatchEvent(new CustomEvent("replay-loading", {detail: {loading: false}}));
if (this._loadPoll) { if (this._loadPoll) {
window.clearInterval(this._loadPoll); window.clearInterval(this._loadPoll);
this._loadPoll = null; this._loadPoll = null;
@@ -136,16 +127,16 @@ class Replay extends LitElement
if (iframeWin) { if (iframeWin) {
iframeWin.addEventListener("beforeunload", () => { iframeWin.addEventListener("beforeunload", () => {
this.isLoading = true; this.setLoading();
}); });
} }
} }
refresh() { setLoading() {
if (this.isLoading && !forceReload) { this.dispatchEvent(new CustomEvent("replay-loading", {detail: {loading: true}}));
return; }
}
refresh() {
const iframe = this.renderRoot.querySelector("iframe"); const iframe = this.renderRoot.querySelector("iframe");
if (iframe) { if (iframe) {
+17 -5
View File
@@ -16,6 +16,12 @@ class Sorter extends LitElement
this.numResults = DEFAULT_RESULTS; this.numResults = DEFAULT_RESULTS;
this.defaultDesc = false; this.defaultDesc = false;
this.sortKey = "url";
this.sortDesc = false;
this.defaultKey = "";
this.defaultDesc = false;
} }
static get properties() { static get properties() {
@@ -68,13 +74,19 @@ class Sorter extends LitElement
this.sortedData = [...this.data]; this.sortedData = [...this.data];
this.numResults = DEFAULT_RESULTS; this.numResults = DEFAULT_RESULTS;
this.sortedData.sort((first, second) => { if (this.sortKey === "") {
if (first[this.sortKey] === second[this.sortKey]) { if (this.sortDesc) {
return 0; this.sortedData.reverse();
} }
} else {
this.sortedData.sort((first, second) => {
if (first[this.sortKey] === second[this.sortKey]) {
return 0;
}
return (this.sortDesc == (first[this.sortKey] < second[this.sortKey])) ? 1 : -1; return (this.sortDesc == (first[this.sortKey] < second[this.sortKey])) ? 1 : -1;
}); });
}
this.sendSortChanged(); this.sendSortChanged();
} }
+2
View File
@@ -235,10 +235,12 @@ class URLResources extends LitElement
} }
.result { .result {
border-bottom: 1px #dbdbdb solid; border-bottom: 1px #dbdbdb solid;
min-height: fit-content;
} }
.results-head { .results-head {
border-bottom: 2px #dbdbdb solid; border-bottom: 2px #dbdbdb solid;
margin-right: 16px; margin-right: 16px;
min-height: fit-content;
} }
.results-head a { .results-head a {
color: black; color: black;
+15 -15
View File
File diff suppressed because one or more lines are too long
+129 -120
View File
File diff suppressed because one or more lines are too long
+7 -2
View File
@@ -292,8 +292,8 @@
"@xtuc/long" "4.2.2" "@xtuc/long" "4.2.2"
"@webrecorder/wabac@github:webrecorder/wabac.js": "@webrecorder/wabac@github:webrecorder/wabac.js":
version "2.0.3" version "2.0.4"
resolved "https://codeload.github.com/webrecorder/wabac.js/tar.gz/39c73bd44a0d0659d0438cb10a29f46c2d58a671" resolved "https://codeload.github.com/webrecorder/wabac.js/tar.gz/a286e76e705a8686c124e755cb22fad9618f4f23"
dependencies: dependencies:
brotli "github:foliojs/brotli.js" brotli "github:foliojs/brotli.js"
fast-xml-parser "^3.15.1" fast-xml-parser "^3.15.1"
@@ -2152,6 +2152,11 @@ fd-slicer@~1.1.0:
dependencies: dependencies:
pend "~1.2.0" pend "~1.2.0"
fetch-ndjson@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fetch-ndjson/-/fetch-ndjson-1.1.0.tgz#dff9f70493c2a8a61b46d4c0a6d2c04255c01618"
integrity sha512-/2xKQdp7MMPMReDXvWiU5BHsvcBjrGGO8s3jJBfEWL/dJXG3/u++BG1WNBM/AGFNvK/1HdrM6H8jFWFCBa9Mdw==
figgy-pudding@^3.5.1: figgy-pudding@^3.5.1:
version "3.5.2" version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"