mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into next60
This commit is contained in:
commit
b84670d503
7
.eslintignore
Normal file
7
.eslintignore
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
bin
|
||||
docs
|
||||
libraries
|
||||
coverage
|
||||
play
|
221
.eslintrc.js
221
.eslintrc.js
@ -1,16 +1,213 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
// plugins: ['prettier'], // to be activated
|
||||
extends: ['eslint:recommended', 'airbnb-base', 'plugin:jsonc/recommended-with-jsonc', 'prettier'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.json', '*.json5', '*.jsonc'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
},
|
||||
{
|
||||
files: ['package.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'jsonc/sort-keys': [
|
||||
'off',
|
||||
{
|
||||
pathPattern: '^$',
|
||||
order: [
|
||||
'name',
|
||||
'version',
|
||||
'private',
|
||||
'packageManager',
|
||||
'description',
|
||||
'type',
|
||||
'keywords',
|
||||
'homepage',
|
||||
'bugs',
|
||||
'license',
|
||||
'author',
|
||||
'contributors',
|
||||
'funding',
|
||||
'files',
|
||||
'main',
|
||||
'module',
|
||||
'exports',
|
||||
'unpkg',
|
||||
'jsdelivr',
|
||||
'browser',
|
||||
'bin',
|
||||
'man',
|
||||
'directories',
|
||||
'repository',
|
||||
'publishConfig',
|
||||
'scripts',
|
||||
'peerDependencies',
|
||||
'peerDependenciesMeta',
|
||||
'optionalDependencies',
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'engines',
|
||||
'config',
|
||||
'overrides',
|
||||
'pnpm',
|
||||
'husky',
|
||||
'lint-staged',
|
||||
'eslintConfig',
|
||||
],
|
||||
},
|
||||
{
|
||||
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
|
||||
order: { type: 'asc' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
globals: {
|
||||
$: true,
|
||||
jQuery: true,
|
||||
glob: true,
|
||||
log: true,
|
||||
EditorWatchdog: true,
|
||||
baseApiUrl: true,
|
||||
// \src\share\canvas_share.js
|
||||
React: true,
|
||||
appState: true,
|
||||
ExcalidrawLib: true,
|
||||
elements: true,
|
||||
files: true,
|
||||
ReactDOM: true,
|
||||
// src\public\app\widgets\type_widgets\relation_map.js
|
||||
jsPlumb: true,
|
||||
panzoom: true,
|
||||
logError: true,
|
||||
// src\public\app\widgets\type_widgets\image.js
|
||||
WZoom: true,
|
||||
// \src\public\app\widgets\type_widgets\read_only_text.js
|
||||
renderMathInElement: true,
|
||||
// \src\public\app\widgets\type_widgets\editable_text.js
|
||||
BalloonEditor: true,
|
||||
CKEditorInspector: true,
|
||||
// \src\public\app\widgets\type_widgets\editable_code.js
|
||||
CodeMirror: true,
|
||||
// \src\public\app\services\resizer.js
|
||||
Split: true,
|
||||
// \src\public\app\services\note_content_renderer.js
|
||||
mermaid: true,
|
||||
// src\public\app\services\frontend_script_api.js
|
||||
dayjs: true,
|
||||
// \src\public\app\widgets\dialogs\markdown_import.js
|
||||
commonmark: true,
|
||||
// \src\public\app\widgets\note_map.js
|
||||
ForceGraph: true,
|
||||
// \src\public\app\setup.js
|
||||
ko: true,
|
||||
syncInProgress: true,
|
||||
// src\public\app\services\utils.js
|
||||
logInfo: true,
|
||||
__non_webpack_require__: true,
|
||||
//
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
// eslint:recommended
|
||||
'no-unused-vars': 'off',
|
||||
'linebreak-style': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'no-empty': 'off',
|
||||
'no-constant-condition': 'off',
|
||||
'getter-return': 'off',
|
||||
'no-cond-assign': 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-extra-semi': 'off',
|
||||
'no-inner-declarations': 'off',
|
||||
|
||||
// prettier
|
||||
'prettier/prettier': ['off', { endOfLine: 'auto' }],
|
||||
|
||||
// airbnb-base
|
||||
'no-console': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'global-require': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
radix: 'off',
|
||||
'import/order': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'prefer-destructuring': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-new': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
strict: 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'no-else-return': 'off',
|
||||
'import/no-dynamic-require': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'prefer-template': 'off',
|
||||
'consistent-return': 'off',
|
||||
'no-continue': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'one-var': 'off',
|
||||
'prefer-const': 'off',
|
||||
'spaced-comment': 'off',
|
||||
'no-loop-func': 'off',
|
||||
'arrow-body-style': 'off',
|
||||
|
||||
'guard-for-in': 'off',
|
||||
'no-return-assign': 'off',
|
||||
'dot-notation': 'off',
|
||||
|
||||
'func-names': 'off',
|
||||
'import/no-useless-path-segments': 'off',
|
||||
'default-param-last': 'off',
|
||||
'prefer-arrow-callback': 'off',
|
||||
'no-unneeded-ternary': 'off',
|
||||
'no-return-await': 'off',
|
||||
'import/extensions': 'off',
|
||||
|
||||
'no-var': 'off',
|
||||
'import/newline-after-import': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'operator-assignment': 'off',
|
||||
'no-eval': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'vars-on-top': 'off',
|
||||
'no-bitwise': 'off',
|
||||
'no-lonely-if': 'off',
|
||||
'no-multi-assign': 'off',
|
||||
'no-promise-executor-return': 'off',
|
||||
'no-empty-function': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
camelcase: 'off',
|
||||
eqeqeq: 'off',
|
||||
'lines-between-class-members': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
'new-cap': 'off',
|
||||
'prefer-object-spread': 'off',
|
||||
'no-new-func': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'lines-around-directive': 'off',
|
||||
'prefer-exponentiation-operator': 'off',
|
||||
'no-restricted-properties': 'off',
|
||||
'prefer-rest-params': 'off',
|
||||
'no-unreachable-loop': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-useless-return': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'prefer-regex-literals': 'off',
|
||||
'import/no-named-as-default-member': 'off',
|
||||
yoda: 'off',
|
||||
'no-script-url': 'off',
|
||||
'no-prototype-builtins':'off'
|
||||
},
|
||||
};
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ server-package.json
|
||||
.idea/httpRequests/
|
||||
data/
|
||||
tmp/
|
||||
.eslintcache
|
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
_
|
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
#npx lint-staged
|
11
.prettierrc.js
Normal file
11
.prettierrc.js
Normal file
@ -0,0 +1,11 @@
|
||||
//https://prettier.io/docs/en/options.html
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'es5',
|
||||
singleQuote: true,
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
// useTabs: false,
|
||||
// bracketSpacing: true,
|
||||
// htmlWhitespaceSensitivity: 'ignore',
|
||||
};
|
6
.vscode/extensions.json
vendored
Normal file
6
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
]
|
||||
}
|
33
.vscode/settings.json
vendored
Normal file
33
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.probe": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"html",
|
||||
"vue",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"html",
|
||||
"vue",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
],
|
||||
"files.eol": "\n",
|
||||
}
|
@ -8,7 +8,6 @@ if (config.https) {
|
||||
// built-in TLS (terminated by trilium) is not supported yet, PRs are welcome
|
||||
// for reverse proxy terminated TLS this will works since config.https will be false
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const port = require('./src/services/port');
|
||||
|
2
libraries/codemirror/addon/lint/eslint.js
vendored
2
libraries/codemirror/addon/lint/eslint.js
vendored
@ -46,7 +46,7 @@
|
||||
const errors = new eslint().verify(text, {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2019
|
||||
ecmaVersion: 2022
|
||||
},
|
||||
extends: ['eslint:recommended', 'airbnb-base'],
|
||||
env: {
|
||||
|
13
nodemon.json
Normal file
13
nodemon.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"restartable": "rs",
|
||||
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
|
||||
"verbose": false,
|
||||
"execMap": {
|
||||
"js": "node --harmony"
|
||||
},
|
||||
"watch": ["src/"],
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"ext": "js,json"
|
||||
}
|
27
package.json
27
package.json
@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.59.3",
|
||||
"version": "0.59.4",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@ -13,20 +13,22 @@
|
||||
"url": "https://github.com/zadam/trilium.git"
|
||||
},
|
||||
"scripts": {
|
||||
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www",
|
||||
"start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www",
|
||||
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
|
||||
"start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
|
||||
"start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
|
||||
"start-electron-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
|
||||
"switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install",
|
||||
"switch-electron": "rm -rf ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild",
|
||||
"switch-electron": "./node_modules/.bin/electron-rebuild",
|
||||
"build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js",
|
||||
"build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/right_panel_widget.js",
|
||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||
"webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js",
|
||||
"webpack": "webpack -c webpack.config.js",
|
||||
"test-jasmine": "jasmine",
|
||||
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
|
||||
"test": "npm run test-jasmine && npm run test-es6",
|
||||
"postinstall": "rimraf ./node_modules/canvas"
|
||||
"postinstall": "rimraf ./node_modules/canvas",
|
||||
"lint": "eslint . --cache",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
@ -100,15 +102,28 @@
|
||||
"electron-packager": "17.1.1",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsonc": "^2.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esm": "3.2.25",
|
||||
"husky": "^8.0.3",
|
||||
"jsonc-eslint-parser": "^2.2.0",
|
||||
"lint-staged": "^13.2.1",
|
||||
"jasmine": "4.6.0",
|
||||
"jsdoc": "4.0.2",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"prettier": "2.8.7",
|
||||
"nodemon": "^2.0.22",
|
||||
"rcedit": "3.0.1",
|
||||
"webpack": "5.78.0",
|
||||
"webpack-cli": "5.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron-installer-debian": "3.1.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --cache --fix"
|
||||
}
|
||||
}
|
||||
|
@ -24,49 +24,12 @@ function isNotePathArchived(notePath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This assumes that note is available. "archived" note means that there isn't a single non-archived note-path
|
||||
* leading to this note.
|
||||
*
|
||||
* @param noteId
|
||||
*/
|
||||
function isArchived(noteId) {
|
||||
const notePath = getSomePath(noteId);
|
||||
|
||||
return isNotePathArchived(notePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} noteId
|
||||
* @param {string} ancestorNoteId
|
||||
* @returns {boolean} - true if given noteId has ancestorNoteId in any of its paths (even archived)
|
||||
*/
|
||||
function isInAncestor(noteId, ancestorNoteId) {
|
||||
if (ancestorNoteId === 'root' || ancestorNoteId === noteId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const note = becca.notes[noteId];
|
||||
|
||||
if (!note) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const parentNote of note.parents) {
|
||||
if (isInAncestor(parentNote.noteId, ancestorNoteId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNoteTitle(childNoteId, parentNoteId) {
|
||||
const childNote = becca.notes[childNoteId];
|
||||
const parentNote = becca.notes[parentNoteId];
|
||||
|
||||
if (!childNote) {
|
||||
log.info(`Cannot find note in cache for noteId '${childNoteId}'`);
|
||||
log.info(`Cannot find note '${childNoteId}'`);
|
||||
return "[error fetching title]";
|
||||
}
|
||||
|
||||
@ -119,107 +82,8 @@ function getNoteTitleForPath(notePathArray) {
|
||||
return titles.join(' / ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns notePath for noteId from cache. Note hoisting is respected.
|
||||
* Archived (and hidden) notes are also returned, but non-archived paths are preferred if available
|
||||
* - this means that archived paths is returned only if there's no non-archived path
|
||||
* - you can check whether returned path is archived using isArchived
|
||||
*
|
||||
* @param {BNote} note
|
||||
* @param {string[]} path
|
||||
*/
|
||||
function getSomePath(note, path = []) {
|
||||
// first try to find note within hoisted note, otherwise take any existing note path
|
||||
return getSomePathInner(note, path, true)
|
||||
|| getSomePathInner(note, path, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BNote} note
|
||||
* @param {string[]} path
|
||||
* @param {boolean}respectHoisting
|
||||
* @returns {string[]|false}
|
||||
*/
|
||||
function getSomePathInner(note, path, respectHoisting) {
|
||||
if (note.isRoot()) {
|
||||
const foundPath = [...path, note.noteId];
|
||||
foundPath.reverse();
|
||||
|
||||
if (respectHoisting && !foundPath.includes(cls.getHoistedNoteId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
const parents = note.parents;
|
||||
if (parents.length === 0) {
|
||||
console.log(`Note '${note.noteId}' - '${note.title}' has no parents.`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const parentNote of parents) {
|
||||
const retPath = getSomePathInner(parentNote, [...path, note.noteId], respectHoisting);
|
||||
|
||||
if (retPath) {
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNotePath(noteId) {
|
||||
const note = becca.notes[noteId];
|
||||
|
||||
if (!note) {
|
||||
console.trace(`Cannot find note '${noteId}' in cache.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const retPath = getSomePath(note);
|
||||
|
||||
if (retPath) {
|
||||
const noteTitle = getNoteTitleForPath(retPath);
|
||||
|
||||
let branchId;
|
||||
|
||||
if (note.isRoot()) {
|
||||
branchId = 'none_root';
|
||||
}
|
||||
else {
|
||||
const parentNote = note.parents[0];
|
||||
branchId = becca.getBranchFromChildAndParent(noteId, parentNote.noteId).branchId;
|
||||
}
|
||||
|
||||
return {
|
||||
noteId: noteId,
|
||||
branchId: branchId,
|
||||
title: noteTitle,
|
||||
notePath: retPath,
|
||||
path: retPath.join('/')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param noteId
|
||||
* @returns {boolean} - true if note exists (is not deleted) and is available in current note hoisting
|
||||
*/
|
||||
function isAvailable(noteId) {
|
||||
const notePath = getNotePath(noteId);
|
||||
|
||||
return !!notePath;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSomePath,
|
||||
getNotePath,
|
||||
getNoteTitle,
|
||||
getNoteTitleForPath,
|
||||
isAvailable,
|
||||
isArchived,
|
||||
isInAncestor,
|
||||
isNotePathArchived
|
||||
};
|
||||
|
@ -688,6 +688,21 @@ class BNote extends AbstractBeccaEntity {
|
||||
return this.hasAttribute('label', 'archived');
|
||||
}
|
||||
|
||||
areAllNotePathsArchived() {
|
||||
// there's a slight difference between note being itself archived and all its note paths being archived
|
||||
// - note is archived when it itself has an archived label or inherits it
|
||||
// - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable)
|
||||
// archived label
|
||||
|
||||
const bestNotePathRecord = this.getSortedNotePathRecords()[0];
|
||||
|
||||
if (!bestNotePathRecord) {
|
||||
throw new Error(`No note path available for note '${this.noteId}'`);
|
||||
}
|
||||
|
||||
return bestNotePathRecord.isArchived;
|
||||
}
|
||||
|
||||
hasInheritableArchivedLabel() {
|
||||
for (const attr of this.getAttributes()) {
|
||||
if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) {
|
||||
@ -1118,6 +1133,8 @@ class BNote extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
|
||||
*
|
||||
* @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
|
||||
*/
|
||||
getAllNotePaths() {
|
||||
@ -1125,18 +1142,73 @@ class BNote extends AbstractBeccaEntity {
|
||||
return [['root']];
|
||||
}
|
||||
|
||||
const notePaths = [];
|
||||
const parentNotes = this.getParentNotes();
|
||||
let notePaths = [];
|
||||
|
||||
for (const parentNote of this.getParentNotes()) {
|
||||
for (const parentPath of parentNote.getAllNotePaths()) {
|
||||
parentPath.push(this.noteId);
|
||||
notePaths.push(parentPath);
|
||||
}
|
||||
if (parentNotes.length === 1) { // optimization for most common case
|
||||
notePaths = parentNotes[0].getAllNotePaths();
|
||||
} else {
|
||||
notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
|
||||
}
|
||||
|
||||
for (const notePath of notePaths) {
|
||||
notePath.push(this.noteId);
|
||||
}
|
||||
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]}
|
||||
*/
|
||||
getSortedNotePathRecords(hoistedNoteId = 'root') {
|
||||
const isHoistedRoot = hoistedNoteId === 'root';
|
||||
|
||||
const notePaths = this.getAllNotePaths().map(path => ({
|
||||
notePath: path,
|
||||
isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId),
|
||||
isArchived: path.some(noteId => this.becca.notes[noteId].isArchived),
|
||||
isHidden: path.includes('_hidden')
|
||||
}));
|
||||
|
||||
notePaths.sort((a, b) => {
|
||||
if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
|
||||
return a.isInHoistedSubTree ? -1 : 1;
|
||||
} else if (a.isArchived !== b.isArchived) {
|
||||
return a.isArchived ? 1 : -1;
|
||||
} else if (a.isHidden !== b.isHidden) {
|
||||
return a.isHidden ? 1 : -1;
|
||||
} else {
|
||||
return a.notePath.length - b.notePath.length;
|
||||
}
|
||||
});
|
||||
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns note path considered to be the "best"
|
||||
*
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {string[]} array of noteIds constituting the particular note path
|
||||
*/
|
||||
getBestNotePath(hoistedNoteId = 'root') {
|
||||
return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns note path considered to be the "best"
|
||||
*
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {string} serialized note path (e.g. 'root/a1h315/js725h')
|
||||
*/
|
||||
getBestNotePathString(hoistedNoteId = 'root') {
|
||||
const notePath = this.getBestNotePath(hoistedNoteId);
|
||||
|
||||
return notePath?.join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
|
||||
*/
|
||||
|
@ -404,7 +404,7 @@ async function findSimilarNotes(noteId) {
|
||||
let score = computeScore(candidateNote);
|
||||
|
||||
if (score >= 1.5) {
|
||||
const notePath = beccaService.getSomePath(candidateNote);
|
||||
const notePath = candidateNote.getBestNotePath();
|
||||
|
||||
// this takes care of note hoisting
|
||||
if (!notePath) {
|
||||
|
@ -413,7 +413,12 @@ export default class TabManager extends Component {
|
||||
await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove });
|
||||
|
||||
if (!noteContextToRemove.isMainContext()) {
|
||||
await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId);
|
||||
const siblings = noteContextToRemove.getMainContext().getSubContexts();
|
||||
const idx = siblings.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId);
|
||||
const contextToActivateIdx = idx === siblings.length - 1 ? idx - 1 : idx + 1;
|
||||
const contextToActivate = siblings[contextToActivateIdx];
|
||||
|
||||
await this.activateNoteContext(contextToActivate.ntxId);
|
||||
}
|
||||
else if (this.mainNoteContexts.length <= 1) {
|
||||
await this.openAndActivateEmptyTab();
|
||||
|
@ -268,6 +268,11 @@ class FNote {
|
||||
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} path
|
||||
* @return {FAttribute[]}
|
||||
* @private
|
||||
*/
|
||||
__getCachedAttributes(path) {
|
||||
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
|
||||
// when template instance is a parent of template itself
|
||||
@ -320,63 +325,49 @@ class FNote {
|
||||
return this.noteId === 'root';
|
||||
}
|
||||
|
||||
getAllNotePaths(encounteredNoteIds = null) {
|
||||
/**
|
||||
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
|
||||
*
|
||||
* @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
|
||||
*/
|
||||
getAllNotePaths() {
|
||||
if (this.noteId === 'root') {
|
||||
return [['root']];
|
||||
}
|
||||
|
||||
if (!encounteredNoteIds) {
|
||||
encounteredNoteIds = new Set();
|
||||
}
|
||||
|
||||
encounteredNoteIds.add(this.noteId);
|
||||
|
||||
const parentNotes = this.getParentNotes();
|
||||
let paths;
|
||||
let notePaths = [];
|
||||
|
||||
if (parentNotes.length === 1) { // optimization for the most common case
|
||||
if (encounteredNoteIds.has(parentNotes[0].noteId)) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
paths = parentNotes[0].getAllNotePaths(encounteredNoteIds);
|
||||
}
|
||||
}
|
||||
else {
|
||||
paths = [];
|
||||
|
||||
for (const parentNote of parentNotes) {
|
||||
if (encounteredNoteIds.has(parentNote.noteId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newSet = new Set(encounteredNoteIds);
|
||||
|
||||
paths.push(...parentNote.getAllNotePaths(newSet));
|
||||
}
|
||||
if (parentNotes.length === 1) { // optimization for most common case
|
||||
notePaths = parentNotes[0].getAllNotePaths();
|
||||
} else {
|
||||
notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
|
||||
}
|
||||
|
||||
for (const path of paths) {
|
||||
path.push(this.noteId);
|
||||
for (const notePath of notePaths) {
|
||||
notePath.push(this.noteId);
|
||||
}
|
||||
|
||||
return paths;
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
getSortedNotePaths(hoistedNotePath = 'root') {
|
||||
/**
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]}
|
||||
*/
|
||||
getSortedNotePathRecords(hoistedNoteId = 'root') {
|
||||
const isHoistedRoot = hoistedNoteId === 'root';
|
||||
|
||||
const notePaths = this.getAllNotePaths().map(path => ({
|
||||
notePath: path,
|
||||
isInHoistedSubTree: path.includes(hoistedNotePath),
|
||||
isArchived: path.find(noteId => froca.notes[noteId].isArchived),
|
||||
isSearch: path.find(noteId => froca.notes[noteId].type === 'search'),
|
||||
isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId),
|
||||
isArchived: path.some(noteId => froca.notes[noteId].isArchived),
|
||||
isHidden: path.includes('_hidden')
|
||||
}));
|
||||
|
||||
notePaths.sort((a, b) => {
|
||||
if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
|
||||
return a.isInHoistedSubTree ? -1 : 1;
|
||||
} else if (a.isSearch !== b.isSearch) {
|
||||
return a.isSearch ? 1 : -1;
|
||||
} else if (a.isArchived !== b.isArchived) {
|
||||
return a.isArchived ? 1 : -1;
|
||||
} else if (a.isHidden !== b.isHidden) {
|
||||
@ -389,6 +380,28 @@ class FNote {
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns note path considered to be the "best"
|
||||
*
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {string[]} array of noteIds constituting the particular note path
|
||||
*/
|
||||
getBestNotePath(hoistedNoteId = 'root') {
|
||||
return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns note path considered to be the "best"
|
||||
*
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {string} serialized note path (e.g. 'root/a1h315/js725h')
|
||||
*/
|
||||
getBestNotePathString(hoistedNoteId = 'root') {
|
||||
const notePath = this.getBestNotePath(hoistedNoteId);
|
||||
|
||||
return notePath?.join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
|
||||
*/
|
||||
@ -412,6 +425,13 @@ class FNote {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FAttribute[]} attributes
|
||||
* @param {string} type
|
||||
* @param {string} name
|
||||
* @return {FAttribute[]}
|
||||
* @private
|
||||
*/
|
||||
__filterAttrs(attributes, type, name) {
|
||||
this.__validateTypeName(type, name);
|
||||
|
||||
@ -541,7 +561,9 @@ class FNote {
|
||||
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
|
||||
*/
|
||||
hasAttribute(type, name) {
|
||||
return !!this.getAttribute(type, name);
|
||||
const attributes = this.getAttributes();
|
||||
|
||||
return attributes.some(attr => attr.name === name && attr.type === type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,7 +227,7 @@ async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cloneNoteToNote(childNoteId, parentNoteId, prefix) {
|
||||
async function cloneNoteToParentNote(childNoteId, parentNoteId, prefix) {
|
||||
const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
|
||||
prefix: prefix
|
||||
});
|
||||
@ -254,5 +254,5 @@ export default {
|
||||
moveNodeUpInHierarchy,
|
||||
cloneNoteAfter,
|
||||
cloneNoteToBranch,
|
||||
cloneNoteToNote,
|
||||
cloneNoteToParentNote,
|
||||
};
|
||||
|
@ -140,7 +140,7 @@ async function processBranchChange(loadResults, ec) {
|
||||
const childNote = froca.notes[ec.entity.noteId];
|
||||
let parentNote = froca.notes[ec.entity.parentNoteId];
|
||||
|
||||
if (childNote && !parentNote) {
|
||||
if (childNote && !childNote.isRoot() && !parentNote) {
|
||||
// a branch cannot exist without the parent
|
||||
// a note loaded into froca has to also contain all its ancestors
|
||||
// this problem happened e.g. in sharing where _share was hidden and thus not loaded
|
||||
|
@ -2,7 +2,6 @@ import server from "./server.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import utils from './utils.js';
|
||||
import noteCreateService from './note_create.js';
|
||||
import treeService from './tree.js';
|
||||
import froca from "./froca.js";
|
||||
|
||||
// this key needs to have this value, so it's hit by the tooltip
|
||||
@ -188,7 +187,8 @@ function initNoteAutocomplete($el, options) {
|
||||
templateNoteId: templateNoteId
|
||||
});
|
||||
|
||||
suggestion.notePath = treeService.getSomeNotePath(note);
|
||||
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
suggestion.notePath = note.getBestNotePathString(hoistedNoteId);
|
||||
}
|
||||
|
||||
$el.setSelectedNotePath(suggestion.notePath);
|
||||
|
@ -4,6 +4,7 @@ import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import attributeRenderer from "./attribute_renderer.js";
|
||||
import noteContentRenderer from "./note_content_renderer.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
|
||||
function setupGlobalTooltip() {
|
||||
$(document).on("mouseenter", "a", mouseEnterHandler);
|
||||
@ -77,13 +78,14 @@ async function renderTooltip(note) {
|
||||
return '<div>Note has been deleted.</div>';
|
||||
}
|
||||
|
||||
const someNotePath = treeService.getSomeNotePath(note);
|
||||
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
const bestNotePath = note.getBestNotePathString(hoistedNoteId);
|
||||
|
||||
if (!someNotePath) {
|
||||
if (!bestNotePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(someNotePath)).prop('outerHTML')}</h5>`;
|
||||
let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(bestNotePath)).prop('outerHTML')}</h5>`;
|
||||
|
||||
const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note);
|
||||
|
||||
|
@ -79,14 +79,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
|
||||
You can ignore this message as it is mostly harmless.`);
|
||||
}
|
||||
|
||||
const someNotePath = getSomeNotePath(child, hoistedNoteId);
|
||||
const bestNotePath = child.getBestNotePath(hoistedNoteId);
|
||||
|
||||
if (someNotePath) { // in case it's root the path may be empty
|
||||
const pathToRoot = someNotePath.split("/").reverse().slice(1);
|
||||
|
||||
if (!pathToRoot.includes("root")) {
|
||||
pathToRoot.push('root');
|
||||
}
|
||||
if (bestNotePath) {
|
||||
const pathToRoot = bestNotePath.reverse().slice(1);
|
||||
|
||||
for (const noteId of pathToRoot) {
|
||||
effectivePathSegments.push(noteId);
|
||||
@ -109,31 +105,17 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
|
||||
else {
|
||||
const note = await froca.getNote(getNoteIdFromNotePath(notePath));
|
||||
|
||||
const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId);
|
||||
const bestNotePath = note.getBestNotePath(hoistedNoteId);
|
||||
|
||||
if (!someNotePathSegments) {
|
||||
throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`);
|
||||
if (!bestNotePath) {
|
||||
throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`);
|
||||
}
|
||||
|
||||
// if there isn't actually any note path with hoisted note then return the original resolved note path
|
||||
return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments;
|
||||
return bestNotePath.includes(hoistedNoteId) ? bestNotePath : effectivePathSegments;
|
||||
}
|
||||
}
|
||||
|
||||
function getSomeNotePathSegments(note, hoistedNotePath = 'root') {
|
||||
utils.assertArguments(note);
|
||||
|
||||
const notePaths = note.getSortedNotePaths(hoistedNotePath);
|
||||
|
||||
return notePaths.length > 0 ? notePaths[0].notePath : null;
|
||||
}
|
||||
|
||||
function getSomeNotePath(note, hoistedNotePath = 'root') {
|
||||
const notePath = getSomeNotePathSegments(note, hoistedNotePath);
|
||||
|
||||
return notePath === null ? null : notePath.join('/');
|
||||
}
|
||||
|
||||
ws.subscribeToMessages(message => {
|
||||
if (message.type === 'openNote') {
|
||||
appContext.tabManager.activateOrOpenNote(message.noteId);
|
||||
@ -341,16 +323,6 @@ function isNotePathInAddress() {
|
||||
|| (notePath === '' && !!ntxId);
|
||||
}
|
||||
|
||||
function parseNotePath(notePath) {
|
||||
let noteIds = notePath.split('/');
|
||||
|
||||
if (noteIds[0] !== 'root') {
|
||||
noteIds = ['root'].concat(noteIds);
|
||||
}
|
||||
|
||||
return noteIds;
|
||||
}
|
||||
|
||||
function isNotePathInHiddenSubtree(notePath) {
|
||||
return notePath?.includes("root/_hidden");
|
||||
}
|
||||
@ -358,8 +330,6 @@ function isNotePathInHiddenSubtree(notePath) {
|
||||
export default {
|
||||
resolveNotePath,
|
||||
resolveNotePathToSegments,
|
||||
getSomeNotePath,
|
||||
getSomeNotePathSegments,
|
||||
getParentProtectedStatus,
|
||||
getNotePath,
|
||||
getNoteIdFromNotePath,
|
||||
@ -370,6 +340,5 @@ export default {
|
||||
getNoteTitleWithPathAsSuffix,
|
||||
parseNavigationStateFromAddress,
|
||||
isNotePathInAddress,
|
||||
parseNotePath,
|
||||
isNotePathInHiddenSubtree
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
@ -9,6 +8,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import SpacedUpdate from "../../services/spaced_update.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attr-detail">
|
||||
@ -598,9 +598,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES);
|
||||
const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId));
|
||||
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
|
||||
for (const note of displayedNotes) {
|
||||
const notePath = treeService.getSomeNotePath(note);
|
||||
const notePath = note.getBestNotePathString(hoistedNoteId);
|
||||
const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true});
|
||||
|
||||
this.$relatedNotesList.append(
|
||||
|
@ -7,7 +7,6 @@ import libraryLoader from "../../services/library_loader.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import attributeRenderer from "../../services/attribute_renderer.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
|
||||
const HELP_TEXT = `
|
||||
@ -503,7 +502,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
|
||||
title: title
|
||||
});
|
||||
|
||||
return treeService.getSomeNotePath(note);
|
||||
return note.getBestNotePathString();
|
||||
}
|
||||
|
||||
async updateAttributeList(attributes) {
|
||||
|
@ -240,7 +240,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
|
||||
if (this.$content.find('span.math-tex').length > 0) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
||||
|
||||
renderMathInElement($content[0], {trust: true});
|
||||
renderMathInElement(this.$content[0], {trust: true});
|
||||
}
|
||||
} else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
|
||||
this.$content.html($("<pre>").text(fullNoteRevision.content));
|
||||
|
@ -1,7 +1,6 @@
|
||||
import linkService from '../../services/link.js';
|
||||
import utils from '../../services/utils.js';
|
||||
import server from '../../services/server.js';
|
||||
import treeService from "../../services/tree.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import hoistedNoteService from "../../services/hoisted_note.js";
|
||||
@ -108,7 +107,7 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
}
|
||||
} else {
|
||||
const note = await froca.getNote(change.noteId);
|
||||
const notePath = treeService.getSomeNotePath(note);
|
||||
const notePath = note.getBestNotePathString();
|
||||
|
||||
if (notePath) {
|
||||
$noteLink = await linkService.createNoteLink(notePath, {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import froca from "../services/froca.js";
|
||||
import server from "../services/server.js";
|
||||
|
||||
const TPL = `<div class="mermaid-widget">
|
||||
<style>
|
||||
@ -74,6 +73,8 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
|
||||
const wheelZoomLoaded = libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM);
|
||||
|
||||
this.$errorContainer.hide();
|
||||
|
||||
try {
|
||||
await this.renderSvg(async renderedSvg => {
|
||||
this.$display.html(renderedSvg);
|
||||
@ -88,8 +89,6 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
speed: 20,
|
||||
zoomOnClick: false
|
||||
});
|
||||
|
||||
this.$errorContainer.hide();
|
||||
});
|
||||
} catch (e) {
|
||||
this.$errorMessage.text(e.message);
|
||||
|
@ -212,7 +212,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$treeSettingsPopup.on("click", e => { e.stopPropagation(); });
|
||||
this.$treeSettingsPopup.on("click", e => {e.stopPropagation();});
|
||||
|
||||
$(document).on('click', () => this.$treeSettingsPopup.hide());
|
||||
|
||||
@ -239,12 +239,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
|
||||
const isEnclosing = ($container, $sub) => {
|
||||
const conOffset = $container.offset();
|
||||
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
|
||||
const conOffset = $container.offset();
|
||||
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
|
||||
const conDistanceFromLeft = conOffset.left + $container.outerWidth(true);
|
||||
|
||||
const subOffset = $sub.offset();
|
||||
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
|
||||
const subOffset = $sub.offset();
|
||||
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
|
||||
const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true);
|
||||
|
||||
return conDistanceFromTop > subDistanceFromTop
|
||||
@ -653,7 +653,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
return noteList;
|
||||
}
|
||||
|
||||
updateNode(node) {
|
||||
async updateNode(node) {
|
||||
const note = froca.getNoteFromCache(node.data.noteId);
|
||||
const branch = froca.getBranch(node.data.branchId);
|
||||
|
||||
@ -677,7 +677,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
node.title = utils.escapeHtml(title);
|
||||
|
||||
if (node.isExpanded() !== branch.isExpanded) {
|
||||
node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true});
|
||||
await node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true});
|
||||
}
|
||||
|
||||
node.renderTitle();
|
||||
@ -829,7 +829,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
await this.setExpandedStatusForSubtree(node, false);
|
||||
}
|
||||
|
||||
collapseTreeEvent() { this.collapseTree(); }
|
||||
collapseTreeEvent() {this.collapseTree();}
|
||||
|
||||
/**
|
||||
* @returns {FancytreeNode|null}
|
||||
@ -900,7 +900,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
if (expand) {
|
||||
await parentNode.setExpanded(true, {noAnimation: true});
|
||||
if (!parentNode.isExpanded()) {
|
||||
await parentNode.setExpanded(true, {noAnimation: true});
|
||||
}
|
||||
|
||||
// although previous line should set the expanded status, it seems to happen asynchronously,
|
||||
// so we need to make sure it is set properly before calling updateNode which uses this flag
|
||||
@ -908,7 +910,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
branch.isExpanded = true;
|
||||
}
|
||||
|
||||
this.updateNode(parentNode);
|
||||
await this.updateNode(parentNode);
|
||||
|
||||
let foundChildNode = this.findChildNode(parentNode, childNoteId);
|
||||
|
||||
@ -1076,10 +1078,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
const activeNode = this.getActiveNode();
|
||||
const activeNodeFocused = activeNode && activeNode.hasFocus();
|
||||
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
|
||||
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
let activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
|
||||
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
|
||||
const activeNoteId = activeNode ? activeNode.data.noteId : null;
|
||||
let activeNoteId = activeNode ? activeNode.data.noteId : null;
|
||||
|
||||
const noteIdsToUpdate = new Set();
|
||||
const noteIdsToReload = new Set();
|
||||
@ -1122,7 +1124,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
for (const ecBranch of loadResults.getBranches()) {
|
||||
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
|
||||
let movedActiveNode = null;
|
||||
let parentsOfAddedNodes = [];
|
||||
|
||||
const allBranches = loadResults.getBranches();
|
||||
const allBranchesDeleted = allBranches.every(branch => !!branch.isDeleted);
|
||||
|
||||
for (const ecBranch of allBranches) {
|
||||
if (ecBranch.parentNoteId === '_share') {
|
||||
// all shared notes have a sign in the tree, even the descendants of shared notes
|
||||
noteIdsToReload.add(ecBranch.noteId);
|
||||
@ -1135,12 +1144,16 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
for (const node of this.getNodesByBranch(ecBranch)) {
|
||||
if (ecBranch.isDeleted) {
|
||||
if (node.isActive()) {
|
||||
const newActiveNode = node.getNextSibling()
|
||||
|| node.getPrevSibling()
|
||||
|| node.getParent();
|
||||
if (allBranchesDeleted) {
|
||||
const newActiveNode = node.getNextSibling()
|
||||
|| node.getPrevSibling()
|
||||
|| node.getParent();
|
||||
|
||||
if (newActiveNode) {
|
||||
newActiveNode.setActive(true, {noEvents: true, noFocus: true});
|
||||
if (newActiveNode) {
|
||||
newActiveNode.setActive(true, {noEvents: true, noFocus: true});
|
||||
}
|
||||
} else {
|
||||
movedActiveNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1154,12 +1167,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
if (!ecBranch.isDeleted) {
|
||||
for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) {
|
||||
parentsOfAddedNodes.push(parentNode)
|
||||
|
||||
if (parentNode.isFolder() && !parentNode.isLoaded()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId);
|
||||
|
||||
if (!found) {
|
||||
// make sure it's loaded
|
||||
await froca.getNote(ecBranch.noteId);
|
||||
@ -1202,7 +1216,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
// for some reason node update cannot be in the batchUpdate() block (node is not re-rendered)
|
||||
for (const noteId of noteIdsToUpdate) {
|
||||
for (const node of this.getNodesByNoteId(noteId)) {
|
||||
this.updateNode(node);
|
||||
await this.updateNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (movedActiveNode) {
|
||||
for (const parentNode of parentsOfAddedNodes) {
|
||||
const found = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId);
|
||||
if (found) {
|
||||
activeNotePath = treeService.getNotePath(found);
|
||||
activeNoteId = found.data.noteId;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedNotePaths = this.note.getSortedNotePaths(this.hoistedNoteId)
|
||||
const sortedNotePaths = this.note.getSortedNotePathRecords(this.hoistedNoteId)
|
||||
.filter(notePath => !notePath.isHidden);
|
||||
|
||||
if (sortedNotePaths.length > 0) {
|
||||
|
@ -25,7 +25,7 @@ export default class SharedSwitchWidget extends SwitchWidget {
|
||||
}
|
||||
|
||||
async switchOn() {
|
||||
await branchService.cloneNoteToNote(this.noteId, '_share');
|
||||
await branchService.cloneNoteToParentNote(this.noteId, '_share');
|
||||
|
||||
syncService.syncNow(true);
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
|
||||
matchBrackets: true,
|
||||
keyMap: options.is('vimKeymapEnabled') ? "vim": "default",
|
||||
matchTags: {bothTags: true},
|
||||
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
|
||||
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
|
||||
lint: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
lineNumbers: true,
|
||||
@ -62,7 +62,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
|
||||
// all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
|
||||
lineWrapping: options.is('codeLineWrapEnabled'),
|
||||
dragDrop: false, // with true the editor inlines dropped files which is not what we expect
|
||||
placeholder: "Type the content of your code note here..."
|
||||
placeholder: "Type the content of your code note here...",
|
||||
});
|
||||
|
||||
this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate());
|
||||
|
@ -4,7 +4,6 @@ import mimeTypesService from '../../services/mime_types.js';
|
||||
import utils from "../../services/utils.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
|
||||
import link from "../../services/link.js";
|
||||
@ -378,7 +377,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
return treeService.getSomeNotePath(resp.note);
|
||||
return resp.note.getBestNotePathString();
|
||||
}
|
||||
|
||||
async refreshIncludedNoteEvent({noteId}) {
|
||||
|
@ -9,11 +9,11 @@ function cloneNoteToBranch(req) {
|
||||
return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix);
|
||||
}
|
||||
|
||||
function cloneNoteToNote(req) {
|
||||
function cloneNoteToParentNote(req) {
|
||||
const {noteId, parentNoteId} = req.params;
|
||||
const {prefix} = req.body;
|
||||
|
||||
return cloningService.cloneNoteToNote(noteId, parentNoteId, prefix);
|
||||
return cloningService.cloneNoteToParentNote(noteId, parentNoteId, prefix);
|
||||
}
|
||||
|
||||
function cloneNoteAfter(req) {
|
||||
@ -30,7 +30,7 @@ function toggleNoteInParent(req) {
|
||||
|
||||
module.exports = {
|
||||
cloneNoteToBranch,
|
||||
cloneNoteToNote,
|
||||
cloneNoteToParentNote,
|
||||
cloneNoteAfter,
|
||||
toggleNoteInParent
|
||||
};
|
||||
|
@ -129,7 +129,7 @@ function getEditedNotesOnDate(req) {
|
||||
notes = notes.map(note => note.getPojo());
|
||||
|
||||
for (const note of notes) {
|
||||
const notePath = note.isDeleted ? null : beccaService.getNotePath(note.noteId);
|
||||
const notePath = note.isDeleted ? null : getNotePathData(note);
|
||||
|
||||
note.notePath = notePath ? notePath.notePath : null;
|
||||
}
|
||||
@ -137,6 +137,32 @@ function getEditedNotesOnDate(req) {
|
||||
return notes;
|
||||
}
|
||||
|
||||
function getNotePathData(note) {
|
||||
const retPath = note.getBestNotePath();
|
||||
|
||||
if (retPath) {
|
||||
const noteTitle = beccaService.getNoteTitleForPath(retPath);
|
||||
|
||||
let branchId;
|
||||
|
||||
if (note.isRoot()) {
|
||||
branchId = 'none_root';
|
||||
}
|
||||
else {
|
||||
const parentNote = note.parents[0];
|
||||
branchId = becca.getBranchFromChildAndParent(note.noteId, parentNote.noteId).branchId;
|
||||
}
|
||||
|
||||
return {
|
||||
noteId: note.noteId,
|
||||
branchId: branchId,
|
||||
title: noteTitle,
|
||||
notePath: retPath,
|
||||
path: retPath.join('/')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNoteRevisions,
|
||||
getNoteRevision,
|
||||
|
@ -3,14 +3,14 @@
|
||||
const sql = require('../../services/sql');
|
||||
const protectedSessionService = require('../../services/protected_session');
|
||||
const noteService = require('../../services/notes');
|
||||
const beccaService = require('../../becca/becca_service');
|
||||
const becca = require("../../becca/becca");
|
||||
|
||||
function getRecentChanges(req) {
|
||||
const {ancestorNoteId} = req.params;
|
||||
|
||||
let recentChanges = [];
|
||||
|
||||
const noteRevisions = sql.getRows(`
|
||||
const noteRevisionRows = sql.getRows(`
|
||||
SELECT
|
||||
notes.noteId,
|
||||
notes.isDeleted AS current_isDeleted,
|
||||
@ -24,16 +24,18 @@ function getRecentChanges(req) {
|
||||
note_revisions
|
||||
JOIN notes USING(noteId)`);
|
||||
|
||||
for (const noteRevision of noteRevisions) {
|
||||
if (beccaService.isInAncestor(noteRevision.noteId, ancestorNoteId)) {
|
||||
recentChanges.push(noteRevision);
|
||||
for (const noteRevisionRow of noteRevisionRows) {
|
||||
const note = becca.getNote(noteRevisionRow.noteId);
|
||||
|
||||
if (note?.hasAncestor(ancestorNoteId)) {
|
||||
recentChanges.push(noteRevisionRow);
|
||||
}
|
||||
}
|
||||
|
||||
// now we need to also collect date points not represented in note revisions:
|
||||
// 1. creation for all notes (dateCreated)
|
||||
// 2. deletion for deleted notes (dateModified)
|
||||
const notes = sql.getRows(`
|
||||
const noteRows = sql.getRows(`
|
||||
SELECT
|
||||
notes.noteId,
|
||||
notes.isDeleted AS current_isDeleted,
|
||||
@ -57,9 +59,11 @@ function getRecentChanges(req) {
|
||||
FROM notes
|
||||
WHERE notes.isDeleted = 1`);
|
||||
|
||||
for (const note of notes) {
|
||||
if (beccaService.isInAncestor(note.noteId, ancestorNoteId)) {
|
||||
recentChanges.push(note);
|
||||
for (const noteRow of noteRows) {
|
||||
const note = becca.getNote(noteRow.noteId);
|
||||
|
||||
if (note?.hasAncestor(ancestorNoteId)) {
|
||||
recentChanges.push(noteRow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ function register(app) {
|
||||
apiRoute(PST, '/api/notes/:noteId/upload-modified-file', notesApiRoute.uploadModifiedFile);
|
||||
apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch);
|
||||
apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent);
|
||||
apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote);
|
||||
apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToParentNote);
|
||||
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
||||
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware],
|
||||
filesRoute.updateFile, apiResultHandler);
|
||||
|
@ -1 +1 @@
|
||||
module.exports = { buildDate:"", buildRevision: "9881e6de3e4966af39ec6245562dca6ac7b25eaa" };
|
||||
module.exports = { buildDate:"2023-04-17T21:40:35+02:00", buildRevision: "1d3272e9f8c27106a66227fbb580677ae5d70427" };
|
||||
|
@ -83,7 +83,7 @@ const ACTION_HANDLERS = {
|
||||
let res;
|
||||
|
||||
if (note.getParentBranches().length > 1) {
|
||||
res = cloningService.cloneNoteToNote(note.noteId, action.targetParentNoteId);
|
||||
res = cloningService.cloneNoteToParentNote(note.noteId, action.targetParentNoteId);
|
||||
}
|
||||
else {
|
||||
res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId);
|
||||
|
@ -8,7 +8,7 @@ const becca = require("../becca/becca");
|
||||
const beccaService = require("../becca/becca_service");
|
||||
const log = require("./log");
|
||||
|
||||
function cloneNoteToNote(noteId, parentNoteId, prefix) {
|
||||
function cloneNoteToParentNote(noteId, parentNoteId, prefix) {
|
||||
const parentNote = becca.getNote(parentNoteId);
|
||||
|
||||
if (parentNote.type === 'search') {
|
||||
@ -19,7 +19,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) {
|
||||
}
|
||||
|
||||
if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) {
|
||||
return { success: false, message: 'Note is deleted.' };
|
||||
return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' };
|
||||
}
|
||||
|
||||
const validationResult = treeService.validateParentChild(parentNoteId, noteId);
|
||||
@ -35,12 +35,12 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) {
|
||||
isExpanded: 0
|
||||
}).save();
|
||||
|
||||
log.info(`Cloned note '${noteId}' to new parent note '${parentNoteId}' with prefix '${prefix}'`);
|
||||
log.info(`Cloned note '${noteId}' to a new parent note '${parentNoteId}' with prefix '${prefix}'`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
branchId: branch.branchId,
|
||||
notePath: `${beccaService.getNotePath(parentNoteId).path}/${noteId}`
|
||||
notePath: `${parentNote.getBestNotePathString()}/${noteId}`
|
||||
};
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) {
|
||||
return { success: false, message: `Parent branch ${parentBranchId} does not exist.` };
|
||||
}
|
||||
|
||||
const ret = cloneNoteToNote(noteId, parentBranch.noteId, prefix);
|
||||
const ret = cloneNoteToParentNote(noteId, parentBranch.noteId, prefix);
|
||||
|
||||
parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user
|
||||
parentBranch.save();
|
||||
@ -182,7 +182,7 @@ function isNoteDeleted(noteId) {
|
||||
|
||||
module.exports = {
|
||||
cloneNoteToBranch,
|
||||
cloneNoteToNote,
|
||||
cloneNoteToParentNote,
|
||||
ensureNoteIsPresentInParent,
|
||||
ensureNoteIsAbsentFromParent,
|
||||
toggleNoteInParent,
|
||||
|
@ -20,6 +20,7 @@ const dayjs = require("dayjs");
|
||||
const htmlSanitizer = require("./html_sanitizer");
|
||||
const ValidationError = require("../errors/validation_error");
|
||||
const noteTypesService = require("./note_types");
|
||||
const fs = require("fs");
|
||||
|
||||
/** @param {BNote} parentNote */
|
||||
function getNewNotePosition(parentNote) {
|
||||
@ -395,7 +396,24 @@ const imageUrlToAttachmentIdMapping = {};
|
||||
|
||||
async function downloadImage(noteId, imageUrl) {
|
||||
try {
|
||||
const imageBuffer = await request.getImage(imageUrl);
|
||||
let imageBuffer;
|
||||
|
||||
if (imageUrl.toLowerCase().startsWith("file://")) {
|
||||
imageBuffer = await new Promise((res, rej) => {
|
||||
const localFilePath = imageUrl.substr("file://".length);
|
||||
|
||||
return fs.readFile(localFilePath, (err, data) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
} else {
|
||||
res(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
imageBuffer = await request.getImage(imageUrl);
|
||||
}
|
||||
|
||||
const parsedUrl = url.parse(imageUrl);
|
||||
const title = path.basename(parsedUrl.pathname);
|
||||
|
||||
|
@ -22,9 +22,9 @@ class NoteFlatTextExp extends Expression {
|
||||
* @param {string[]} tokens
|
||||
* @param {string[]} path
|
||||
*/
|
||||
function searchDownThePath(note, tokens, path) {
|
||||
const searchDownThePath = (note, tokens, path) => {
|
||||
if (tokens.length === 0) {
|
||||
const retPath = beccaService.getSomePath(note, path);
|
||||
const retPath = this.getNotePath(note, path);
|
||||
|
||||
if (retPath) {
|
||||
const noteId = retPath[retPath.length - 1];
|
||||
@ -131,6 +131,17 @@ class NoteFlatTextExp extends Expression {
|
||||
return resultNoteSet;
|
||||
}
|
||||
|
||||
getNotePath(note, path) {
|
||||
if (path.length === 0) {
|
||||
return note.getBestNotePath();
|
||||
} else {
|
||||
const closestNoteId = path[0];
|
||||
const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePath();
|
||||
|
||||
return [...closestNoteBestNotePath, ...path.slice(1)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns noteIds which have at least one matching tokens
|
||||
*
|
||||
|
@ -157,7 +157,7 @@ function findResultsWithExpression(expression, searchContext) {
|
||||
const searchResults = noteSet.notes
|
||||
.filter(note => !note.isDeleted)
|
||||
.map(note => {
|
||||
const notePathArray = executionContext.noteIdToNotePath[note.noteId] || beccaService.getSomePath(note);
|
||||
const notePathArray = executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath();
|
||||
|
||||
if (!notePathArray) {
|
||||
throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`);
|
||||
|
@ -9,6 +9,18 @@ const protectedSessionService = require('./protected_session');
|
||||
const becca = require("../becca/becca");
|
||||
const AbstractBeccaEntity = require("../becca/entities/abstract_becca_entity");
|
||||
|
||||
const env = require('./env');
|
||||
if (env.isDev()) {
|
||||
const chokidar = require('chokidar');
|
||||
const debounce = require('debounce');
|
||||
const debouncedReloadFrontend = debounce(reloadFrontend, 200);
|
||||
chokidar
|
||||
.watch('src/public')
|
||||
.on('add', debouncedReloadFrontend)
|
||||
.on('change', debouncedReloadFrontend)
|
||||
.on('unlink', debouncedReloadFrontend);
|
||||
}
|
||||
|
||||
let webSocketServer;
|
||||
let lastSyncedPush = null;
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
const path = require('path');
|
||||
const assetPath = require('./src/services/asset_path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
mobile: './src/public/app/desktop.js',
|
||||
},
|
||||
output: {
|
||||
publicPath: `${assetPath}/app-dist/`,
|
||||
path: path.resolve(__dirname, 'src/public/app-dist'),
|
||||
filename: 'desktop.js'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
target: 'electron-renderer'
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
const path = require('path');
|
||||
const assetPath = require('./src/services/asset_path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
mobile: './src/public/app/setup.js',
|
||||
},
|
||||
output: {
|
||||
publicPath: `${assetPath}/app-dist/`,
|
||||
path: path.resolve(__dirname, 'src/public/app-dist'),
|
||||
filename: 'setup.js'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
target: 'electron-renderer'
|
||||
};
|
@ -4,13 +4,15 @@ const assetPath = require('./src/services/asset_path');
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
setup: './src/public/app/setup.js',
|
||||
mobile: './src/public/app/mobile.js',
|
||||
desktop: './src/public/app/desktop.js',
|
||||
},
|
||||
output: {
|
||||
publicPath: `${assetPath}/app-dist/`,
|
||||
path: path.resolve(__dirname, 'src/public/app-dist'),
|
||||
filename: 'mobile.js'
|
||||
filename: '[name].js',
|
||||
},
|
||||
devtool: 'source-map',
|
||||
target: 'electron-renderer'
|
||||
target: 'electron-renderer',
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user