diff --git a/src/pageentry.js b/src/pageentry.js new file mode 100644 index 0000000..d61b9fb --- /dev/null +++ b/src/pageentry.js @@ -0,0 +1,348 @@ +import prettyBytes from 'pretty-bytes'; + +import { LitElement, html, css } from 'lit-element'; + +import "keyword-mark-element/lib/keyword-mark.js"; + +import { getTS } from './pageutils'; + +import { wrapCss } from './misc'; + + +// =========================================================================== +class PageEntry extends LitElement +{ + constructor() { + super(); + this.query = ""; + this.textSnippet = ""; + this.page = null; + this.replayPrefix = ""; + this.deleting = false; + this.editable = false; + this.iconValid = false; + this.index = 0; + this.isSidebar = false; + this.isCurrent = false; + + this.timestamp = ""; + this.date = null; + } + + static get properties() { + return { + query: { type: String }, + textSnippet: { type: String }, + page: { type: Object }, + replayPrefix: { type: String }, + deleting: { type: Boolean }, + selected: { type: Boolean }, + editable: { type: Boolean }, + iconValid: { type: Boolean }, + index: { type: Number }, + isSidebar: { type: Boolean }, + isCurrent: { type: Boolean }, + timestamp: { type: String }, + date: { type: Object } + } + } + + static get styles() { + return wrapCss(css` + :host { + min-height: min-content; + width: 100%; + word-break: break-all; + position: relative; + display: flex; + flex-direction: row; + background: transparent; + } + + .check-select { + padding-right: 1.0em; + height: 100%; + margin: auto 0 auto 0; + } + + .columns { + width: 100%; + } + + .full { + flex: auto; + min-height: 0px; + } + + .sidebar .column { + width: unset !important; + } + + .sidebar { + width: 100%; + } + + .favicon { + width: 24px !important; + height: 24px !important; + display: inline-block; + vertical-align: text-bottom; + } + img.favicon { + filter: drop-shadow(1px 1px 2px grey); + } + + .media-left { + align-self: center; + } + + .delete { + position: absolute; + top: 8px; + right: 8px; + } + + .delete:hover { + background-color: rgb(241, 70, 104); + } + + @media screen and (max-width: 768px) { + ${PageEntry.sidebarStyles()} + } + + ${PageEntry.sidebarStyles(css`.sidebar `)} + + .current a { + background-color: rgb(207, 243, 255); + font-style: italic; + display: block; + } + + .current .curr-page { + font-style: italic; + font-size: 9px; + color: black; + } + + .is-inline-date { + display: none; + } + `); + } + + static sidebarStyles(prefix = css``) { + return css` + ${prefix} .col-date { + margin-left: calc(24px + 1rem); + display: none; + } + ${prefix} .col-date div { + display: inline; + } + ${prefix} .col-index { + position: absolute; + top: 0px; + left: 0px; + margin-top: -0.75em; + } + ${prefix} .columns { + display: flex; + flex-direction: column-reverse; + } + ${prefix} .is-inline-date { + display: initial !important; + font-style: italic; + } + `; + } + + updated(changedProperties) { + if (changedProperties.has("page") || changedProperties.has("query")) { + this.updateSnippet(); + this.iconValid = !!this.page.favIconUrl; + //this.updateFavIcon(); + this.deleting = false; + + const res = this.getDateTS(); + this.timestamp = res.timestamp; + this.date = res.date; + } + } + + getDateTS() { + let date = null; + let ts = this.page.ts; + try { + date = new Date(this.page.ts || this.page.date); + } catch (e) { + + } + + const timestamp = (date && !isNaN(date)) ? getTS(date.toISOString()) : ""; + return {date, timestamp}; + } + + render() { + const p = this.page; + const date = this.date; + + const hasSize = typeof(p.size) === "number"; + + return html` + ${this.editable ? html` +
+ +
` : ``} + +
+
+ ${this.index ? html` +
${this.index}.
+ ` : ``} +
+
${date ? date.toLocaleDateString() : ""}
+
${date ? date.toLocaleTimeString() : ""}
+
+
+ +
+
+
+ + ${this.editable ? html` + ${!this.deleting ? html` + ` : html` + + `}` : ''} + `; + } + + async updateFavIcon() { + if (!this.page.favIconUrl) { + this.favIconData = null; + return; + } + + const resp = await fetch(`${this.replayPrefix}/${this.timestamp}id_/${this.page.favIconUrl}`); + + if (resp.status != 200) { + this.favIconData = null; + return; + } + + const payload = await resp.arrayBuffer(); + const mime = resp.headers.get("content-type"); + + try { + this.favIconData = `data:${mime};base64,${btoa(String.fromCharCode.apply(null, payload))}`; + } catch (e) { + console.log(e); + this.favIconData = null; + } + } + + updateSnippet() { + const oldVal = this.textSnippet; + + if (!this.query || !this.page.text) { + this.textSnippet = null; + this.requestUpdate('textSnippet', oldVal); + return; + } + + let textContent = this.page.text; + let query = this.query; + + let inx = textContent.indexOf(this.query); + + if (inx < 0) { + let textLower = textContent.toLowerCase(); + let queryLower = query.toLowerCase(); + + inx = textLower.indexOf(queryLower); + + if (inx < 0) { + this.textSnippet = null; + this.requestUpdate('textSnippet', oldVal); + return; + } + + textContent = textLower; + query = queryLower; + } + + //let lastInx = textContent.lastIndexOf(query); + let lastInx = inx; + + inx = Math.max(inx - 100, 0); + lastInx = Math.min(lastInx + 200, textContent.length); + + if (inx === 0 && lastInx === textContent.length) { + this.textSnippet = textContent; + } else { + this.textSnippet = "..." + textContent.slice(inx, lastInx) + "..."; + } + + this.requestUpdate('textSnippet', oldVal); + } + + onReplay(event) { + event.preventDefault(); + + const data = { + url: this.page.url, + ts: this.timestamp, + view: "replay" + }; + this.sendChangeEvent(data); + return false; + } + + sendChangeEvent(data) { + this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data}})); + } + + onSendDeletePage(event) { + const page = this.page.id; + this.deleting = true; + this.dispatchEvent(new CustomEvent("delete-page", {detail: {page}})); + } + + onSendSelToggle(event) { + const page = this.page.id; + const selected = event.currentTarget.checked; + this.dispatchEvent(new CustomEvent("sel-page", {detail: {page, selected}})); + } +} + +customElements.define("wr-page-entry", PageEntry); + +export { PageEntry }; diff --git a/src/pages.js b/src/pages.js index b8db9ad..4fa89b1 100644 --- a/src/pages.js +++ b/src/pages.js @@ -4,10 +4,7 @@ import { LitElement, html, css } from 'lit-element'; import { wrapCss } from './misc'; import ndjson from 'fetch-ndjson'; -import prettyBytes from 'pretty-bytes'; - import FlexSearch from 'flexsearch'; -import "keyword-mark-element/lib/keyword-mark.js"; import { getTS } from './pageutils'; @@ -824,340 +821,8 @@ class Pages extends LitElement } } -// =========================================================================== -class PageEntry extends LitElement -{ - constructor() { - super(); - this.query = ""; - this.textSnippet = ""; - this.page = null; - this.replayPrefix = ""; - this.deleting = false; - this.editable = false; - this.iconValid = false; - this.index = 0; - this.isSidebar = false; - this.isCurrent = false; - this.timestamp = ""; - this.date = null; - } - - static get properties() { - return { - query: { type: String }, - textSnippet: { type: String }, - page: { type: Object }, - replayPrefix: { type: String }, - deleting: { type: Boolean }, - selected: { type: Boolean }, - editable: { type: Boolean }, - iconValid: { type: Boolean }, - index: { type: Number }, - isSidebar: { type: Boolean }, - isCurrent: { type: Boolean }, - timestamp: { type: String }, - date: { type: Object } - } - } - - static get styles() { - return wrapCss(css` - :host { - min-height: min-content; - width: 100%; - word-break: break-all; - position: relative; - display: flex; - flex-direction: row; - background: transparent; - } - - .check-select { - padding-right: 1.0em; - height: 100%; - margin: auto 0 auto 0; - } - - .columns { - width: 100%; - } - - .full { - flex: auto; - min-height: 0px; - } - - .sidebar .column { - width: unset !important; - } - - .sidebar { - width: 100%; - } - - .favicon { - width: 24px !important; - height: 24px !important; - display: inline-block; - vertical-align: text-bottom; - } - img.favicon { - filter: drop-shadow(1px 1px 2px grey); - } - - .media-left { - align-self: center; - } - - .delete { - position: absolute; - top: 8px; - right: 8px; - } - - .delete:hover { - background-color: rgb(241, 70, 104); - } - - @media screen and (max-width: 768px) { - ${PageEntry.sidebarStyles()} - } - - ${PageEntry.sidebarStyles(css`.sidebar `)} - - .current a { - background-color: rgb(207, 243, 255); - font-style: italic; - display: block; - } - - .current .curr-page { - font-style: italic; - font-size: 9px; - color: black; - } - - .is-inline-date { - display: none; - } - `); - } - - static sidebarStyles(prefix = css``) { - return css` - ${prefix} .col-date { - margin-left: calc(24px + 1rem); - display: none; - } - ${prefix} .col-date div { - display: inline; - } - ${prefix} .col-index { - position: absolute; - top: 0px; - left: 0px; - margin-top: -0.75em; - } - ${prefix} .columns { - display: flex; - flex-direction: column-reverse; - } - ${prefix} .is-inline-date { - display: initial !important; - font-style: italic; - } - `; - } - - updated(changedProperties) { - if (changedProperties.has("page") || changedProperties.has("query")) { - this.updateSnippet(); - this.iconValid = !!this.page.favIconUrl; - //this.updateFavIcon(); - this.deleting = false; - - const res = this.getDateTS(); - this.timestamp = res.timestamp; - this.date = res.date; - } - } - - getDateTS() { - let date = null; - let ts = this.page.ts; - try { - date = new Date(this.page.ts || this.page.date); - } catch (e) { - - } - - const timestamp = (date && !isNaN(date)) ? getTS(date.toISOString()) : ""; - return {date, timestamp}; - } - - render() { - const p = this.page; - const date = this.date; - - const hasSize = typeof(p.size) === "number"; - - return html` - ${this.editable ? html` -
- -
` : ``} - -
-
- ${this.index ? html` -
${this.index}.
- ` : ``} -
-
${date ? date.toLocaleDateString() : ""}
-
${date ? date.toLocaleTimeString() : ""}
-
-
- -
-
-
- - ${this.editable ? html` - ${!this.deleting ? html` - ` : html` - - `}` : ''} - `; - } - - async updateFavIcon() { - if (!this.page.favIconUrl) { - this.favIconData = null; - return; - } - - const resp = await fetch(`${this.replayPrefix}/${this.timestamp}id_/${this.page.favIconUrl}`); - - if (resp.status != 200) { - this.favIconData = null; - return; - } - - const payload = await resp.arrayBuffer(); - const mime = resp.headers.get("content-type"); - - try { - this.favIconData = `data:${mime};base64,${btoa(String.fromCharCode.apply(null, payload))}`; - } catch (e) { - console.log(e); - this.favIconData = null; - } - } - - updateSnippet() { - const oldVal = this.textSnippet; - - if (!this.query || !this.page.text) { - this.textSnippet = null; - this.requestUpdate('textSnippet', oldVal); - return; - } - - let textContent = this.page.text; - let query = this.query; - - let inx = textContent.indexOf(this.query); - - if (inx < 0) { - let textLower = textContent.toLowerCase(); - let queryLower = query.toLowerCase(); - - inx = textLower.indexOf(queryLower); - - if (inx < 0) { - this.textSnippet = null; - this.requestUpdate('textSnippet', oldVal); - return; - } - - textContent = textLower; - query = queryLower; - } - - let lastInx = textContent.lastIndexOf(query); - - inx = Math.max(inx - 100, 0); - lastInx = Math.min(lastInx + 200, textContent.length); - - if (inx === 0 && lastInx === textContent.length) { - this.textSnippet = textContent; - } else { - this.textSnippet = "..." + textContent.slice(inx, lastInx) + "..."; - } - - this.requestUpdate('textSnippet', oldVal); - } - - onReplay(event) { - event.preventDefault(); - - const data = { - url: this.page.url, - ts: this.timestamp, - view: "replay" - }; - this.sendChangeEvent(data); - return false; - } - - sendChangeEvent(data) { - this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data}})); - } - - onSendDeletePage(event) { - const page = this.page.id; - this.deleting = true; - this.dispatchEvent(new CustomEvent("delete-page", {detail: {page}})); - } - - onSendSelToggle(event) { - const page = this.page.id; - const selected = event.currentTarget.checked; - this.dispatchEvent(new CustomEvent("sel-page", {detail: {page, selected}})); - } -} customElements.define("wr-page-view", Pages); -customElements.define("wr-page-entry", PageEntry); -export { Pages, PageEntry }; +export { Pages }; diff --git a/src/ui.js b/src/ui.js index 49aad53..8e8dd45 100644 --- a/src/ui.js +++ b/src/ui.js @@ -6,6 +6,7 @@ import { Story } from './story'; import { GDrive } from './gdrive'; import { Loader } from './loader'; import { Pages } from './pages'; +import { PageEntry } from './pageentry'; import { Replay } from './replay'; import { Sorter } from './sorter'; import { URLResources } from './url-resources';