split pageentry into pageentry.js

text snippet: only include the first match to limit size of snippets
This commit is contained in:
Ilya Kreymer
2020-08-31 19:20:09 -07:00
parent 804529aeea
commit 609bcaff85
3 changed files with 350 additions and 336 deletions
+348
View File
@@ -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`
<div class="check-select">
<label class="checkbox">
<input @change=${this.onSendSelToggle} type="checkbox" .checked="${this.selected}">
</label>
</div>` : ``}
<div class="${this.isSidebar ? "sidebar" : "full"}" @click="${this.onReplay}">
<div class="columns">
${this.index ? html`
<div class="column col-index is-1 is-size-7">${this.index}.</div>
` : ``}
<div class="column col-date is-2">
<div>${date ? date.toLocaleDateString() : ""}</div>
<div>${date ? date.toLocaleTimeString() : ""}</div>
</div>
<div class="column">
<article class="media">
<figure class="media-left">
<p class="">
${this.iconValid ? html`
<img class="favicon" @error="${(e) => this.iconValid = false}" src="${this.replayPrefix}/${this.timestamp}id_/${p.favIconUrl}"/>` : html`
<span class="favicon"></span>`}
</p>
</figure>
<div class="media-content ${this.isCurrent ? 'current' : ''}">
<a @click="${this.onReplay}" href="#">
${this.isCurrent ? html`<p class="curr-page is-pulled-right">Current Page</p>` : ``}
<p class="is-size-6 has-text-weight-bold has-text-link text">
<keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark>
</p>
<p class="has-text-dark text"><keyword-mark keywords="${this.query}">${p.url}</keyword-mark></p>
<p class="has-text-grey-dark text is-inline-date">
${date ? date.toLocaleString(): ""}
</p>
</a>
${this.textSnippet ? html`
<div class="text"><keyword-mark keywords="${this.query}">${this.textSnippet}</keyword-mark></div>` : html``}
</div>
${hasSize ? html`
<div class="media-right" style="margin-right: 2em">
${prettyBytes(p.size)}
</div>` : ``}
</article>
</div>
</div>
</div>
${this.editable ? html`
${!this.deleting ? html`
<button @click="${this.onSendDeletePage}" class="delete"></button>` : html`
<button class="button delete is-loading is-static"></button>
`}` : ''}
`;
}
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 };
+1 -336
View File
@@ -4,10 +4,7 @@ import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc'; import { wrapCss } from './misc';
import ndjson from 'fetch-ndjson'; import ndjson from 'fetch-ndjson';
import prettyBytes from 'pretty-bytes';
import FlexSearch from 'flexsearch'; import FlexSearch from 'flexsearch';
import "keyword-mark-element/lib/keyword-mark.js";
import { getTS } from './pageutils'; 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`
<div class="check-select">
<label class="checkbox">
<input @change=${this.onSendSelToggle} type="checkbox" .checked="${this.selected}">
</label>
</div>` : ``}
<div class="${this.isSidebar ? "sidebar" : "full"}" @click="${this.onReplay}">
<div class="columns">
${this.index ? html`
<div class="column col-index is-1 is-size-7">${this.index}.</div>
` : ``}
<div class="column col-date is-2">
<div>${date ? date.toLocaleDateString() : ""}</div>
<div>${date ? date.toLocaleTimeString() : ""}</div>
</div>
<div class="column">
<article class="media">
<figure class="media-left">
<p class="">
${this.iconValid ? html`
<img class="favicon" @error="${(e) => this.iconValid = false}" src="${this.replayPrefix}/${this.timestamp}id_/${p.favIconUrl}"/>` : html`
<span class="favicon"></span>`}
</p>
</figure>
<div class="media-content ${this.isCurrent ? 'current' : ''}">
<a @click="${this.onReplay}" href="#">
${this.isCurrent ? html`<p class="curr-page is-pulled-right">Current Page</p>` : ``}
<p class="is-size-6 has-text-weight-bold has-text-link text">
<keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark>
</p>
<p class="has-text-dark text"><keyword-mark keywords="${this.query}">${p.url}</keyword-mark></p>
<p class="has-text-grey-dark text is-inline-date">
${date ? date.toLocaleString(): ""}
</p>
</a>
${this.textSnippet ? html`
<div class="text"><keyword-mark keywords="${this.query}">${this.textSnippet}</keyword-mark></div>` : html``}
</div>
${hasSize ? html`
<div class="media-right" style="margin-right: 2em">
${prettyBytes(p.size)}
</div>` : ``}
</article>
</div>
</div>
</div>
${this.editable ? html`
${!this.deleting ? html`
<button @click="${this.onSendDeletePage}" class="delete"></button>` : html`
<button class="button delete is-loading is-static"></button>
`}` : ''}
`;
}
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-view", Pages);
customElements.define("wr-page-entry", PageEntry);
export { Pages, PageEntry }; export { Pages };
+1
View File
@@ -6,6 +6,7 @@ import { Story } from './story';
import { GDrive } from './gdrive'; import { GDrive } from './gdrive';
import { Loader } from './loader'; import { Loader } from './loader';
import { Pages } from './pages'; import { Pages } from './pages';
import { PageEntry } from './pageentry';
import { Replay } from './replay'; import { Replay } from './replay';
import { Sorter } from './sorter'; import { Sorter } from './sorter';
import { URLResources } from './url-resources'; import { URLResources } from './url-resources';