diff --git a/main.go b/main.go
index e608601..96f08aa 100644
--- a/main.go
+++ b/main.go
@@ -25,7 +25,7 @@ import (
const VERSION = "v1"
const CONTEXT_USER = 1
-const SYNC_PAGINATION = 500
+const SYNC_PAGINATION = 200
var (
FlagGenerate bool
diff --git a/static/css/notes2.css b/static/css/notes2.css
index adfa51d..e6d0ef6 100644
--- a/static/css/notes2.css
+++ b/static/css/notes2.css
@@ -1,5 +1,9 @@
@import "theme.css";
+:root {
+ --content-width: 900px;
+}
+
html {
background-color: #fff;
}
@@ -10,19 +14,18 @@ html {
display: grid;
grid-template-areas:
"tree crumbs"
- "tree sync"
"tree name"
+ "tree sync"
"tree content"
/*
"tree checklist"
- "tree schedule"
"tree files"
*/
"tree blank"
;
grid-template-columns: min-content 1fr;
grid-template-rows:
- 48px 56px 48px min-content 1fr;
+ min-content min-content 48px 1fr;
@media only screen and (max-width: 600px) {
@@ -33,7 +36,6 @@ html {
"content"
/*
"checklist"
- "schedule"
"files"
*/
"blank"
@@ -43,7 +45,14 @@ html {
#tree {
display: none;
}
+
+ n2-syncprogress {
+ .el-count {
+ top: 4px;
+ }
+ }
}
+
}
#tree {
@@ -141,9 +150,10 @@ html {
display: grid;
align-items: start;
justify-items: center;
+ height: min-content;
margin: 16px 16px;
- .crumbs {
+ n2-crumbs {
background: #e4e4e4;
display: flex;
flex-wrap: wrap;
@@ -162,7 +172,7 @@ html {
}
}
- .crumb {
+ n2-crumb {
margin-right: 8px;
cursor: pointer;
user-select: none;
@@ -174,17 +184,17 @@ html {
}
}
- .crumb:after {
- content: "•";
- margin-left: 8px;
+ n2-crumb:after {
+ content: ">";
+ font-weight: bold;
color: var(--color1)
}
- .crumb:last-child {
+ n2-crumb:last-child {
margin-right: 0;
}
- .crumb:last-child:after {
+ n2-crumb:last-child:after {
content: '';
margin-left: 0px;
}
@@ -193,143 +203,112 @@ html {
}
-#sync-progress {
+n2-syncprogress {
--radius: 8px;
+ display: grid;
grid-area: sync;
display: grid;
justify-items: center;
align-items: center;
- width: 100%;
- height: 56px;
+ position: relative;
- .container {
- position: relative;
+ opacity: 0;
+ transition: height 0s 500ms, opacity 500ms linear, visibility 0s 500ms;
- progress {
- width: 900px;
- padding: 0 7px;
- max-width: 900px;
- height: 24px;
- border-radius: 8px;
- }
-
- .count {
- position: absolute;
- top: 5px;
- width: 100%;
- white-space: nowrap;
- color: #888;
- text-align: center;
- font-size: 12pt;
- font-weight: bold;
- }
-
- progress[value]::-webkit-progress-bar {
- background-color: #eee;
- box-shadow: 0 2px var(--radius) rgba(0, 0, 0, 0.25) inset;
- border-radius: var(--radius);
- }
-
- progress[value]::-moz-progress-bar {
- background-color: #eee;
- box-shadow: 0 2px var(--radius) rgba(0, 0, 0, 0.25) inset;
- border-radius: var(--radius);
- }
-
- progress[value]::-webkit-progress-value {
- background: rgb(186, 95, 89);
- background: linear-gradient(180deg, rgba(186, 95, 89, 1) 0%, rgba(254, 95, 85, 1) 50%, rgba(186, 95, 89, 1) 100%);
- border-radius: var(--radius);
- }
-
- progress[value]::-moz-progress-value {
- background: rgb(186, 95, 89);
- background: linear-gradient(180deg, rgba(186, 95, 89, 1) 0%, rgba(254, 95, 85, 1) 50%, rgba(186, 95, 89, 1) 100%);
- border-radius: var(--radius);
- }
+ &.show {
+ opacity: 1;
+ transition: visibility, height 0s, opacity 500ms linear;
}
-
-
- &.hidden {
- visibility: hidden;
- opacity: 0;
- transition: visibility 0s 500ms, opacity 500ms linear;
+ progress {
+ width: calc(100% - 32px);
+ max-width: var(--content-width);
+ height: 24px;
+ border-radius: 8px;
}
-}
+ .count {
+ position: absolute;
+ top: 16px;
+ width: 100%;
+ white-space: nowrap;
+ color: #888;
+ text-align: center;
+ font-size: 12pt;
+ font-weight: bold;
+ }
-#name {
- color: #333;
- font-weight: bold;
- text-align: center;
- font-size: 1.15em;
- margin-top: 0px;
- margin-bottom: 16px;
-}
+ progress[value]::-webkit-progress-bar {
+ background-color: #eee;
+ box-shadow: 0 2px var(--radius) rgba(0, 0, 0, 0.25) inset;
+ border-radius: var(--radius);
+ }
-/* ============================================================= *
- * Textarea replicates the height of an element expanding height *
- * ============================================================= */
-.grow-wrap {
- /* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */
- display: grid;
- grid-area: content;
- font-size: 1.0em;
-}
+ progress[value]::-moz-progress-bar {
+ background-color: #eee;
+ box-shadow: 0 2px var(--radius) rgba(0, 0, 0, 0.25) inset;
+ border-radius: var(--radius);
+ }
-.grow-wrap::after {
- /* Note the weird space! Needed to preventy jumpy behavior */
- content: attr(data-replicated-value) " ";
+ progress[value]::-webkit-progress-value {
+ background: rgb(186, 95, 89);
+ background: linear-gradient(180deg, rgba(186, 95, 89, 1) 0%, rgba(254, 95, 85, 1) 50%, rgba(186, 95, 89, 1) 100%);
+ border-radius: var(--radius);
+ }
- /* This is how textarea text behaves */
- width: calc(100% - 32px);
- max-width: 900px;
- white-space: pre-wrap;
- word-wrap: break-word;
- background: rgba(0, 255, 255, 0.5);
- justify-self: center;
+ progress[value]::-moz-progress-value {
+ background: rgb(186, 95, 89);
+ background: linear-gradient(180deg, rgba(186, 95, 89, 1) 0%, rgba(254, 95, 85, 1) 50%, rgba(186, 95, 89, 1) 100%);
+ border-radius: var(--radius);
+ }
- /* Hidden from view, clicks, and screen readers */
- visibility: hidden;
-}
-
-.grow-wrap>textarea {
- /* You could leave this, but after a user resizes, then it ruins the auto sizing */
- resize: none;
-
- /* Firefox shows scrollbar on growth, you can hide like this. */
- overflow: hidden;
-}
-
-.grow-wrap>textarea,
-.grow-wrap::after {
- /* Identical styling required!! */
- padding: 0.5rem;
- font: inherit;
-
- /* Place on top of each other */
- grid-area: 1 / 1 / 2 / 2;
}
/* ============================================================= */
-#node-content {
- justify-self: center;
- word-wrap: break-word;
- font-family: monospace;
- color: #333;
- width: calc(100% - 32px);
- max-width: 900px;
- resize: none;
- border: none;
- outline: none;
+n2-nodeui {
+ margin-bottom: 32px;
- &:invalid {
- background: #f5f5f5;
- padding-top: 16px;
+ .el-name {
+ color: #333;
+ font-weight: bold;
+ text-align: center;
+ font-size: 1.15em;
+ margin-top: 8px;
+ margin-bottom: 0px;
+ }
+
+ .el-node-content {
+ justify-self: center;
+ word-wrap: break-word;
+ font-family: monospace;
+ color: #333;
+
+ /*
+ width: 100%;
+ max-width: var(--content-width);
+ field-sizing: content;
+ */
+
+ width: calc(100% - 32px);
+ max-width: var(--content-width);
+ field-sizing: content;
+
+ resize: none;
+ border: none;
+ outline: none;
+
+ padding: 16px 0;
+ border-top: 1px solid #e0e0e0;
+ border-bottom: 1px solid #e0e0e0;
+ margin-bottom: 32px;
+
+ &:invalid {
+ background: #f5f5f5;
+ padding-top: 16px;
+ }
}
}
diff --git a/static/js/app.mjs b/static/js/app.mjs
index 89ecab3..c68475e 100644
--- a/static/js/app.mjs
+++ b/static/js/app.mjs
@@ -1,8 +1,7 @@
import { ROOT_NODE } from 'node_store'
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
import { N2Tree } from 'tree'
-import { NodeUINative, Node } from 'node'
-import { SyncProgress } from 'sync'
+import { Node } from 'node'
export class App {
constructor() {// {{{
@@ -10,7 +9,7 @@ export class App {
this.treeNative = new N2Tree()
this.crumbs = new N2Crumbs()
this.crumbsElement = document.getElementById('crumbs')
- this.nodeUI = new NodeUINative(document.getElementById('note'))
+ this.nodeUI = document.getElementById('note')
_mbus.subscribe('TREE_TRUNK_FETCHED', async () => {
document.getElementById('tree').append(this.treeNative.render())
@@ -36,11 +35,12 @@ export class App {
document.getElementById('node-content')?.focus()
})
- const syncProgress = document.getElementById('sync-progress')
- new SyncProgress(syncProgress)
-
window._sync = new Sync()
- window._sync.run()
+
+ // I think it is uncomfortable having the sync running as soon as the page load.
+ // I haven't gotten the time to look at the page before stuff jumps around.
+ // There a slight delay to initiate sync seems reasonable.
+ setTimeout(() => window._sync.run(), 1000)
}// }}}
keyHandler(event) {//{{{
@@ -55,9 +55,9 @@ export class App {
switch (event.key.toUpperCase()) {
case 'T':
if (document.activeElement.id === 'tree-nodes')
- document.getElementById('node-content').focus()
+ this.nodeUI.takeFocus()
else
- document.getElementById('tree-nodes').focus()
+ this.nodeUI.takeFocus()
break
case 'F':
diff --git a/static/js/node.mjs b/static/js/node.mjs
index 3820941..949724c 100644
--- a/static/js/node.mjs
+++ b/static/js/node.mjs
@@ -1,345 +1,20 @@
-import { h, Component, createRef } from 'preact'
-import htm from 'htm'
-import { signal } from 'preact/signals'
import { ROOT_NODE } from 'node_store'
-import { SyncProgress } from 'sync'
-const html = htm.bind(h)
+import { CustomHTMLElement } from './lib/custom_html_element.mjs'
-export class NodeUI extends Component {
- constructor(props) {//{{{
- super(props)
- this.menu = signal(false)
- this.node = signal(null)
- this.nodeContent = createRef()
- this.nodeProperties = createRef()
- this.nodeModified = signal(false)
- this.keys = signal([])
- this.page = signal('node')
- this.crumbs = []
- this.syncProgress = createRef()
- window.addEventListener('popstate', evt => {
- if (evt.state?.hasOwnProperty('nodeUUID'))
- _notes2.current.goToNode(evt.state.nodeUUID, true)
- else
- _notes2.current.goToNode('00000000-0000-0000-0000-000000000000', true)
- })
-
- window.addEventListener('keydown', evt => this.keyHandler(evt))
- }//}}}
- render() {//{{{
- if (this.node.value === null)
- return
-
- const node = this.node.value
- document.title = node.get('Name')
-
- const nodeModified = this.nodeModified.value ? 'node-modified' : ''
-
-
- const crumbDivs = [
- html`
_notes2.current.goToNode(ROOT_NODE)}>Start
`
- ]
- for (let i = this.crumbs.length - 1; i >= 0; i--) {
- const crumbNode = this.crumbs[i]
- crumbDivs.push(html` _notes2.current.goToNode(crumbNode.UUID)}>${crumbNode.get('Name')}
`)
- }
- if (node.UUID !== ROOT_NODE)
- crumbDivs.push(
- html` _notes2.current.goToNode(node.UUID)}>${node.get('Name')}
`
- )
-
- return html`
- this.saveNode()}>
-
- ${crumbDivs}
-
-
-
- ${node.get('Name')}
- <${NodeContent} key=${node.UUID} node=${node} ref=${this.nodeContent} />
-
+export class N2NodeUI extends CustomHTMLElement {
+ static {// {{{
+ this.tmpl = document.createElement('template')
+ this.tmpl.innerHTML = `
+
+
`
+ }// }}}
-
-
-
- return
-
- let crumbs = [
- html` this.goToNode(0)}>Start
`
- ]
-
- crumbs = crumbs.concat(node.Crumbs.slice(0).map(node =>
- html` this.goToNode(node.ID)}>${node.Name}
`
- ).reverse())
-
-
- // Page to display
- let page = ''
- switch (this.page.value) {
- case 'node':
- if (node.ID === 0) {
- page = html`
- { this.page.value = 'schedule-events' }}>Schedule events
- ${children.length > 0 ? html`${children}
Notes version ${window._VERSION}
` : html``}
- `
- } else {
- let padlock = ''
- if (node.CryptoKeyID > 0)
- padlock = html` `
-
- page = html`
- ${children.length > 0 ? html`${children}
` : html``}
-
- ${node.Name} ${padlock}
-
- <${NodeContent} key=${node.ID} node=${node} ref=${this.nodeContent} />
- <${NodeEvents} events=${node.ScheduleEvents.value} />
- <${Checklist} ui=${this} groups=${node.ChecklistGroups} />
- <${NodeFiles} node=${this.node.value} />
- `
- }
- break
-
- case 'upload':
- page = html`<${UploadUI} nodeui=${this} />`
- break
-
- case 'node-properties':
- page = html`<${NodeProperties} ref=${this.nodeProperties} nodeui=${this} />`
- break
-
- case 'keys':
- page = html`<${Keys} nodeui=${this} />`
- break
-
- case 'profile-settings':
- page = html`<${ProfileSettings} nodeui=${this} />`
- break
-
- case 'search':
- page = html`<${Search} nodeui=${this} />`
- break
-
- case 'schedule-events':
- page = html`<${ScheduleEventList} nodeui=${this} />`
- break
- }
-
- const menu = () => (this.menu.value ? html`<${Menu} nodeui=${this} />` : null)
- const checklist = () =>
- html`
- { evt.stopPropagation(); this.toggleChecklist() }}>
-
-
`
-
- return html`
- <${menu} />
-
-
-
-
-
- ${page}
- `
- }//}}}
- async componentDidMount() {//{{{
- console.log('hum')
- _notes2.current.goToNode(this.props.startNode.UUID, true)
- _notes2.current.tree.expandToTrunk(this.props.startNode)
-
- const syncProgressEl = document.getElementById('#sync-progress')
- console.log(syncProgressEl)
- }//}}}
- setNode(node) {//{{{
- this.nodeModified.value = false
- this.node.value = node
- }//}}}
- setCrumbs(nodes) {//{{{
- this.crumbs = nodes
- }//}}}
- async saveNode() {//{{{
- if (!this.nodeModified.value)
- return
-
- /* The node history is a local store for node history.
- * This could be provisioned from the server or cleared if
- * deemed unnecessary.
- *
- * The send queue is what will be sent back to the server
- * to have a recorded history of the notes.
- *
- * A setting to be implemented in the future could be to
- * not save the history locally at all. */
- const node = this.node.value
-
- // The node is still in its old state and will present
- // the unmodified content to the node store.
- const history = nodeStore.nodesHistory.add(node)
-
- // Prepares the node object for saving.
- // Sets Updated value to current date and time.
- await node.save()
-
- // Updated node is added to the send queue to be stored on server.
- const sendQueue = nodeStore.sendQueue.add(this.node.value)
-
- // Updated node is saved to the primary node store.
- const nodeStoreAdding = nodeStore.add([node])
-
- await Promise.all([history, sendQueue, nodeStoreAdding])
- this.nodeModified.value = false
- }//}}}
-
- keyHandler(evt) {//{{{
- let handled = true
-
- // All keybindings is Alt+Shift, since the popular browsers at the time (2023) allows to override thees.
- // Ctrl+S is the exception to using Alt+Shift, since it is overridable and in such widespread use for saving.
- // Thus, the exception is acceptable to consequent use of alt+shift.
- if (!(evt.shiftKey && evt.altKey) && !(evt.key.toUpperCase() === 'S' && evt.ctrlKey))
- return
-
- switch (evt.key.toUpperCase()) {
- case 'T':
- if (document.activeElement.id === 'tree')
- document.getElementById('node-content').focus()
- else
- document.getElementById('tree').focus()
- break
-
- case 'F':
- _mbus.dispatch('op-search')
- break
- /*
- case 'C':
- this.showPage('node')
- break
-
- case 'E':
- this.showPage('keys')
- break
-
- case 'M':
- this.toggleMarkdown()
- break
-
- case 'N':
- this.createNode()
- break
-
- case 'P':
- this.showPage('node-properties')
- break
-
- */
- case 'S':
- if (this.page.value === 'node')
- this.saveNode()
- else if (this.page.value === 'node-properties')
- this.nodeProperties.current.save()
- break
- /*
-
- case 'U':
- this.showPage('upload')
- break
-
- case 'F':
- this.showPage('search')
- break
- */
-
- default:
- handled = false
- }
-
- if (handled) {
- evt.preventDefault()
- evt.stopPropagation()
- }
- }//}}}
-}
-
-class NodeContent extends Component {
- constructor(props) {//{{{
- super(props)
- this.contentDiv = createRef()
- this.state = {
- modified: false,
- }
- }//}}}
- render({ node }) {//{{{
- let content = ''
- try {
- content = node.content()
- } catch (err) {
- return html`
-
${err.message}
- `
- }
-
- /*
- let element
- if (node.RenderMarkdown.value)
- element = html`<${MarkdownContent} key='markdown-content' content=${content} />`
- else
- */
- const element = html`
-
-
-
- `
-
- return element
- }//}}}
- componentDidMount() {//{{{
- this.resize()
- window.addEventListener('resize', () => this.resize())
-
- const contentResizeObserver = new ResizeObserver(entries => {
- for (const idx in entries) {
- const w = entries[idx].contentRect.width
- document.querySelector('#crumbs .crumbs').style.width = `${w}px`
- }
- });
-
- const nodeContent = document.getElementById('node-content')
- contentResizeObserver.observe(nodeContent);
-
- }//}}}
- componentDidUpdate() {//{{{
- this.resize()
- }//}}}
- contentChanged(evt) {//{{{
- _notes2.current.nodeUI.current.nodeModified.value = true
- this.props.node.setContent(evt.target.value)
- this.resize()
- }//}}}
- resize() {//{{{
- const textarea = document.getElementById('node-content')
- if (textarea)
- textarea.parentNode.dataset.replicatedValue = textarea.value
- }//}}}
-}
-
-export class NodeUINative {
- constructor(parentElement) {// {{{
+ constructor() {// {{{
+ super()
this.node = null
- this.parent = parentElement
- this.parent.replaceChildren(this.createElements())
+
+ this.style.display = 'contents'
_mbus.subscribe('NODE_UI_OPEN', event => {
this.node = event.detail.data
@@ -353,42 +28,25 @@ export class NodeUINative {
_mbus.subscribe('NODE_UNMODIFIED', () => {
document.querySelector('#crumbs .crumbs')?.classList.remove('node-modified')
})
- }// }}}
- createElements() {// {{{
- const tmpl = document.createElement('template')
- tmpl.innerHTML = `
-
-
-
-
- `
- tmpl.content.querySelector('#node-content').addEventListener('input', event => this.contentChanged(event))
-
- return tmpl.content
+ this.elNodeContent.addEventListener('input', event => this.contentChanged(event))
}// }}}
render() {// {{{
- this.parent.querySelector('.grow-wrap').style.display = (this.node === null ? 'none' : 'grid')
- this.parent.querySelector('#name').innerText = this.node?.get('Name') ?? ''
- this.parent.querySelector('#node-content').value = this.node?.get('Content') ?? ''
-
- this.resize()
- return this.parent
+ this.elName.innerText = this.node?.get('Name') ?? ''
+ this.elNodeContent.value = this.node?.get('Content') ?? ''
+ }// }}}
+ takeFocus() {// {{{
+ this.elNodeContent.focus()
}// }}}
- resize() {//{{{
- const textarea = this.parent.querySelector('#node-content')
- textarea.parentNode.dataset.replicatedValue = textarea.value
- }//}}}
contentChanged(event) {//{{{
this.node.setContent(event.target.value)
- this.resize()
}//}}}
isModified() {// {{{
return this.node?.isModified()
}// }}}
-
}
+customElements.define('n2-nodeui', N2NodeUI)
export class Node {
static sort(a, b) {//{{{
diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs
index ef43233..98642d1 100644
--- a/static/js/node_store.mjs
+++ b/static/js/node_store.mjs
@@ -13,7 +13,7 @@ export class NodeStore {
this.sendQueue = null
this.nodesHistory = null
}//}}}
- async initializeDB() {//{{{
+ initializeDB() {//{{{
return new Promise((resolve, reject) => {
const req = indexedDB.open('notes', 7)
@@ -78,7 +78,7 @@ export class NodeStore {
}
})
}//}}}
- async initializeRootNode() {//{{{
+ initializeRootNode() {//{{{
return new Promise((resolve, reject) => {
// The root node is a magical node which displays as the first node if none is specified.
// If not already existing, it will be created.
@@ -120,7 +120,7 @@ export class NodeStore {
return n
}//}}}
- async getAppState(key) {//{{{
+ getAppState(key) {//{{{
return new Promise((resolve, reject) => {
const trx = this.db.transaction('app_state', 'readonly')
const appState = trx.objectStore('app_state')
@@ -135,7 +135,7 @@ export class NodeStore {
getRequest.onerror = (event) => reject(event.target.error)
})
}//}}}
- async setAppState(key, value) {//{{{
+ setAppState(key, value) {//{{{
return new Promise((resolve, reject) => {
try {
const t = this.db.transaction('app_state', 'readwrite')
@@ -159,32 +159,7 @@ export class NodeStore {
})
}//}}}
- /* TODO - Remove?
- async storeNode(node) {//{{{
- return new Promise((resolve, reject) => {
- const t = this.db.transaction('nodes', 'readwrite')
- const nodeStore = t.objectStore('nodes')
- t.onerror = (event) => {
- console.log('transaction error', event.target.error)
- reject(event.target.error)
- }
- t.oncomplete = () => {
- resolve()
- }
-
- const nodeReq = nodeStore.put(node.data)
- nodeReq.onsuccess = () => {
- console.debug(`Storing ${node.UUID} (${node.get('Name')})`)
- }
- queueReq.onerror = (event) => {
- console.log(`Error storing ${node.UUID}`, event.target.error)
- reject(event.target.error)
- }
- })
- }//}}}
- */
-
- async upsertNodeRecords(records) {//{{{
+ upsertNodeRecords(records) {//{{{
return new Promise((resolve, reject) => {
const t = this.db.transaction('nodes', 'readwrite')
const nodeStore = t.objectStore('nodes')
@@ -222,7 +197,7 @@ export class NodeStore {
}
})
}//}}}
- async getTreeNodes(parent, newLevel) {//{{{
+ getTreeNodes(parent, newLevel) {//{{{
return new Promise((resolve, reject) => {
// Parent of toplevel nodes is ROOT_NODE in indexedDB.
// Only the root node has '' as parent.
@@ -274,13 +249,13 @@ export class NodeStore {
})
}//}}}
- async add(records) {//{{{
+ add(records) {//{{{
return new Promise((resolve, reject) => {
try {
const t = this.db.transaction('nodes', 'readwrite')
const nodeStore = t.objectStore('nodes')
t.onerror = (event) => {
- console.log('transaction error', event.target.error)
+ console.error('transaction error', event.target.error)
reject(event.target.error)
}
@@ -291,12 +266,9 @@ export class NodeStore {
const addReq = nodeStore.put(record.data)
const promise = new Promise((resolve, reject) => {
- addReq.onsuccess = () => {
- console.debug('OK!', record.ID, record.Name)
- resolve()
- }
+ addReq.onsuccess = () => resolve()
addReq.onerror = (event) => {
- console.log('Error!', event.target.error, record.ID)
+ console.error('Error!', event.target.error, record.ID)
reject(event.target.error)
}
})
@@ -309,7 +281,7 @@ export class NodeStore {
}
})
}//}}}
- async get(uuid) {//{{{
+ get(uuid) {//{{{
return new Promise((resolve, reject) => {
const trx = this.db.transaction('nodes', 'readonly')
const nodeStore = trx.objectStore('nodes')
@@ -325,7 +297,7 @@ export class NodeStore {
}
})
}//}}}
- async getNodeAncestry(node, accumulated) {//{{{
+ getNodeAncestry(node, accumulated) {//{{{
return new Promise((resolve, reject) => {
if (accumulated === undefined)
accumulated = []
@@ -357,7 +329,7 @@ export class NodeStore {
}//}}}
- async nodeCount() {//{{{
+ nodeCount() {//{{{
return new Promise((resolve, reject) => {
const t = this.db.transaction('nodes', 'readwrite')
const nodeStore = t.objectStore('nodes')
@@ -374,7 +346,7 @@ class SimpleNodeStore {
this.db = db
this.storeName = storeName
}//}}}
- async add(node) {//{{{
+ add(node) {//{{{
return new Promise((resolve, reject) => {
const t = this.db.transaction(['nodes', this.storeName], 'readwrite')
const store = t.objectStore(this.storeName)
@@ -394,7 +366,7 @@ class SimpleNodeStore {
}
})
}//}}}
- async retrieve(limit) {//{{{
+ retrieve(limit) {//{{{
return new Promise((resolve, reject) => {
const cursorReq = this.db
.transaction(['nodes', this.storeName], 'readonly')
@@ -422,7 +394,7 @@ class SimpleNodeStore {
}
})
}//}}}
- async delete(keys) {//{{{
+ delete(keys) {//{{{
const store = this.db
.transaction(['nodes', this.storeName], 'readwrite')
.objectStore(this.storeName)
@@ -439,7 +411,7 @@ class SimpleNodeStore {
}
return Promise.all(promises)
}//}}}
- async count() {//{{{
+ count() {//{{{
const store = this.db
.transaction(['nodes', this.storeName], 'readonly')
.objectStore(this.storeName)
diff --git a/static/js/sync.mjs b/static/js/sync.mjs
index edc93ea..fd606f3 100644
--- a/static/js/sync.mjs
+++ b/static/js/sync.mjs
@@ -1,5 +1,6 @@
import { API } from 'api'
import { Node } from 'node'
+import { CustomHTMLElement } from './lib/custom_html_element.mjs'
export class Sync {
constructor() {//{{{
@@ -154,70 +155,43 @@ export class Sync {
}//}}}
}
-export class SyncProgress {
- constructor(parentEl) {//{{{
+export class N2SyncProgress extends CustomHTMLElement {
+ static {
+ this.tmpl = document.createElement('template')
+ this.tmpl.innerHTML = `
+
+
0 / 0
+ `
+ }
+ constructor() {//{{{
+ super()
+
this.reset()
_mbus.subscribe('SYNC_COUNT', event => this.progressHandler(event))
_mbus.subscribe('SYNC_HANDLED', event => this.progressHandler(event))
_mbus.subscribe('SYNC_DONE', event => this.progressHandler(event))
-
- this.el = this.createElements()
- parentEl.replaceChildren(this.el)
}//}}}
- createElements() {
- const div = document.createElement('div')
- div.classList.add('container')
- div.innerHTML = `
-
-
0 / 0
- `
-
- this.elProgress = div.querySelector('progress')
- this.elCount = div.querySelector('.count')
- return div
- }
reset() {//{{{
- this.forceUpdateRequest = null
this.state = {
nodesToSync: 0,
nodesSynced: 0,
- syncedDone: false,
- }
- document.getElementById('sync-progress')?.classList.remove('hidden')
- }//}}}
- getSnapshotBeforeUpdate(_, prevState) {//{{{
- if (!prevState.syncedDone && this.state.syncedDone)
- setTimeout(() => document.getElementById('sync-progress')?.classList.add('hidden'), 750)
- }//}}}
- componentDidUpdate() {//{{{
- if (!this.state.syncedDone) {
- if (this.forceUpdateRequest !== null)
- clearTimeout(this.forceUpdateRequest)
- this.forceUpdateRequest = setTimeout(
- () => {
- this.forceUpdateRequest = null
- this.forceUpdate()
- },
- 50
- )
}
}//}}}
progressHandler(event) {//{{{
const eventData = event.detail.data
switch (event.type) {
case 'SYNC_COUNT':
- console.log(eventData.count)
this.state.nodesToSync = eventData.count
+ this.setSyncState(true)
break
case 'SYNC_HANDLED':
- console.log('sync handled')
this.state.nodesSynced = eventData.handled
break
case 'SYNC_DONE':
// Hides the progress bar.
- this.state.syncedDone = true
+ this.setSyncState(false)
// Don't update anything if nothing was synced.
if (this.state.nodesSynced === 0)
@@ -233,17 +207,17 @@ export class SyncProgress {
this.render()
}//}}}
render() {//{{{
- console.log('render', this.state.nodesToSync)
this.elProgress.max = this.state.nodesToSync
this.elProgress.value = this.state.nodesSynced
this.elCount.innerText = `${this.state.nodesSynced} / ${this.state.nodesToSync}`
- /*
- if (nodesToSync === 0)
- return html`
`
- */
-
-
}//}}}
+ setSyncState(state) {// {{{
+ if (state)
+ this.classList.add('show')
+ else
+ setTimeout(() => this.classList.remove('show'), 1500)
+ }// }}}
}
+customElements.define('n2-syncprogress', N2SyncProgress)
// vim: foldmethod=marker
diff --git a/static/less/notes2.less b/static/less/notes2.less
deleted file mode 100644
index 2c57f53..0000000
--- a/static/less/notes2.less
+++ /dev/null
@@ -1,365 +0,0 @@
-@import "theme.less";
-
-html {
- background-color: #fff;
-}
-
-#notes2 {
- min-height: 100vh;
-
- display: grid;
- grid-template-areas:
- "tree crumbs"
- "tree sync"
- "tree name"
- "tree content"
- //"tree checklist"
- //"tree schedule"
- //"tree files"
- "tree blank"
- ;
- grid-template-columns: min-content 1fr;
- grid-template-rows:
- 48px
- 56px
- 48px
- min-content
- 1fr;
-
-
- @media only screen and (max-width: 600px) {
- grid-template-areas:
- "crumbs"
- "sync"
- "name"
- "content"
- //"checklist"
- //"schedule"
- //"files"
- "blank"
- ;
- grid-template-columns: 1fr;
-
- #tree {
- display: none;
- }
- }
-}
-
-#tree {
- grid-area: tree;
- display: grid;
- padding: 16px 0px 16px 16px;
- color: #ddd;
- z-index: 100; // Over crumbs shadow
- border-left: 2px solid #333;
-
- &:focus {
- border-left: 2px solid #FE5F55;
- }
-
- #logo {
- display: grid;
- position: relative;
- justify-items: center;
- margin-top: 8px;
- margin-bottom: 8px;
- margin-left: 24px;
- margin-right: 24px;
- cursor: pointer;
-
- img {
- width: 128px;
- left: -20px;
-
- }
- }
-
- .icons {
- display: flex;
- justify-content: center;
- margin-bottom: 32px;
- gap: 8px;
- }
-
- .node {
- display: grid;
- grid-template-columns: 24px min-content;
- grid-template-rows:
- min-content
- 1fr;
- margin-top: 12px;
-
-
- .expand-toggle {
- user-select: none;
- img {
- width: 16px;
- height: 16px;
- }
- }
-
- .name {
- white-space: nowrap;
- cursor: pointer;
- user-select: none;
-
- &:hover {
- color: @color1;
- }
- &.selected {
- color: @color1;
- font-weight: bold;
- }
-
- }
-
- .children {
- padding-left: 24px;
- margin-left: 8px;
- border-left: 1px solid #444;
- grid-column: 1 / -1;
-
- &.collapsed {
- display: none;
- }
- }
- }
-}
-
-#tree-nodes {
- padding: 16px 32px;
- background-color: #333;
- border-radius: 8px;
- box-shadow: 5px 5px 10px -5px rgba(0,0,0,0.75);
-}
-
-#crumbs {
- grid-area: crumbs;
- display: grid;
- align-items: start;
- justify-items: center;
- margin: 16px 16px;
-
- .crumbs {
- background: #e4e4e4;
- display: flex;
- flex-wrap: wrap;
- padding: 8px 16px;
- background: #e4e4e4;
- color: #333;
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
-
- &.node-modified {
- background-color: @color1;
- color: @color2;
- .crumb:after {
- color: @color2;
- }
- }
-
- .crumb {
- margin-right: 8px;
- cursor: pointer;
- user-select: none;
- -webkit-tap-highlight-color: transparent;
-
- a {
- text-decoration: none;
- color: inherit;
- }
- }
-
- .crumb:after {
- content: "•";
- margin-left: 8px;
- color: @color1
- }
-
- .crumb:last-child {
- margin-right: 0;
- }
- .crumb:last-child:after {
- content: '';
- margin-left: 0px;
- }
-
- }
-
-}
-
-#sync-progress {
- grid-area: sync;
- display: grid;
- justify-items: center;
-
- width: 100%;
- height: 56px;
- position: relative;
-
- progress {
- width: 100%;
- padding: 0 7px;
- max-width: 900px;
- height: 16px;
- border-radius: 4px;
- }
-
- progress[value]::-webkit-progress-bar {
- background-color: #eee;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25) inset;
- border-radius: 4px;
- }
-
- progress[value]::-moz-progress-bar {
- background-color: #eee;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25) inset;
- border-radius: 4px;
- }
-
- progress[value]::-webkit-progress-value {
- background: rgb(186,95,89);
- background: linear-gradient(180deg, rgba(186,95,89,1) 0%, rgba(254,95,85,1) 50%, rgba(186,95,89,1) 100%);
- border-radius: 4px;
- }
-
- // TODO: style the progress value for Firefox
- progress[value]::-moz-progress-value {
- background: rgb(186,95,89);
- background: linear-gradient(180deg, rgba(186,95,89,1) 0%, rgba(254,95,85,1) 50%, rgba(186,95,89,1) 100%);
- border-radius: 4px;
- }
-
- .count {
- width: min-content;
- white-space: nowrap;
- margin-top: 0px;
- color: #888;
- position: absolute;
- top: 22px;
- }
-
- &.hidden {
- visibility: hidden;
- opacity: 0;
- transition: visibility 0s 500ms, opacity 500ms linear;
- }
-
-}
-
-#name {
- color: #333;
- font-weight: bold;
- text-align: center;
- font-size: 1.15em;
- margin-top: 0px;
- margin-bottom: 16px;
-}
-
-/* ============================================================= *
- * Textarea replicates the height of an element expanding height *
- * ============================================================= */
-.grow-wrap {
- /* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */
- display: grid;
- grid-area: content;
- font-size: 1.0em;
-}
-.grow-wrap::after {
- /* Note the weird space! Needed to preventy jumpy behavior */
- content: attr(data-replicated-value) " ";
-
- /* This is how textarea text behaves */
- width: calc(100% - 32px);
- max-width: 900px;
- white-space: pre-wrap;
- word-wrap: break-word;
- background: rgba(0, 255, 255, 0.5);
- justify-self: center;
-
- /* Hidden from view, clicks, and screen readers */
- visibility: hidden;
-}
-.grow-wrap > textarea {
- /* You could leave this, but after a user resizes, then it ruins the auto sizing */
- resize: none;
-
- /* Firefox shows scrollbar on growth, you can hide like this. */
- overflow: hidden;
-}
-.grow-wrap > textarea,
-.grow-wrap::after {
- /* Identical styling required!! */
- padding: 0.5rem;
- font: inherit;
-
- /* Place on top of each other */
- grid-area: 1 / 1 / 2 / 2;
-}
-/* ============================================================= */
-
-#node-content {
- justify-self: center;
- word-wrap: break-word;
- font-family: monospace;
- color: #333;
- width: calc(100% - 32px);
- max-width: 900px;
- resize: none;
- border: none;
- outline: none;
-
- &:invalid {
- background: #f5f5f5;
- padding-top: 16px;
- }
-}
-
-#blank {
- grid-area: blank;
- height: 32px;
-}
-
-dialog.op {
- &::backdrop {
- background: rgba(0, 0, 0, 0.5);
- }
-
- .header {
- font-weight: bold;
- margin-top: 16px;
-
- &:first-child {
- margin-top: 0px;
- }
- }
-
-}
-
-#op-search {
- .results {
- display: grid;
- grid-template-columns: min-content min-content;
- grid-gap: 6px 16px;
-
- div {
- white-space: nowrap;
- }
-
-
- .ancestors {
- display: flex;
-
- .ancestor::after {
- content: ">";
- margin: 0px 8px;
- color: #a00;
- }
-
- .ancestor:last-child::after {
- content: "";
- }
- }
- }
-}
diff --git a/static/service_worker.js b/static/service_worker.js
index 6c77241..c48c162 100644
--- a/static/service_worker.js
+++ b/static/service_worker.js
@@ -6,13 +6,6 @@ const CACHED_ASSETS = [
'/css/{{ .VERSION }}/main.css',
'/css/{{ .VERSION }}/notes2.css',
- '/js/{{ .VERSION }}/lib/preact/preact.mjs',
- '/js/{{ .VERSION }}/lib/preact/devtools.mjs',
- '/js/{{ .VERSION }}/lib/signals/signals.mjs',
- '/js/{{ .VERSION }}/lib/signals/signals-core.mjs',
- '/js/{{ .VERSION }}/lib/preact/hooks.mjs',
- '/js/{{ .VERSION }}/lib/preact/debug.mjs',
- '/js/{{ .VERSION }}/lib/htm/htm.mjs',
'/js/{{ .VERSION }}/lib/fullcalendar.min.js',
'/js/{{ .VERSION }}/lib/node_modules/marked/marked.min.js',
'/js/{{ .VERSION }}/lib/sjcl.js',
diff --git a/views/layouts/main.gotmpl b/views/layouts/main.gotmpl
index 470cfe5..b3b5514 100644
--- a/views/layouts/main.gotmpl
+++ b/views/layouts/main.gotmpl
@@ -2,21 +2,11 @@
-
+
{{ end }}