Compare commits
No commits in common. "main" and "v31" have entirely different histories.
8 changed files with 17 additions and 277 deletions
4
main.go
4
main.go
|
|
@ -25,9 +25,9 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const VERSION = "v34"
|
||||
const VERSION = "v31"
|
||||
const CONTEXT_USER = 1
|
||||
const SYNC_PAGINATION = 200
|
||||
const SYNC_PAGINATION = 1
|
||||
|
||||
var (
|
||||
FlagGenerate bool
|
||||
|
|
|
|||
|
|
@ -93,19 +93,9 @@ export class App {
|
|||
// 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.
|
||||
const CTRL = !event.shiftKey && event.ctrlKey && !event.altKey
|
||||
const SHIFT_CTRL = event.shiftKey && event.ctrlKey && !event.altKey
|
||||
const SHIFT_ALT = event.shiftKey && !event.ctrlKey && event.altKey
|
||||
const SHIFT_CTRL_ALT = event.shiftKey && event.ctrlKey && event.altKey
|
||||
|
||||
// Space is better handled with event.code
|
||||
if (event.code.toUpperCase() == 'SPACE' && SHIFT_CTRL) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
document.querySelector('n2-nodenavigator').open()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch (event.key.toUpperCase()) {
|
||||
case 'F2':
|
||||
this.nodeUI.renameNode()
|
||||
|
|
@ -143,11 +133,6 @@ export class App {
|
|||
this.nodeUI.saveNode()
|
||||
break
|
||||
|
||||
case ' ':
|
||||
if (!CTRL) { handled = false; break }
|
||||
console.log('ctrl+space')
|
||||
break
|
||||
|
||||
default:
|
||||
handled = false
|
||||
}
|
||||
|
|
@ -236,7 +221,7 @@ export class App {
|
|||
const devPrefSet = localStorage.getItem('device_preference_set') || 'default'
|
||||
const userData = localStorage.getItem('user') || '{"default": {}}'
|
||||
const user = JSON.parse(userData)
|
||||
return new N2PreferenceSet(devPrefSet, user.Preferences[devPrefSet] || {})
|
||||
return new N2PreferenceSet(devPrefSet, user.Preferences[devPrefSet])
|
||||
}// }}}
|
||||
}
|
||||
|
||||
|
|
@ -418,175 +403,8 @@ class N2DragIcon extends CustomHTMLElement {
|
|||
}// }}}
|
||||
}
|
||||
|
||||
class N2NodeNavigator extends CustomHTMLElement {
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
dialog {
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
left: 32px;
|
||||
margin-top: 0;
|
||||
margin-bottom: auto;
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.field-search {
|
||||
width: 100%;
|
||||
|
||||
&.no-match {
|
||||
color: #a00;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-path, .el-children {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.el-path {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
gap: 8px;
|
||||
|
||||
.separator {
|
||||
color: var(--color1);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<dialog data-el="dlg">
|
||||
<input data-field="search" value="">
|
||||
<div data-el="path">Path:</div>
|
||||
<div data-el="children"></div>
|
||||
</dialog>
|
||||
`
|
||||
}
|
||||
constructor() {// {{{
|
||||
super(true)
|
||||
this.rootNode = new Node({ UUID: ROOT_NODE, Name: 'Start', Special: true }, -1)
|
||||
this.currNode = this.rootNode
|
||||
|
||||
this.fieldSearch.addEventListener('input', () => {
|
||||
this.render()
|
||||
})
|
||||
|
||||
this.fieldSearch.addEventListener('keydown', event => {
|
||||
if (event.key == 'Enter' && this.currNode !== null && this.lastSearchTermResultedInNode) {
|
||||
globalThis._app.goToNode(this.currNode.UUID, false, false)
|
||||
this.close()
|
||||
}
|
||||
})
|
||||
}// }}}
|
||||
async open() {// {{{
|
||||
if (this.elDlg.open)
|
||||
return
|
||||
|
||||
this.currNode = null
|
||||
this.fieldSearch.value = ''
|
||||
|
||||
this.render()
|
||||
this.elDlg.showModal()
|
||||
}// }}}
|
||||
close() {// {{{
|
||||
this.elDlg.close()
|
||||
}// }}}
|
||||
async render() {// {{{
|
||||
let currNode = this.rootNode
|
||||
this.lastSearchTermResultedInNode = false
|
||||
const searchPath = this.getSearchPath()
|
||||
const path = []
|
||||
|
||||
searchLoop:
|
||||
for (const search of searchPath) {
|
||||
// string.split returns an empty string if last character is a space.
|
||||
// We don't want to make a difference if that's the case.
|
||||
if (search === '')
|
||||
continue
|
||||
|
||||
// fetched nodes hasn't fetched children automatically.
|
||||
if (!currNode.hasFetchedChildren())
|
||||
await currNode.fetchChildren()
|
||||
|
||||
// Normalized names of current node's children are matched against
|
||||
// the current search term and compiled into a list.
|
||||
// The algorithm stops when there are none or more than one.
|
||||
const matches = []
|
||||
for (const childNode of currNode.Children) {
|
||||
const childName = this.normalizeName(childNode.get('Name'))
|
||||
if (childName.match(search))
|
||||
matches.push(childNode)
|
||||
}
|
||||
|
||||
switch (matches.length) {
|
||||
case 0:
|
||||
this.fieldSearch.classList.add('no-match')
|
||||
this.lastSearchTermResultedInNode = false
|
||||
break searchLoop
|
||||
|
||||
case 1:
|
||||
this.fieldSearch.classList.remove('no-match')
|
||||
currNode = matches[0]
|
||||
const separator = document.createElement('div')
|
||||
separator.classList.add('separator')
|
||||
separator.innerHTML = `>`
|
||||
|
||||
const pathEl = document.createElement('div')
|
||||
pathEl.innerText = currNode.get('Name')
|
||||
path.push(separator, pathEl)
|
||||
|
||||
this.lastSearchTermResultedInNode = true
|
||||
continue
|
||||
|
||||
default:
|
||||
// More than one match.
|
||||
this.lastSearchTermResultedInNode = false
|
||||
this.fieldSearch.classList.remove('no-match')
|
||||
break searchLoop
|
||||
}
|
||||
}
|
||||
|
||||
// The currNode can be the last of the search and not having had it children fetched previously.
|
||||
if (!currNode.hasFetchedChildren())
|
||||
await currNode.fetchChildren()
|
||||
|
||||
this.elPath.replaceChildren(...path)
|
||||
|
||||
// Children are rendered for the current node to be found by the search path.
|
||||
const search = this.lastSearchTermResultedInNode ? '' : searchPath.slice(-1)
|
||||
|
||||
const childNodes = []
|
||||
for (const childNode of currNode.Children) {
|
||||
const childName = this.normalizeName(childNode.get('Name'))
|
||||
if (!childName.match(search))
|
||||
continue
|
||||
|
||||
const childEl = document.createElement('div')
|
||||
childEl.classList.add('child-node')
|
||||
childEl.innerText = `${this.normalizeName(childNode.get('Name'))}`
|
||||
childNodes.push(childEl)
|
||||
}
|
||||
|
||||
this.currNode = currNode
|
||||
globalThis.navNode = currNode.get('Name') + `, ${this.lastSearchTermResultedInNode}`
|
||||
|
||||
this.elChildren.replaceChildren(...childNodes)
|
||||
}// }}}
|
||||
normalizeName(name) {// {{{
|
||||
return name.toLowerCase().replaceAll(/\s+/g, '_')
|
||||
}// }}}
|
||||
getSearchPath() {// {{{
|
||||
return this.fieldSearch.value.toLowerCase().split(/\s+/)
|
||||
}// }}}
|
||||
}
|
||||
|
||||
customElements.define('n2-crumbs', N2Crumbs)
|
||||
customElements.define('n2-crumb', N2Crumb)
|
||||
customElements.define('n2-nodenavigator', N2NodeNavigator)
|
||||
customElements.define('n2-dragicon', N2DragIcon)
|
||||
|
||||
// vim: foldmethod=marker
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export class CustomHTMLElement extends HTMLElement {
|
|||
const fieldName = this.toElementName('field', field)
|
||||
this[fieldName] = el
|
||||
this._fields.set(this.toElementName('', field), el)
|
||||
el.classList.add('field-' + el.dataset.field)
|
||||
}
|
||||
|
||||
const name = el.dataset.el
|
||||
|
|
|
|||
|
|
@ -328,52 +328,6 @@ export class NodeStore {
|
|||
this.nodes[ORPHANED_NODE] = new Node({ UUID: ORPHANED_NODE, Name: 'Orphaned nodes', Special: true }, -1)
|
||||
}// }}}
|
||||
|
||||
deleteAll() {// {{{
|
||||
this.db.close()
|
||||
|
||||
const req = indexedDB.deleteDatabase('notes')
|
||||
|
||||
req.onsuccess = async () => {
|
||||
try {
|
||||
console.log('Database deleted successfully')
|
||||
|
||||
// Deleting the storage will log out the user.
|
||||
await this.deleteStorage()
|
||||
await this.deleteCaches()
|
||||
await this.deleteServiceWorkers()
|
||||
|
||||
alert('Database deleted, will now log out.')
|
||||
location.href = '/login'
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
req.onerror = (event) => {
|
||||
console.error('Error deleting database:', event.target.errorCode)
|
||||
}
|
||||
|
||||
req.onblocked = () => {
|
||||
console.warn('Deletion blocked. Close all other tabs/connections to this DB.')
|
||||
}
|
||||
}// }}}
|
||||
async deleteStorage() {// {{{
|
||||
// Clearing local- and session-storage in case
|
||||
// something of value to evil people gets stored there.
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
console.log('Local- and session-storage cleared.')
|
||||
}// }}}
|
||||
async deleteCaches() {// {{{
|
||||
const keys = await caches.keys()
|
||||
return Promise.all(keys.map(key => caches.delete(key)))
|
||||
}// }}}
|
||||
async deleteServiceWorkers() {// {{{
|
||||
const registrations = await navigator.serviceWorker.getRegistrations()
|
||||
await Promise.all(registrations.map(r => r.unregister()))
|
||||
}// }}}
|
||||
|
||||
node(uuid, dataIfUndefined, newLevel) {//{{{
|
||||
let n = this.nodes[uuid]
|
||||
if (n === undefined && dataIfUndefined !== undefined)
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ export class N2PreferenceSet extends CustomHTMLElement {
|
|||
super(true)
|
||||
this.name = name
|
||||
this.data = data
|
||||
this.validateDefaultProfile()
|
||||
this.render()
|
||||
|
||||
// Enable the save button when settings are modified.
|
||||
|
|
@ -249,16 +248,6 @@ export class N2PreferenceSet extends CustomHTMLElement {
|
|||
this.elName.addEventListener('click', () => this.updateName())
|
||||
this.elDelete.addEventListener('click', () => this.deleteSet())
|
||||
}// }}}
|
||||
validateDefaultProfile() {// {{{
|
||||
if (!this.data.hasOwnProperty('default'))
|
||||
this.data.default = {}
|
||||
|
||||
if (!this.data.default.hasOwnProperty('DownloadFiles'))
|
||||
this.data.default.DownloadFiles = false
|
||||
|
||||
if (!this.data.default.hasOwnProperty('DownloadImages'))
|
||||
this.data.default.DownloadImages = false
|
||||
}// }}}
|
||||
updateName() {// {{{
|
||||
if (this.name == 'default') {
|
||||
alert('Can not change name of the default profile.')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { CustomHTMLElement } from "./lib/custom_html_element.mjs"
|
|||
import { API } from 'api'
|
||||
|
||||
export class N2PageStorage extends CustomHTMLElement {
|
||||
static {// {{{
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<style>
|
||||
|
|
@ -25,24 +25,20 @@ export class N2PageStorage extends CustomHTMLElement {
|
|||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.el-btn-clear-all {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Local storage</h1>
|
||||
|
||||
<h2>Notes</h2>
|
||||
<h2>Nodes</h2>
|
||||
<div class="table">
|
||||
<div>Local notes</div>
|
||||
<div>Local nodes</div>
|
||||
<div data-el="count-nodes"></div>
|
||||
|
||||
<div>History notes</div>
|
||||
<div data-el="count-history-nodes"></div>
|
||||
|
||||
<div>Notes to send</div>
|
||||
<div>Queued to sync</div>
|
||||
<div data-el="count-queued-nodes"></div>
|
||||
|
||||
<div>History nodes</div>
|
||||
<div data-el="count-history-nodes"></div>
|
||||
</div>
|
||||
|
||||
<h2>Files</h2>
|
||||
|
|
@ -56,24 +52,17 @@ export class N2PageStorage extends CustomHTMLElement {
|
|||
<div>Files to send</div>
|
||||
<div data-el="count-queued-files"></div>
|
||||
</div>
|
||||
|
||||
<h2>Clear storage</h2>
|
||||
<div>Remove all data from this device (not server) and logout the session.</div>
|
||||
<div>This ensures that no notes or files is left on the browser.</div>
|
||||
<button data-el="btn-clear-all">Clear and logout</button>
|
||||
`
|
||||
}// }}}
|
||||
constructor() {// {{{
|
||||
}
|
||||
constructor() {
|
||||
super(true)
|
||||
|
||||
.elBtnClearAll.addEventListener('click', ()=>this.clearAll())
|
||||
|
||||
window._mbus.subscribe('SHOW_PAGE', event => {
|
||||
if (event.detail.data?.page == 'storage')
|
||||
this.render()
|
||||
})
|
||||
}// }}}
|
||||
async render() {// {{{
|
||||
}
|
||||
async render() {
|
||||
API.query('GET', '/file/count')
|
||||
.then(res => {
|
||||
this.elCountFilesOnServer.innerText = res.Count
|
||||
|
|
@ -91,12 +80,6 @@ export class N2PageStorage extends CustomHTMLElement {
|
|||
this.elCountQueuedFiles.innerText = await globalThis.nodeStore.filesMetadata.countToUpload()
|
||||
|
||||
this.elCountFilesOnDevice.innerText = await globalThis.nodeStore.filesMetadata.count()
|
||||
}// }}}
|
||||
clearAll() {// {{{
|
||||
if (!confirm('Do you want to remove ALL data from this device and logout?'))
|
||||
return
|
||||
|
||||
globalThis.nodeStore.deleteAll()
|
||||
}// }}}
|
||||
}
|
||||
}
|
||||
customElements.define('n2-pagestorage', N2PageStorage)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@
|
|||
<script>
|
||||
globalThis._VERSION = "{{ .VERSION }}"
|
||||
|
||||
const token = localStorage.getItem('token')
|
||||
if (token && navigator.serviceWorker)
|
||||
if (navigator.serviceWorker)
|
||||
navigator.serviceWorker.register('/service_worker.js', { type: 'module' })
|
||||
</script>
|
||||
<script type="module" defer>
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@
|
|||
</div>
|
||||
|
||||
<n2-syncprogress></n2-syncprogress>
|
||||
|
||||
<n2-nodenavigator></n2-nodenavigator>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue