Compare commits
No commits in common. "28111cc8eb93b9ac8b1bbac45ceb8a2dabc87f64" and "5f068ac0365250583b3363ebda7389b50233d555" have entirely different histories.
28111cc8eb
...
5f068ac036
5 changed files with 75 additions and 491 deletions
42
main.go
42
main.go
|
|
@ -140,8 +140,6 @@ func main() { // {{{
|
|||
http.HandleFunc("/sync/to_server", authenticated(actionSyncToServer))
|
||||
|
||||
http.HandleFunc("/node/retrieve/{uuid}", authenticated(actionNodeRetrieve))
|
||||
http.HandleFunc("/node/history/retrieve/{uuid}/{offset}", authenticated(actionNodeHistoryRetrieve))
|
||||
http.HandleFunc("/node/history/count/{uuid}", authenticated(actionNodeHistoryCount))
|
||||
|
||||
http.HandleFunc("/service_worker.js", pageServiceWorker)
|
||||
|
||||
|
|
@ -330,46 +328,6 @@ func actionNodeRetrieve(w http.ResponseWriter, r *http.Request) { // {{{
|
|||
"Node": node,
|
||||
})
|
||||
} // }}}
|
||||
func actionNodeHistoryRetrieve(w http.ResponseWriter, r *http.Request) { // {{{
|
||||
user := getUser(r)
|
||||
var err error
|
||||
|
||||
uuid := r.PathValue("uuid")
|
||||
offset, err := strconv.Atoi(r.PathValue("offset"))
|
||||
if err != nil {
|
||||
responseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
nodes, hasMore, err := RetrieveNodeHistory(user.UserID, uuid, offset)
|
||||
if err != nil {
|
||||
responseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
responseData(w, map[string]any{
|
||||
"OK": true,
|
||||
"Nodes": nodes,
|
||||
"HasMore": hasMore,
|
||||
})
|
||||
} // }}}
|
||||
func actionNodeHistoryCount(w http.ResponseWriter, r *http.Request) { // {{{
|
||||
user := getUser(r)
|
||||
var err error
|
||||
|
||||
uuid := r.PathValue("uuid")
|
||||
|
||||
count, err := RetrieveNodeHistoryCount(user.UserID, uuid)
|
||||
if err != nil {
|
||||
responseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
responseData(w, map[string]any{
|
||||
"OK": true,
|
||||
"Count": count,
|
||||
})
|
||||
} // }}}
|
||||
func actionSyncToServer(w http.ResponseWriter, r *http.Request) { // {{{
|
||||
user := getUser(r)
|
||||
|
||||
|
|
|
|||
67
node.go
67
node.go
|
|
@ -3,8 +3,8 @@ package main
|
|||
import (
|
||||
// External
|
||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
||||
"github.com/derektata/lorem/ipsum"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/derektata/lorem/ipsum"
|
||||
|
||||
// Standard
|
||||
"database/sql"
|
||||
|
|
@ -248,71 +248,6 @@ func RetrieveNode(userID int, nodeUUID string) (node Node, err error) { // {{{
|
|||
|
||||
return
|
||||
} // }}}
|
||||
func RetrieveNodeHistory(userID int, nodeUUID string, offset int) (nodes []Node, hasMore bool, err error) { // {{{
|
||||
nodes = []Node{}
|
||||
|
||||
var rows *sqlx.Rows
|
||||
rows, err = db.Queryx(`
|
||||
SELECT
|
||||
uuid,
|
||||
user_id,
|
||||
name,
|
||||
created,
|
||||
updated,
|
||||
content,
|
||||
content_encrypted
|
||||
FROM node_history
|
||||
WHERE
|
||||
user_id = $1 AND
|
||||
uuid = $2
|
||||
LIMIT $3 OFFSET $4
|
||||
`,
|
||||
userID,
|
||||
nodeUUID,
|
||||
SYNC_PAGINATION+1,
|
||||
offset,
|
||||
)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
node := Node{}
|
||||
if err = rows.StructScan(&node); err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
if len(nodes) > SYNC_PAGINATION {
|
||||
hasMore = true
|
||||
nodes = nodes[0 : len(nodes)-1]
|
||||
}
|
||||
return
|
||||
} // }}}
|
||||
func RetrieveNodeHistoryCount(userID int, nodeUUID string) (count int, err error) { // {{{
|
||||
var row *sql.Row
|
||||
row = db.QueryRow(`
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM node_history
|
||||
WHERE
|
||||
user_id = $1 AND
|
||||
uuid = $2
|
||||
`,
|
||||
userID,
|
||||
nodeUUID,
|
||||
)
|
||||
if err = row.Scan(&count); err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
} // }}}
|
||||
func NodeCrumbs(nodeUUID string) (nodes []Node, err error) { // {{{
|
||||
var rows *sqlx.Rows
|
||||
rows, err = db.Queryx(`
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
--thumbnail-width: 300px;
|
||||
--thumbnail-height: 100px;
|
||||
|
||||
/*
|
||||
--colorize: invert(10%) sepia(61%) saturate(5017%) hue-rotate(323deg) brightness(90%) contrast(109%);
|
||||
*/
|
||||
--colorize: invert(59%) sepia(71%) saturate(3270%) hue-rotate(327deg) brightness(100%) contrast(99%);
|
||||
|
||||
--line-color: #ccc;
|
||||
|
|
@ -20,11 +23,6 @@ html {
|
|||
filter: var(--colorize);
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1em;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* ------------------------------------- *
|
||||
* Default application grid in wide mode *
|
||||
* ------------------------------------- */
|
||||
|
|
@ -521,137 +519,52 @@ dialog.op {
|
|||
|
||||
|
||||
n2-pagehistory {
|
||||
|
||||
.back,
|
||||
.node-name {
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.group-label {
|
||||
font-weight: bold;
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
padding: 8px 32px;
|
||||
display: inline-block;
|
||||
margin-left: 32px;
|
||||
transform: translateY(14px);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.el-back-image,
|
||||
.el-back-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.group {
|
||||
border: 1px solid #ccc;
|
||||
padding: 32px;
|
||||
margin-bottom: 32px;
|
||||
border-radius: 8px;
|
||||
background-color: #fafafa;
|
||||
.el-node-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
box-shadow:
|
||||
rgba(0, 0, 0, 0.4) 0px 2px 4px,
|
||||
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
|
||||
rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
|
||||
}
|
||||
.el-nodes {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
.el-stats {
|
||||
margin-bottom: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-gap: 8px 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-gap: 4px 8px;
|
||||
|
||||
.el-fetch-history-progress {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.history-node {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
.el-back-image,
|
||||
.el-back-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pagination {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 16px;
|
||||
|
||||
.el-node-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.el-nodes {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: min-content minmax(min-content, max-content) min-content 1fr;
|
||||
|
||||
background-color: var(--line-color);
|
||||
gap: 1px;
|
||||
border: 1px solid var(--line-color);
|
||||
|
||||
n2-pagehistorynode>* {
|
||||
padding: 8px 12px;
|
||||
background-color: #fff;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
grid-gap: 32px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
user-select: none;
|
||||
|
||||
n2-pagehistorynode {
|
||||
|
||||
&.selected .el-index:after {
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
|
||||
content: '>';
|
||||
color: var(--color1);
|
||||
.el-prev {
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-index {
|
||||
position: relative;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.el-updated {
|
||||
white-space: initial;
|
||||
}
|
||||
|
||||
.el-date {
|
||||
white-space: nowrap;
|
||||
.el-next {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.el-time {
|
||||
white-space: nowrap;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.el-name {
|
||||
white-space: initial;
|
||||
/*overflow-wrap: anywhere;*/
|
||||
word-break: break-all;
|
||||
color: var(--color1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 16px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
grid-gap: 16px;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
|
||||
.el-prev,
|
||||
.el-next {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border: 1px solid #aaa;
|
||||
background-color: #eee;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -456,30 +456,13 @@ class NodeHistoryStore extends SimpleNodeStore {
|
|||
request.onerror = (event) => reject(event.target.error)
|
||||
})
|
||||
}//}}}
|
||||
hasNode(uuid, updated) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = this.db
|
||||
.transaction(['nodes', this.storeName], 'readonly')
|
||||
.objectStore(this.storeName)
|
||||
.getKey([uuid, updated])
|
||||
|
||||
req.onsuccess = (event) => {
|
||||
resolve(event.target.result !== undefined)
|
||||
}
|
||||
|
||||
req.onerror = (event) => {
|
||||
console.log(event.target.error)
|
||||
reject(event.target.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
retrievePage(uuid, perPage, page) {// {{{
|
||||
retrievePage(uuid, perPage, page) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
const cursor = this.db
|
||||
.transaction(['nodes', this.storeName], 'readonly')
|
||||
.objectStore(this.storeName)
|
||||
.index('byUUID')
|
||||
.openCursor(uuid, 'prev')
|
||||
.openCursor(uuid)
|
||||
|
||||
let retrieved = 0
|
||||
let first = true
|
||||
|
|
@ -514,10 +497,10 @@ class NodeHistoryStore extends SimpleNodeStore {
|
|||
}
|
||||
}
|
||||
})
|
||||
}// }}}
|
||||
}
|
||||
}
|
||||
|
||||
export function uuidv7() {// {{{
|
||||
export function uuidv7() {
|
||||
// random bytes
|
||||
const value = new Uint8Array(16)
|
||||
crypto.getRandomValues(value)
|
||||
|
|
@ -541,6 +524,6 @@ export function uuidv7() {// {{{
|
|||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("")
|
||||
return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}`
|
||||
}// }}}
|
||||
}
|
||||
|
||||
// vim: foldmethod=marker
|
||||
|
|
|
|||
|
|
@ -1,299 +1,94 @@
|
|||
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
||||
import { Node } from './page_node.mjs'
|
||||
import { MarkedPosition } from './marked_position.mjs'
|
||||
|
||||
|
||||
export class N2PageHistory extends CustomHTMLElement {
|
||||
static PAGESIZE = 15
|
||||
static {// {{{
|
||||
static PAGESIZE = 10
|
||||
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<style>
|
||||
n2-pagehistory {
|
||||
margin-top: 32px;
|
||||
|
||||
.layout {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="back">
|
||||
<div class="layout">
|
||||
<img data-el="back-image" src="/images/${_VERSION}/icon_back.svg" class="colorize">
|
||||
<div data-el="back-text">Back to node</div>
|
||||
</div>
|
||||
|
||||
<div class="node-name">
|
||||
<img src="/images/${_VERSION}/icon_history.svg" class="colorize">
|
||||
<h1 data-el="node-name"></h1>
|
||||
</div>
|
||||
|
||||
<div class="group-label">Actions</div>
|
||||
<div class="group">
|
||||
<button data-el="download-history">Fetch all history from server</button>
|
||||
<div data-el="fetch-history-progress"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="group-label">History</div>
|
||||
<div class="group">
|
||||
<div data-el="stats">
|
||||
<div>History on server:</div>
|
||||
<div data-el="stats-on-server"></div>
|
||||
|
||||
<div>History on client:</div>
|
||||
<div data-el="stats-on-client"></div>
|
||||
</div>
|
||||
|
||||
<div data-el="nodes"></div>
|
||||
|
||||
<div data-el="pagination">
|
||||
|
||||
<div class="pagination">
|
||||
<div data-el="prev"><</div>
|
||||
<div data-el="page"></div>
|
||||
<div data-el="next">></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-el="markdown-content"></div>
|
||||
`
|
||||
}// }}}
|
||||
}
|
||||
|
||||
constructor() {// {{{
|
||||
constructor() {
|
||||
super()
|
||||
this.selectedNode = null
|
||||
|
||||
this.setAttribute('tabindex', '-1')
|
||||
this.addEventListener('keydown', event => this.keyHandler(event))
|
||||
|
||||
// Connect back icon and text to give the user a way back to the node.
|
||||
this.elBackImage.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'node' }))
|
||||
this.elBackText.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'node' }))
|
||||
this.elPrev.addEventListener('click', () => this.prevPage())
|
||||
this.elNext.addEventListener('click', () => this.nextPage())
|
||||
this.elDownloadHistory.addEventListener('click', async () => {
|
||||
await this.downloadHistory()
|
||||
await this.useNode(this.node)
|
||||
this.render(true)
|
||||
|
||||
})
|
||||
this.elPrev.addEventListener('click', ()=>this.prevPage())
|
||||
this.elNext.addEventListener('click', ()=>this.nextPage())
|
||||
|
||||
_mbus.subscribe('NODE_UI_OPEN', async (event) => {
|
||||
await this.useNode(event.detail.data)
|
||||
this.render()
|
||||
})
|
||||
}
|
||||
|
||||
_mbus.subscribe('HISTORY_NODE_SELECTED', (event) => {
|
||||
this.selectedNode = event.detail.data.historyNode
|
||||
})
|
||||
}// }}}
|
||||
async render(keepFetchHistoryProgress) {// {{{
|
||||
this.elNodeName.innerText = this.node.get('Name')
|
||||
this.elPage.innerText = `${this.page} / ${this.pages}`
|
||||
this.elStatsOnClient.innerText = `${this.nodesTotal}`
|
||||
this.elStatsOnServer.innerText = `${this.historyOnServerTotal}`
|
||||
|
||||
if (this.nodesTotal <= N2PageHistory.PAGESIZE)
|
||||
this.elPagination.style.display = 'none'
|
||||
else
|
||||
this.elPagination.style.display = ''
|
||||
|
||||
let nodes = await nodeStore.nodesHistory.retrievePage(this.node.UUID, N2PageHistory.PAGESIZE, this.page)
|
||||
let i = 0
|
||||
let divs = nodes.map(n => {
|
||||
i++
|
||||
const index = 1 + this.nodesTotal - (N2PageHistory.PAGESIZE * (this.page - 1) + i)
|
||||
const div = new N2PageHistoryNode(n, index)
|
||||
div.render()
|
||||
return div
|
||||
})
|
||||
this.elNodes.replaceChildren(...divs)
|
||||
|
||||
if (!keepFetchHistoryProgress)
|
||||
this.elFetchHistoryProgress.innerText = ''
|
||||
|
||||
// Select the first node.
|
||||
if (!this.selectedNode) {
|
||||
this.elNodes.firstElementChild?.select()
|
||||
}
|
||||
|
||||
// Any selected history node is rendered with markdown.
|
||||
/*
|
||||
this.marked = new MarkedPosition()
|
||||
this.elNodeMarkdown.innerHTML = this.marked.parse(this.elNodeContent.value)
|
||||
*/
|
||||
}// }}}
|
||||
|
||||
async useNode(node) {// {{{
|
||||
async useNode(node) {
|
||||
this.node = node
|
||||
this.page = 1
|
||||
|
||||
this.nodesTotal = await nodeStore.nodesHistory.count(this.node.UUID)
|
||||
this.historyOnServerTotal = await this.getServerTotal()
|
||||
this.pages = Math.ceil(this.nodesTotal / N2PageHistory.PAGESIZE)
|
||||
}// }}}
|
||||
keyHandler(event) {// {{{
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
this.prevPage()
|
||||
break
|
||||
case 'ArrowRight':
|
||||
this.nextPage()
|
||||
break
|
||||
case 'ArrowUp':
|
||||
const prevNode = this.selectedNode?.previousElementSibling
|
||||
if (prevNode)
|
||||
prevNode.select()
|
||||
break
|
||||
case 'ArrowDown':
|
||||
const nextNode = this.selectedNode?.nextElementSibling
|
||||
if (nextNode)
|
||||
nextNode.select()
|
||||
break
|
||||
}
|
||||
}// }}}
|
||||
}
|
||||
|
||||
prevPage() {// {{{
|
||||
|
||||
prevPage() {
|
||||
if (this.page == 1)
|
||||
return
|
||||
|
||||
// Selecting a node on another page is wrong.
|
||||
this.selectedNode = null
|
||||
this.page--
|
||||
this.render()
|
||||
}// }}}
|
||||
nextPage() {// {{{
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
if (this.page >= this.pages)
|
||||
return
|
||||
// Selecting a node on another page is wrong.
|
||||
this.selectedNode = null
|
||||
this.page++
|
||||
this.render()
|
||||
}// }}}
|
||||
}
|
||||
|
||||
async getServerTotal() {// {{{
|
||||
const res = await fetch(`/node/history/count/${this.node.UUID}`, {
|
||||
headers: {
|
||||
"Authorization": 'Bearer ' + localStorage.getItem('token'),
|
||||
}
|
||||
})
|
||||
const json = await res.json()
|
||||
async render() {
|
||||
this.elNodeName.innerText = this.node.get('Name')
|
||||
this.elPage.innerText = `${this.page} / ${this.pages}`
|
||||
|
||||
if (!json.OK) {
|
||||
alert(json.Error)
|
||||
return
|
||||
this.elNodes.innerHTML = ''
|
||||
let nodes = await nodeStore.nodesHistory.retrievePage(this.node.UUID, N2PageHistory.PAGESIZE, this.page)
|
||||
let i = 0
|
||||
for (const n of nodes) {
|
||||
i++
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = `
|
||||
<div>${N2PageHistory.PAGESIZE * (this.page - 1) + i}</div>
|
||||
<div>${n.get('Updated').replace('T', ' ')}</div>
|
||||
`
|
||||
div.classList.add('history-node')
|
||||
this.elNodes.append(div)
|
||||
}
|
||||
|
||||
return json.Count
|
||||
}// }}}
|
||||
async downloadHistory() {// {{{
|
||||
try {
|
||||
const nodes = []
|
||||
let offset = 0
|
||||
let hasMore = true
|
||||
|
||||
while (hasMore) {
|
||||
const history = await this.downloadHistoryPage(offset)
|
||||
hasMore = history.HasMore
|
||||
for (const nodeData of history.Nodes) {
|
||||
nodes.push(new Node(nodeData))
|
||||
}
|
||||
offset = nodes.length
|
||||
this.elFetchHistoryProgress.innerText = `${nodes.length} fetched.`
|
||||
}
|
||||
|
||||
let num = 0
|
||||
for (const node of nodes) {
|
||||
const ok = await nodeStore.nodesHistory.hasNode(node.UUID, node.get('Updated'))
|
||||
if (ok) num++
|
||||
await nodeStore.nodesHistory.add(node)
|
||||
}
|
||||
|
||||
this.elFetchHistoryProgress.innerText = `${nodes.length} fetched - all history fetched.`
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert(e)
|
||||
}
|
||||
}// }}}
|
||||
async downloadHistoryPage(offset) {// {{{
|
||||
const res = await fetch(`/node/history/retrieve/${this.node.UUID}/${offset}`, {
|
||||
headers: {
|
||||
"Authorization": 'Bearer ' + localStorage.getItem('token'),
|
||||
}
|
||||
})
|
||||
const json = await res.json()
|
||||
|
||||
if (!json.OK) {
|
||||
alert(json.Error)
|
||||
return
|
||||
}
|
||||
|
||||
return json
|
||||
}// }}}
|
||||
}
|
||||
}
|
||||
customElements.define('n2-pagehistory', N2PageHistory)
|
||||
|
||||
|
||||
class N2PageHistoryNode extends CustomHTMLElement {
|
||||
static {// {{{
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<div data-el="index"></div>
|
||||
<div data-el="updated"><span data-el="date"></span> <span data-el="time"></span></div>
|
||||
<div data-el="size"></div>
|
||||
<div data-el="name"></div>
|
||||
`
|
||||
}// }}}
|
||||
constructor(node, index) {// {{{
|
||||
super()
|
||||
|
||||
this.node = node
|
||||
this.index = index
|
||||
|
||||
this.style.display = 'contents'
|
||||
this.selected = false
|
||||
|
||||
this.addEventListener('click', () => this.select())
|
||||
|
||||
// Another history node has been selected.
|
||||
_mbus.subscribe('HISTORY_NODE_SELECTED', (event) => {
|
||||
if (this.node.get('Updated') == event.detail.data.historyNode.node.get('Updated'))
|
||||
return
|
||||
this.selected = false
|
||||
this.render()
|
||||
})
|
||||
}// }}}
|
||||
|
||||
select() {// {{{
|
||||
this.selected = true
|
||||
// Other nodes are told to unselect and rerender.
|
||||
_mbus.dispatch('HISTORY_NODE_SELECTED', { historyNode: this })
|
||||
this.render()
|
||||
}// }}}
|
||||
render() {// {{{
|
||||
const date = this.node.get('Updated').slice(0, 10)
|
||||
const time = this.node.get('Updated').slice(11, 19)
|
||||
|
||||
if (this.selected)
|
||||
this.classList.add('selected')
|
||||
else
|
||||
this.classList.remove('selected')
|
||||
|
||||
this.elIndex.innerText = this.index
|
||||
this.elDate.innerText = date
|
||||
this.elTime.innerText = time
|
||||
this.elSize.innerText = this.node.get('Content').length
|
||||
this.elName.innerText = this.node.get('Name')
|
||||
}// }}}
|
||||
formatSize(s) {// {{{
|
||||
let div = 1
|
||||
let unit = 'B'
|
||||
if (s >= 1048576) {
|
||||
div = 1048576
|
||||
unit = 'MB'
|
||||
} else if (s >= 1024) {
|
||||
div = 1024
|
||||
unit = 'kB'
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
maximumFractionDigits: 0
|
||||
}).format(Math.round(s / div)) + ' ' + unit
|
||||
}// }}}
|
||||
}
|
||||
customElements.define('n2-pagehistorynode', N2PageHistoryNode)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue