mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'feature/typescript_backend_3' into feature/typescript_backend_4
This commit is contained in:
commit
0daa4cc89a
@ -1,7 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
bin
|
|
||||||
docs
|
|
||||||
libraries
|
|
||||||
coverage
|
|
||||||
play
|
|
212
.eslintrc.js
212
.eslintrc.js
@ -1,212 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
commonjs: true,
|
|
||||||
es2021: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
// 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' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
globals: {
|
|
||||||
$: true,
|
|
||||||
jQuery: true,
|
|
||||||
glob: true,
|
|
||||||
log: true,
|
|
||||||
EditorWatchdog: true,
|
|
||||||
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,
|
|
||||||
FancytreeNode: 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\content_renderer.js
|
|
||||||
mermaid: true,
|
|
||||||
// src\public\app\services\frontend_script_api.js
|
|
||||||
dayjs: 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,
|
|
||||||
describe: true,
|
|
||||||
it: true,
|
|
||||||
expect: true
|
|
||||||
},
|
|
||||||
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
.husky/.gitignore
vendored
1
.husky/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
_
|
|
@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
#npx lint-staged
|
|
6
.idea/jsLinters/eslint.xml
generated
6
.idea/jsLinters/eslint.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="EslintConfiguration">
|
|
||||||
<option name="fix-on-save" value="true" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,13 +0,0 @@
|
|||||||
//https://prettier.io/docs/en/options.html
|
|
||||||
module.exports = {
|
|
||||||
semi: true,
|
|
||||||
trailingComma: 'none',
|
|
||||||
singleQuote: true,
|
|
||||||
printWidth: 100,
|
|
||||||
tabWidth: 4,
|
|
||||||
useTabs: false,
|
|
||||||
quoteProps: "as-needed",
|
|
||||||
bracketSpacing: true,
|
|
||||||
arrowParens: "avoid"
|
|
||||||
// htmlWhitespaceSensitivity: 'ignore',
|
|
||||||
};
|
|
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode",
|
|
||||||
]
|
|
||||||
}
|
|
33
.vscode/settings.json
vendored
33
.vscode/settings.json
vendored
@ -1,34 +1,5 @@
|
|||||||
{
|
{
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
|
||||||
},
|
|
||||||
"[json]": {
|
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": true,
|
"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",
|
"files.eol": "\n",
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
}
|
}
|
||||||
|
@ -2,49 +2,37 @@
|
|||||||
|
|
||||||
SRC_DIR=./dist/trilium-linux-x64-src
|
SRC_DIR=./dist/trilium-linux-x64-src
|
||||||
|
|
||||||
if [ "$1" != "DONTCOPY" ]
|
[ "$1" != "DONTCOPY" ] && ./bin/copy-trilium.sh "$SRC_DIR"
|
||||||
then
|
|
||||||
./bin/copy-trilium.sh $SRC_DIR
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
rm -r "$SRC_DIR"/src/public/app-dist/*.mobile.*
|
||||||
|
|
||||||
echo "Copying required linux-x64 binaries"
|
echo "Copying required linux-x64 binaries"
|
||||||
|
cp -r bin/better-sqlite3/linux-desktop-better_sqlite3.node "$SRC_DIR"/node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
||||||
cp -r bin/better-sqlite3/linux-desktop-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
|
||||||
|
|
||||||
echo "Packaging linux x64 electron build"
|
echo "Packaging linux x64 electron build"
|
||||||
|
./node_modules/.bin/electron-packager "$SRC_DIR" --asar --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
|
||||||
./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
|
|
||||||
|
|
||||||
BUILD_DIR=./dist/trilium-linux-x64
|
BUILD_DIR=./dist/trilium-linux-x64
|
||||||
rm -rf $BUILD_DIR
|
rm -rf "$BUILD_DIR"
|
||||||
|
|
||||||
mv "./dist/Trilium Notes-linux-x64" $BUILD_DIR
|
mv "./dist/Trilium Notes-linux-x64" "$BUILD_DIR"
|
||||||
|
|
||||||
cp images/app-icons/png/128x128.png $BUILD_DIR/icon.png
|
cp images/app-icons/png/128x128.png "$BUILD_DIR"/icon.png
|
||||||
|
cp bin/tpl/anonymize-database.sql "$BUILD_DIR"/
|
||||||
|
|
||||||
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
|
cp -r dump-db "$BUILD_DIR"/
|
||||||
|
rm -rf "$BUILD_DIR"/dump-db/node_modules
|
||||||
|
|
||||||
cp -r dump-db $BUILD_DIR/
|
for f in 'trilium-portable' 'trilium-safe-mode' 'trilium-no-cert-check'; do
|
||||||
rm -rf $BUILD_DIR/dump-db/node_modules
|
cp bin/tpl/"$f".sh "$BUILD_DIR"/
|
||||||
|
chmod 755 "$BUILD_DIR"/"$f".sh
|
||||||
cp bin/tpl/trilium-portable.sh $BUILD_DIR/
|
done
|
||||||
chmod 755 $BUILD_DIR/trilium-portable.sh
|
|
||||||
|
|
||||||
cp bin/tpl/trilium-safe-mode.sh $BUILD_DIR/
|
|
||||||
chmod 755 $BUILD_DIR/trilium-safe-mode.sh
|
|
||||||
|
|
||||||
cp bin/tpl/trilium-no-cert-check.sh $BUILD_DIR/
|
|
||||||
chmod 755 $BUILD_DIR/trilium-no-cert-check.sh
|
|
||||||
|
|
||||||
echo "Packaging linux x64 electron distribution..."
|
echo "Packaging linux x64 electron distribution..."
|
||||||
VERSION=`jq -r ".version" package.json`
|
VERSION=`jq -r ".version" package.json`
|
||||||
|
|
||||||
cd dist
|
pushd dist
|
||||||
|
tar cJf "trilium-linux-x64-${VERSION}.tar.xz" trilium-linux-x64
|
||||||
tar cJf trilium-linux-x64-${VERSION}.tar.xz trilium-linux-x64
|
popd
|
||||||
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
bin/build-debian.sh
|
bin/build-debian.sh
|
||||||
|
@ -4,47 +4,49 @@ if [[ $# -eq 0 ]] ; then
|
|||||||
echo "Missing argument of target directory"
|
echo "Missing argument of target directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if ! [[ $(which npm) ]]; then
|
||||||
|
echo "Missing npm"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
n exec 18.18.2 npm run webpack
|
n exec 18.18.2 npm run webpack || npm run webpack
|
||||||
|
|
||||||
DIR=$1
|
DIR="$1"
|
||||||
|
|
||||||
rm -rf $DIR
|
rm -rf "$DIR"
|
||||||
mkdir $DIR
|
mkdir -pv "$DIR"
|
||||||
|
|
||||||
echo "Copying Trilium to build directory $DIR"
|
echo "Copying Trilium to build directory $DIR"
|
||||||
|
|
||||||
cp -r images $DIR/
|
for d in 'images' 'libraries' 'src' 'db'; do
|
||||||
cp -r libraries $DIR/
|
cp -r "$d" "$DIR"/
|
||||||
cp -r src $DIR/
|
done
|
||||||
cp -r db $DIR/
|
for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample.ini' 'electron.js'; do
|
||||||
cp -r package.json $DIR/
|
cp "$f" "$DIR"/
|
||||||
cp -r package-lock.json $DIR/
|
done
|
||||||
cp -r README.md $DIR/
|
cp webpack-* "$DIR"/ # here warning because there is no 'webpack-*', but webpack.config.js only
|
||||||
cp -r LICENSE $DIR/
|
|
||||||
cp -r config-sample.ini $DIR/
|
|
||||||
cp -r electron.js $DIR/
|
|
||||||
cp webpack-* $DIR/
|
|
||||||
|
|
||||||
# run in subshell (so we return to original dir)
|
# run in subshell (so we return to original dir)
|
||||||
(cd $DIR && n exec 18.18.2 npm install --only=prod)
|
(cd $DIR && n exec 18.18.2 npm install --only=prod)
|
||||||
|
|
||||||
|
if [[ -d "$DIR"/node_modules ]]; then
|
||||||
# cleanup of useless files in dependencies
|
# cleanup of useless files in dependencies
|
||||||
rm -r $DIR/node_modules/image-q/demo
|
for d in 'image-q/demo' 'better-sqlite3/Release' 'better-sqlite3/deps/sqlite3.tar.gz' '@jimp/plugin-print/fonts' 'jimp/browser' 'jimp/fonts'; do
|
||||||
rm -r $DIR/node_modules/better-sqlite3/Release
|
[[ -e "$DIR"/node_modules/"$d" ]] && rm -rv "$DIR"/node_modules/"$d"
|
||||||
rm -r $DIR/node_modules/better-sqlite3/deps/sqlite3.tar.gz
|
done
|
||||||
rm -r $DIR/node_modules/@jimp/plugin-print/fonts
|
|
||||||
rm -r $DIR/node_modules/jimp/browser
|
|
||||||
rm -r $DIR/node_modules/jimp/fonts
|
|
||||||
|
|
||||||
# delete all tests (there are often large images as test file for jimp etc.)
|
# delete all tests (there are often large images as test file for jimp etc.)
|
||||||
find $DIR/node_modules -name test -exec rm -rf {} \;
|
for d in 'test' 'docs' 'demo'; do
|
||||||
find $DIR/node_modules -name docs -exec rm -rf {} \;
|
find "$DIR"/node_modules -name "$d" -exec rm -rf {} \;
|
||||||
find $DIR/node_modules -name demo -exec rm -rf {} \;
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
find $DIR/libraries -name "*.map" -type f -delete
|
find $DIR/libraries -name "*.map" -type f -delete
|
||||||
|
|
||||||
cp $DIR/src/public/app/share.js $DIR/src/public/app-dist/
|
d="$DIR"/src/public
|
||||||
cp -r $DIR/src/public/app/doc_notes $DIR/src/public/app-dist/
|
[[ -d "$d"/app-dist ]] || mkdir -pv "$d"/app-dist
|
||||||
|
cp "$d"/app/share.js "$d"/app-dist/
|
||||||
|
cp -r "$d"/app/doc_notes "$d"/app-dist/
|
||||||
|
|
||||||
rm -rf $DIR/src/public/app
|
rm -rf "$d"/app
|
||||||
|
unset f d DIR
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
const beccaLoader = require('../../src/becca/becca_loader');
|
const beccaLoader = require('../../src/becca/becca_loader');
|
||||||
const becca = require('../../src/becca/becca.js');
|
const becca = require('../../src/becca/becca');
|
||||||
const cls = require('../../src/services/cls');
|
const cls = require('../../src/services/cls');
|
||||||
const log = require('../../src/services/log');
|
const log = require('../../src/services/log');
|
||||||
const sql = require('../../src/services/sql');
|
const sql = require('../../src/services/sql');
|
||||||
@ -13,7 +13,7 @@ module.exports = () => {
|
|||||||
|
|
||||||
for (const note of Object.values(becca.notes)) {
|
for (const note of Object.values(becca.notes)) {
|
||||||
try {
|
try {
|
||||||
const attachment = note.convertToParentAttachment({autoConversion: true});
|
const attachment = note.convertToParentAttachment({ autoConversion: true });
|
||||||
|
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`);
|
log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`);
|
||||||
|
@ -10,7 +10,7 @@ if (config.Network.https) {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = require('./src/services/port.ts');
|
const port = require('./src/services/port');
|
||||||
const host = require('./src/services/host');
|
const host = require('./src/services/host');
|
||||||
|
|
||||||
const options = { timeout: 2000 };
|
const options = { timeout: 2000 };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const sanitize = require("sanitize-filename");
|
const sanitize = require("sanitize-filename");
|
||||||
const sql = require('./sql');
|
const sql = require('./sql.js');
|
||||||
const decryptService = require('./decrypt.js');
|
const decryptService = require('./decrypt.js');
|
||||||
const dataKeyService = require('./data_key.js');
|
const dataKeyService = require('./data_key.js');
|
||||||
const extensionService = require('./extension.js');
|
const extensionService = require('./extension.js');
|
||||||
@ -74,7 +74,7 @@ function dumpDocument(documentPath, targetPath, options) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
|
let { content } = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
|
||||||
|
|
||||||
if (content !== null && noteRow.isProtected && dataKey) {
|
if (content !== null && noteRow.isProtected && dataKey) {
|
||||||
content = decryptService.decrypt(dataKey, content);
|
content = decryptService.decrypt(dataKey, content);
|
||||||
@ -108,7 +108,7 @@ function dumpDocument(documentPath, targetPath, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(childTargetPath, {recursive: true});
|
fs.mkdirSync(childTargetPath, { recursive: true });
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(`DUMPERROR: Creating directory ${childTargetPath} failed with error '${e.message}'`);
|
console.error(`DUMPERROR: Creating directory ${childTargetPath} failed with error '${e.message}'`);
|
||||||
@ -157,7 +157,7 @@ function validatePaths(documentPath, targetPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(targetPath)) {
|
if (!fs.existsSync(targetPath)) {
|
||||||
const ret = fs.mkdirSync(targetPath, {recursive: true});
|
const ret = fs.mkdirSync(targetPath, { recursive: true });
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
console.error(`Target path '${targetPath}' could not be created. Run with --help to see usage.`);
|
console.error(`Target path '${targetPath}' could not be created. Run with --help to see usage.`);
|
||||||
|
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7293
package-lock.json
generated
7293
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
77
package.json
77
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.63.1-beta",
|
"version": "0.63.5",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -31,21 +31,18 @@
|
|||||||
"test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test jasmine",
|
"test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test jasmine",
|
||||||
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
|
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
|
||||||
"test": "npm run test-jasmine && npm run test-es6",
|
"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 || echo 'Husky install failed, expected on flatpak build'"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "6.0.4",
|
"@braintree/sanitize-url": "6.0.4",
|
||||||
"@electron/remote": "2.1.0",
|
"@electron/remote": "2.1.2",
|
||||||
"@excalidraw/excalidraw": "0.16.1",
|
"@excalidraw/excalidraw": "0.17.3",
|
||||||
"@types/cls-hooked": "^4.3.8",
|
"archiver": "7.0.0",
|
||||||
"archiver": "6.0.1",
|
"async-mutex": "0.4.1",
|
||||||
"async-mutex": "0.4.0",
|
"axios": "1.6.7",
|
||||||
"axios": "1.6.2",
|
|
||||||
"better-sqlite3": "8.4.0",
|
"better-sqlite3": "8.4.0",
|
||||||
"boxicons": "2.1.4",
|
"boxicons": "2.1.4",
|
||||||
"chokidar": "3.5.3",
|
"chokidar": "3.6.0",
|
||||||
"cls-hooked": "4.2.2",
|
"cls-hooked": "4.2.2",
|
||||||
"compression": "1.7.4",
|
"compression": "1.7.4",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.6",
|
||||||
@ -55,35 +52,35 @@
|
|||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"ejs": "3.1.9",
|
"ejs": "3.1.9",
|
||||||
"electron-debug": "3.2.0",
|
"electron-debug": "3.2.0",
|
||||||
"electron-dl": "3.5.1",
|
"electron-dl": "3.5.2",
|
||||||
"electron-window-state": "5.0.3",
|
"electron-window-state": "5.0.3",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"express": "4.18.2",
|
"express": "4.18.3",
|
||||||
"express-partial-content": "1.0.2",
|
"express-partial-content": "1.0.2",
|
||||||
"express-rate-limit": "7.1.4",
|
"express-rate-limit": "7.2.0",
|
||||||
"express-session": "1.17.3",
|
"express-session": "1.18.0",
|
||||||
"force-graph": "1.43.4",
|
"force-graph": "1.43.5",
|
||||||
"fs-extra": "11.1.1",
|
"fs-extra": "11.2.0",
|
||||||
"helmet": "7.1.0",
|
"helmet": "7.1.0",
|
||||||
"html": "1.0.0",
|
"html": "1.0.0",
|
||||||
"html2plaintext": "2.1.4",
|
"html2plaintext": "2.1.4",
|
||||||
"http-proxy-agent": "7.0.0",
|
"http-proxy-agent": "7.0.2",
|
||||||
"https-proxy-agent": "7.0.2",
|
"https-proxy-agent": "7.0.4",
|
||||||
"image-type": "4.1.0",
|
"image-type": "4.1.0",
|
||||||
"ini": "3.0.1",
|
"ini": "3.0.1",
|
||||||
"is-animated": "2.0.2",
|
"is-animated": "2.0.2",
|
||||||
"is-svg": "4.3.2",
|
"is-svg": "4.3.2",
|
||||||
"jimp": "0.22.10",
|
"jimp": "0.22.12",
|
||||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"jquery-hotkeys": "0.2.2",
|
"jquery-hotkeys": "0.2.2",
|
||||||
"jsdom": "22.1.0",
|
"jsdom": "24.0.0",
|
||||||
"katex": "0.16.9",
|
"katex": "0.16.9",
|
||||||
"marked": "9.1.6",
|
"marked": "12.0.0",
|
||||||
"mermaid": "10.6.1",
|
"mermaid": "10.9.0",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"node-abi": "3.51.0",
|
"node-abi": "3.56.0",
|
||||||
"normalize-strings": "1.1.1",
|
"normalize-strings": "1.1.1",
|
||||||
"open": "8.4.1",
|
"open": "8.4.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
@ -95,25 +92,26 @@
|
|||||||
"rimraf": "5.0.5",
|
"rimraf": "5.0.5",
|
||||||
"safe-compare": "1.1.4",
|
"safe-compare": "1.1.4",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.12.1",
|
||||||
"sax": "1.3.0",
|
"sax": "1.3.0",
|
||||||
"semver": "7.5.4",
|
"semver": "7.6.0",
|
||||||
"serve-favicon": "2.5.0",
|
"serve-favicon": "2.5.0",
|
||||||
"session-file-store": "1.5.0",
|
"session-file-store": "1.5.0",
|
||||||
"split.js": "1.6.5",
|
"split.js": "1.6.5",
|
||||||
"stream-throttle": "0.1.3",
|
"stream-throttle": "0.1.3",
|
||||||
"striptags": "3.2.0",
|
"striptags": "3.2.0",
|
||||||
"tmp": "0.2.1",
|
"tmp": "0.2.3",
|
||||||
"tree-kill": "1.2.2",
|
"tree-kill": "1.2.2",
|
||||||
"turndown": "7.1.2",
|
"turndown": "7.1.2",
|
||||||
"unescape": "1.0.1",
|
"unescape": "1.0.1",
|
||||||
"ws": "8.14.2",
|
"ws": "8.16.0",
|
||||||
"xml2js": "0.6.2",
|
"xml2js": "0.6.2",
|
||||||
"yauzl": "2.10.0"
|
"yauzl": "3.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/archiver": "^6.0.2",
|
"@types/archiver": "^6.0.2",
|
||||||
"@types/better-sqlite3": "^7.6.9",
|
"@types/better-sqlite3": "^7.6.9",
|
||||||
|
"@types/cls-hooked": "^4.3.8",
|
||||||
"@types/escape-html": "^1.0.4",
|
"@types/escape-html": "^1.0.4",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/html": "^1.0.4",
|
"@types/html": "^1.0.4",
|
||||||
@ -127,35 +125,22 @@
|
|||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"electron": "25.9.8",
|
"electron": "25.9.8",
|
||||||
"electron-builder": "24.6.4",
|
"electron-builder": "24.13.3",
|
||||||
"electron-packager": "17.1.2",
|
"electron-packager": "17.1.2",
|
||||||
"electron-rebuild": "3.2.9",
|
"electron-rebuild": "3.2.9",
|
||||||
"eslint": "8.54.0",
|
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
|
||||||
"eslint-config-prettier": "9.0.0",
|
|
||||||
"eslint-plugin-import": "2.29.0",
|
|
||||||
"eslint-plugin-jsonc": "2.10.0",
|
|
||||||
"eslint-plugin-prettier": "5.0.1",
|
|
||||||
"esm": "3.2.25",
|
"esm": "3.2.25",
|
||||||
"husky": "8.0.3",
|
|
||||||
"jasmine": "5.1.0",
|
"jasmine": "5.1.0",
|
||||||
"jsdoc": "4.0.2",
|
"jsdoc": "4.0.2",
|
||||||
"jsonc-eslint-parser": "2.4.0",
|
|
||||||
"lint-staged": "15.1.0",
|
|
||||||
"lorem-ipsum": "2.0.8",
|
"lorem-ipsum": "2.0.8",
|
||||||
"nodemon": "3.0.1",
|
"nodemon": "3.1.0",
|
||||||
"prettier": "3.1.0",
|
|
||||||
"rcedit": "4.0.1",
|
"rcedit": "4.0.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"webpack": "5.89.0",
|
"webpack": "5.90.3",
|
||||||
"webpack-cli": "5.1.4"
|
"webpack-cli": "5.1.4"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"electron-installer-debian": "3.2.0"
|
"electron-installer-debian": "3.2.0"
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*.js": "eslint --cache --fix"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ import BAttachment = require('./entities/battachment');
|
|||||||
import { AttachmentRow, RevisionRow } from './entities/rows';
|
import { AttachmentRow, RevisionRow } from './entities/rows';
|
||||||
import BBlob = require('./entities/bblob');
|
import BBlob = require('./entities/bblob');
|
||||||
import BRecentNote = require('./entities/brecent_note');
|
import BRecentNote = require('./entities/brecent_note');
|
||||||
|
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
|
||||||
|
|
||||||
interface AttachmentOpts {
|
interface AttachmentOpts {
|
||||||
includeContentLength?: boolean;
|
includeContentLength?: boolean;
|
||||||
@ -20,7 +21,7 @@ interface AttachmentOpts {
|
|||||||
* Becca is a backend cache of all notes, branches, and attributes.
|
* Becca is a backend cache of all notes, branches, and attributes.
|
||||||
* There's a similar frontend cache Froca, and share cache Shaca.
|
* There's a similar frontend cache Froca, and share cache Shaca.
|
||||||
*/
|
*/
|
||||||
class Becca {
|
export default class Becca {
|
||||||
loaded!: boolean;
|
loaded!: boolean;
|
||||||
|
|
||||||
notes!: Record<string, BNote>;
|
notes!: Record<string, BNote>;
|
||||||
@ -190,7 +191,11 @@ class Becca {
|
|||||||
.map(row => new BAttachment(row));
|
.map(row => new BAttachment(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlob(entity: { blobId: string }): BBlob | null {
|
getBlob(entity: { blobId?: string }): BBlob | null {
|
||||||
|
if (!entity.blobId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
|
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
|
||||||
|
|
||||||
const BBlob = require('./entities/bblob'); // avoiding circular dependency problems
|
const BBlob = require('./entities/bblob'); // avoiding circular dependency problems
|
||||||
@ -209,8 +214,7 @@ class Becca {
|
|||||||
return this.etapiTokens[etapiTokenId];
|
return this.etapiTokens[etapiTokenId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {AbstractBeccaEntity|null} */
|
getEntity<T extends AbstractBeccaEntity<T>>(entityName: string, entityId: string): AbstractBeccaEntity<T> | null {
|
||||||
getEntity(entityName: string, entityId: string) {
|
|
||||||
if (!entityName || !entityId) {
|
if (!entityName || !entityId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -276,4 +280,12 @@ class Becca {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = Becca;
|
/**
|
||||||
|
* This interface contains the data that is shared across all the objects of a given derived class of {@link AbstractBeccaEntity}.
|
||||||
|
* For example, all BAttributes will share their content, but all BBranches will have another set of this data.
|
||||||
|
*/
|
||||||
|
export interface ConstructorData<T extends AbstractBeccaEntity<T>> {
|
||||||
|
primaryKeyName: string;
|
||||||
|
entityName: string;
|
||||||
|
hashedProperties: (keyof T)[];
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import Becca = require("./becca-interface");
|
import Becca from "./becca-interface";
|
||||||
|
|
||||||
const becca = new Becca();
|
const becca = new Becca();
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import BEtapiToken = require('./entities/betapi_token');
|
|||||||
import cls = require('../services/cls');
|
import cls = require('../services/cls');
|
||||||
import entityConstructor = require('../becca/entity_constructor');
|
import entityConstructor = require('../becca/entity_constructor');
|
||||||
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows';
|
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows';
|
||||||
|
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
|
||||||
|
|
||||||
const beccaLoaded = new Promise<void>((res, rej) => {
|
const beccaLoaded = new Promise<void>((res, rej) => {
|
||||||
sqlInit.dbReady.then(() => {
|
sqlInit.dbReady.then(() => {
|
||||||
@ -75,7 +76,7 @@ function reload(reason: string) {
|
|||||||
require('../services/ws').reloadFrontend(reason || "becca reloaded");
|
require('../services/ws').reloadFrontend(reason || "becca reloaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
|
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({ entityName, entityRow }) => {
|
||||||
if (!becca.loaded) {
|
if (!becca.loaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -89,7 +90,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
|
|||||||
if (beccaEntity) {
|
if (beccaEntity) {
|
||||||
beccaEntity.updateFromRow(entityRow);
|
beccaEntity.updateFromRow(entityRow);
|
||||||
} else {
|
} else {
|
||||||
beccaEntity = new EntityClass();
|
beccaEntity = new EntityClass() as AbstractBeccaEntity<AbstractBeccaEntity<any>>;
|
||||||
beccaEntity.updateFromRow(entityRow);
|
beccaEntity.updateFromRow(entityRow);
|
||||||
beccaEntity.init();
|
beccaEntity.init();
|
||||||
}
|
}
|
||||||
@ -98,7 +99,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
|
|||||||
postProcessEntityUpdate(entityName, entityRow);
|
postProcessEntityUpdate(entityName, entityRow);
|
||||||
});
|
});
|
||||||
|
|
||||||
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
|
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
|
||||||
if (!becca.loaded) {
|
if (!becca.loaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -125,7 +126,7 @@ function postProcessEntityUpdate(entityName: string, entityRow: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
|
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({ entityName, entityId }) => {
|
||||||
if (!becca.loaded) {
|
if (!becca.loaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import cls = require('../../services/cls');
|
|||||||
import log = require('../../services/log');
|
import log = require('../../services/log');
|
||||||
import protectedSessionService = require('../../services/protected_session');
|
import protectedSessionService = require('../../services/protected_session');
|
||||||
import blobService = require('../../services/blob');
|
import blobService = require('../../services/blob');
|
||||||
import Becca = require('../becca-interface');
|
import Becca, { ConstructorData } from '../becca-interface';
|
||||||
|
|
||||||
let becca: Becca | null = null;
|
let becca: Becca | null = null;
|
||||||
|
|
||||||
@ -18,26 +18,22 @@ interface ContentOpts {
|
|||||||
forceFrontendReload?: boolean;
|
forceFrontendReload?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConstructorData<T extends AbstractBeccaEntity<T>> {
|
|
||||||
primaryKeyName: string;
|
|
||||||
entityName: string;
|
|
||||||
hashedProperties: (keyof T)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all backend entities.
|
* Base class for all backend entities.
|
||||||
|
*
|
||||||
|
* @type T the same entity type needed for self-reference in {@link ConstructorData}.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
||||||
|
|
||||||
utcDateModified?: string;
|
utcDateModified?: string;
|
||||||
protected dateCreated?: string;
|
protected dateCreated?: string;
|
||||||
protected dateModified?: string;
|
protected dateModified?: string;
|
||||||
protected isSynced?: boolean;
|
|
||||||
|
|
||||||
protected blobId?: string;
|
|
||||||
|
|
||||||
utcDateCreated!: string;
|
utcDateCreated!: string;
|
||||||
|
|
||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
|
isSynced?: boolean;
|
||||||
|
blobId?: string;
|
||||||
|
|
||||||
protected beforeSaving() {
|
protected beforeSaving() {
|
||||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||||
@ -46,7 +42,7 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getUtcDateChanged() {
|
getUtcDateChanged() {
|
||||||
return this.utcDateModified || this.utcDateCreated;
|
return this.utcDateModified || this.utcDateCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +66,7 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected generateHash(isDeleted: boolean): string {
|
generateHash(isDeleted?: boolean): string {
|
||||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||||
let contentToHash = "";
|
let contentToHash = "";
|
||||||
|
|
||||||
@ -96,6 +92,10 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
|||||||
|
|
||||||
abstract getPojo(): {};
|
abstract getPojo(): {};
|
||||||
|
|
||||||
|
abstract init(): void;
|
||||||
|
|
||||||
|
abstract updateFromRow(row: unknown): void;
|
||||||
|
|
||||||
get isDeleted(): boolean {
|
get isDeleted(): boolean {
|
||||||
// TODO: Not sure why some entities don't implement it.
|
// TODO: Not sure why some entities don't implement it.
|
||||||
return false;
|
return false;
|
||||||
|
@ -11,7 +11,8 @@ import BNote = require('./bnote');
|
|||||||
import BBranch = require('./bbranch');
|
import BBranch = require('./bbranch');
|
||||||
|
|
||||||
const attachmentRoleToNoteTypeMapping = {
|
const attachmentRoleToNoteTypeMapping = {
|
||||||
'image': 'image'
|
'image': 'image',
|
||||||
|
'file': 'file'
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ContentOpts {
|
interface ContentOpts {
|
||||||
@ -36,10 +37,10 @@ class BAttachment extends AbstractBeccaEntity<BAttachment> {
|
|||||||
noteId?: number;
|
noteId?: number;
|
||||||
attachmentId?: string;
|
attachmentId?: string;
|
||||||
/** either noteId or revisionId to which this attachment belongs */
|
/** either noteId or revisionId to which this attachment belongs */
|
||||||
ownerId: string;
|
ownerId!: string;
|
||||||
role: string;
|
role!: string;
|
||||||
mime: string;
|
mime!: string;
|
||||||
title: string;
|
title!: string;
|
||||||
type?: keyof typeof attachmentRoleToNoteTypeMapping;
|
type?: keyof typeof attachmentRoleToNoteTypeMapping;
|
||||||
position?: number;
|
position?: number;
|
||||||
blobId?: string;
|
blobId?: string;
|
||||||
@ -53,6 +54,11 @@ class BAttachment extends AbstractBeccaEntity<BAttachment> {
|
|||||||
constructor(row: AttachmentRow) {
|
constructor(row: AttachmentRow) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.updateFromRow(row);
|
||||||
|
this.decrypt();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromRow(row: AttachmentRow): void {
|
||||||
if (!row.ownerId?.trim()) {
|
if (!row.ownerId?.trim()) {
|
||||||
throw new Error("'ownerId' must be given to initialize a Attachment entity");
|
throw new Error("'ownerId' must be given to initialize a Attachment entity");
|
||||||
} else if (!row.role?.trim()) {
|
} else if (!row.role?.trim()) {
|
||||||
@ -75,8 +81,10 @@ class BAttachment extends AbstractBeccaEntity<BAttachment> {
|
|||||||
this.utcDateModified = row.utcDateModified;
|
this.utcDateModified = row.utcDateModified;
|
||||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||||
this.contentLength = row.contentLength;
|
this.contentLength = row.contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
this.decrypt();
|
init(): void {
|
||||||
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(): BAttachment {
|
copy(): BAttachment {
|
||||||
@ -130,7 +138,7 @@ class BAttachment extends AbstractBeccaEntity<BAttachment> {
|
|||||||
return this._getContent() as Buffer;
|
return this._getContent() as Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent(content: any, opts: ContentOpts) {
|
setContent(content: string | Buffer, opts: ContentOpts) {
|
||||||
this._setContent(content, opts);
|
this._setContent(content, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
|
import AbstractBeccaEntity = require("./abstract_becca_entity");
|
||||||
import { BlobRow } from "./rows";
|
import { BlobRow } from "./rows";
|
||||||
|
|
||||||
// TODO: Why this does not extend the abstract becca?
|
// TODO: Why this does not extend the abstract becca?
|
||||||
class BBlob {
|
class BBlob extends AbstractBeccaEntity<BBlob> {
|
||||||
static get entityName() { return "blobs"; }
|
static get entityName() { return "blobs"; }
|
||||||
static get primaryKeyName() { return "blobId"; }
|
static get primaryKeyName() { return "blobId"; }
|
||||||
static get hashedProperties() { return ["blobId", "content"]; }
|
static get hashedProperties() { return ["blobId", "content"]; }
|
||||||
|
|
||||||
blobId: string;
|
blobId!: string;
|
||||||
content: string | Buffer;
|
content!: string | Buffer;
|
||||||
contentLength: number;
|
contentLength!: number;
|
||||||
dateModified: string;
|
dateModified!: string;
|
||||||
utcDateModified: string;
|
utcDateModified!: string;
|
||||||
|
|
||||||
constructor(row: BlobRow) {
|
constructor(row: BlobRow) {
|
||||||
|
super();
|
||||||
|
this.updateFromRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromRow(row: BlobRow): void {
|
||||||
this.blobId = row.blobId;
|
this.blobId = row.blobId;
|
||||||
this.content = row.content;
|
this.content = row.content;
|
||||||
this.contentLength = row.contentLength;
|
this.contentLength = row.contentLength;
|
||||||
@ -20,6 +26,10 @@ class BBlob {
|
|||||||
this.utcDateModified = row.utcDateModified;
|
this.utcDateModified = row.utcDateModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
getPojo() {
|
getPojo() {
|
||||||
return {
|
return {
|
||||||
blobId: this.blobId,
|
blobId: this.blobId,
|
||||||
|
@ -1657,6 +1657,10 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
|||||||
position
|
position
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
throw new Error("Attempted to save an attachment with no content.");
|
||||||
|
}
|
||||||
|
|
||||||
attachment.setContent(content, {forceSave: true});
|
attachment.setContent(content, {forceSave: true});
|
||||||
|
|
||||||
return attachment;
|
return attachment;
|
||||||
|
@ -32,6 +32,10 @@ class BOption extends AbstractBeccaEntity<BOption> {
|
|||||||
this.utcDateModified = row.utcDateModified;
|
this.utcDateModified = row.utcDateModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(): void {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
super.beforeSaving();
|
||||||
|
|
||||||
|
@ -11,19 +11,28 @@ import AbstractBeccaEntity = require('./abstract_becca_entity');
|
|||||||
class BRecentNote extends AbstractBeccaEntity<BRecentNote> {
|
class BRecentNote extends AbstractBeccaEntity<BRecentNote> {
|
||||||
static get entityName() { return "recent_notes"; }
|
static get entityName() { return "recent_notes"; }
|
||||||
static get primaryKeyName() { return "noteId"; }
|
static get primaryKeyName() { return "noteId"; }
|
||||||
|
static get hashedProperties() { return ["noteId", "notePath"]; }
|
||||||
|
|
||||||
noteId: string;
|
noteId!: string;
|
||||||
notePath: string;
|
notePath!: string;
|
||||||
utcDateCreated: string;
|
utcDateCreated!: string;
|
||||||
|
|
||||||
constructor(row: RecentNoteRow) {
|
constructor(row: RecentNoteRow) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.updateFromRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromRow(row: RecentNoteRow): void {
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
this.notePath = row.notePath;
|
this.notePath = row.notePath;
|
||||||
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
|
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(): void {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
getPojo() {
|
getPojo() {
|
||||||
return {
|
return {
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
|
@ -29,22 +29,30 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
|
|||||||
"utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; }
|
"utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; }
|
||||||
|
|
||||||
revisionId?: string;
|
revisionId?: string;
|
||||||
noteId: string;
|
noteId!: string;
|
||||||
type: string;
|
type!: string;
|
||||||
mime: string;
|
mime!: string;
|
||||||
isProtected: boolean;
|
isProtected!: boolean;
|
||||||
title: string;
|
title!: string;
|
||||||
blobId?: string;
|
blobId?: string;
|
||||||
dateLastEdited?: string;
|
dateLastEdited?: string;
|
||||||
dateCreated: string;
|
dateCreated!: string;
|
||||||
utcDateLastEdited?: string;
|
utcDateLastEdited?: string;
|
||||||
utcDateCreated: string;
|
utcDateCreated!: string;
|
||||||
contentLength?: number;
|
contentLength?: number;
|
||||||
content?: string;
|
content?: string;
|
||||||
|
|
||||||
constructor(row: RevisionRow, titleDecrypted = false) {
|
constructor(row: RevisionRow, titleDecrypted = false) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.updateFromRow(row);
|
||||||
|
if (this.isProtected && !titleDecrypted) {
|
||||||
|
const decryptedTitle = protectedSessionService.isProtectedSessionAvailable() ? protectedSessionService.decryptString(this.title) : null;
|
||||||
|
this.title = decryptedTitle || "[protected]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromRow(row: RevisionRow) {
|
||||||
this.revisionId = row.revisionId;
|
this.revisionId = row.revisionId;
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
this.type = row.type;
|
this.type = row.type;
|
||||||
@ -58,11 +66,10 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
|
|||||||
this.utcDateCreated = row.utcDateCreated;
|
this.utcDateCreated = row.utcDateCreated;
|
||||||
this.utcDateModified = row.utcDateModified;
|
this.utcDateModified = row.utcDateModified;
|
||||||
this.contentLength = row.contentLength;
|
this.contentLength = row.contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isProtected && !titleDecrypted) {
|
init() {
|
||||||
const decryptedTitle = protectedSessionService.isProtectedSessionAvailable() ? protectedSessionService.decryptString(this.title) : null;
|
// Do nothing.
|
||||||
this.title = decryptedTitle || "[protected]";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNote() {
|
getNote() {
|
||||||
@ -115,7 +122,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent(content: any, opts: ContentOpts = {}) {
|
setContent(content: string | Buffer, opts: ContentOpts = {}) {
|
||||||
this._setContent(content, opts);
|
this._setContent(content, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +165,13 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
|
|||||||
return this.getAttachments().filter(attachment => attachment.title === title)[0];
|
return this.getAttachments().filter(attachment => attachment.title === title)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revisions are not soft-deletable, they are immediately hard-deleted (erased).
|
||||||
|
*/
|
||||||
|
eraseRevision() {
|
||||||
|
require("../../services/erase.js").eraseRevisions([this.revisionId]);
|
||||||
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
super.beforeSaving();
|
||||||
|
|
||||||
|
@ -108,7 +108,3 @@ export interface NoteRow {
|
|||||||
utcDateModified: string;
|
utcDateModified: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeRow {
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ConstructorData } from './becca-interface';
|
||||||
|
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
|
||||||
import BAttachment = require('./entities/battachment');
|
import BAttachment = require('./entities/battachment');
|
||||||
import BAttribute = require('./entities/battribute');
|
import BAttribute = require('./entities/battribute');
|
||||||
import BBlob = require('./entities/bblob');
|
import BBlob = require('./entities/bblob');
|
||||||
@ -8,7 +10,9 @@ import BOption = require('./entities/boption');
|
|||||||
import BRecentNote = require('./entities/brecent_note');
|
import BRecentNote = require('./entities/brecent_note');
|
||||||
import BRevision = require('./entities/brevision');
|
import BRevision = require('./entities/brevision');
|
||||||
|
|
||||||
const ENTITY_NAME_TO_ENTITY: Record<string, any> = {
|
type EntityClass = new (row?: any) => AbstractBeccaEntity<any>;
|
||||||
|
|
||||||
|
const ENTITY_NAME_TO_ENTITY: Record<string, ConstructorData<any> & EntityClass> = {
|
||||||
"attachments": BAttachment,
|
"attachments": BAttachment,
|
||||||
"attributes": BAttribute,
|
"attributes": BAttribute,
|
||||||
"blobs": BBlob,
|
"blobs": BBlob,
|
||||||
|
@ -43,7 +43,7 @@ interface DateLimits {
|
|||||||
function filterUrlValue(value: string) {
|
function filterUrlValue(value: string) {
|
||||||
return value
|
return value
|
||||||
.replace(/https?:\/\//ig, "")
|
.replace(/https?:\/\//ig, "")
|
||||||
.replace(/www\./ig, "")
|
.replace(/www.js\./ig, "")
|
||||||
.replace(/(\.net|\.com|\.org|\.info|\.edu)/ig, "");
|
.replace(/(\.net|\.com|\.org|\.info|\.edu)/ig, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import froca from "./froca.js";
|
|||||||
import attributeRenderer from "./attribute_renderer.js";
|
import attributeRenderer from "./attribute_renderer.js";
|
||||||
import libraryLoader from "./library_loader.js";
|
import libraryLoader from "./library_loader.js";
|
||||||
import treeService from "./tree.js";
|
import treeService from "./tree.js";
|
||||||
|
import utils from "./utils.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="note-list">
|
<div class="note-list">
|
||||||
@ -215,7 +216,11 @@ class NoteListRenderer {
|
|||||||
if (highlightedTokens.length > 0) {
|
if (highlightedTokens.length > 0) {
|
||||||
await libraryLoader.requireLibrary(libraryLoader.MARKJS);
|
await libraryLoader.requireLibrary(libraryLoader.MARKJS);
|
||||||
|
|
||||||
this.highlightRegex = new RegExp(highlightedTokens.join("|"), 'gi');
|
const regex = highlightedTokens
|
||||||
|
.map(token => utils.escapeRegExp(token))
|
||||||
|
.join("|");
|
||||||
|
|
||||||
|
this.highlightRegex = new RegExp(regex, 'gi');
|
||||||
} else {
|
} else {
|
||||||
this.highlightRegex = null;
|
this.highlightRegex = null;
|
||||||
}
|
}
|
||||||
|
@ -487,12 +487,14 @@ function areObjectsEqual () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyHtmlToClipboard(content) {
|
function copyHtmlToClipboard(content) {
|
||||||
const clipboardItem = new ClipboardItem({
|
function listener(e) {
|
||||||
'text/html': new Blob([content], {type: 'text/html'}),
|
e.clipboardData.setData("text/html", content);
|
||||||
'text/plain': new Blob([content], {type: 'text/plain'})
|
e.clipboardData.setData("text/plain", content);
|
||||||
});
|
e.preventDefault();
|
||||||
|
}
|
||||||
navigator.clipboard.write([clipboardItem]);
|
document.addEventListener("copy", listener);
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.removeEventListener("copy", listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import libraryLoader from "../../services/library_loader.js";
|
import libraryLoader from '../../services/library_loader.js';
|
||||||
import TypeWidget from "./type_widget.js";
|
import TypeWidget from './type_widget.js';
|
||||||
import utils from '../../services/utils.js';
|
import utils from '../../services/utils.js';
|
||||||
import linkService from '../../services/link.js';
|
import linkService from '../../services/link.js';
|
||||||
import debounce from "../../services/debounce.js";
|
import debounce from '../../services/debounce.js';
|
||||||
|
|
||||||
const {sleep} = utils;
|
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
|
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
|
||||||
@ -115,7 +113,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
||||||
|
|
||||||
// will be overwritten
|
// will be overwritten
|
||||||
this.excalidrawRef;
|
|
||||||
this.$render;
|
this.$render;
|
||||||
this.$widget;
|
this.$widget;
|
||||||
this.reactHandlers; // used to control react state
|
this.reactHandlers; // used to control react state
|
||||||
@ -155,7 +152,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
const renderElement = this.$render.get(0);
|
const renderElement = this.$render.get(0);
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(renderElement);
|
ReactDOM.unmountComponentAtNode(renderElement);
|
||||||
ReactDOM.render(React.createElement(this.createExcalidrawReactApp), renderElement);
|
const root = ReactDOM.createRoot(renderElement);
|
||||||
|
root.render(React.createElement(this.createExcalidrawReactApp));
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.$widget;
|
return this.$widget;
|
||||||
@ -179,9 +177,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
const blob = await note.getBlob();
|
const blob = await note.getBlob();
|
||||||
|
|
||||||
// before we load content into excalidraw, make sure excalidraw has loaded
|
// before we load content into excalidraw, make sure excalidraw has loaded
|
||||||
while (!this.excalidrawRef?.current) {
|
while (!this.excalidrawApi) {
|
||||||
console.log("excalidrawRef not yet loaded, sleep 200ms...");
|
console.log("excalidrawApi not yet loaded, sleep 200ms...");
|
||||||
await sleep(200);
|
await utils.sleep(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +197,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
collaborators: []
|
collaborators: []
|
||||||
};
|
};
|
||||||
|
|
||||||
this.excalidrawRef.current.updateScene(sceneData);
|
this.excalidrawApi.updateScene(sceneData);
|
||||||
}
|
}
|
||||||
else if (blob.content) {
|
else if (blob.content) {
|
||||||
// load saved content into excalidraw canvas
|
// load saved content into excalidraw canvas
|
||||||
@ -246,9 +244,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
fileArray.push(file);
|
fileArray.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.excalidrawRef.current.updateScene(sceneData);
|
this.excalidrawApi.updateScene(sceneData);
|
||||||
this.excalidrawRef.current.addFiles(fileArray);
|
this.excalidrawApi.addFiles(fileArray);
|
||||||
this.excalidrawRef.current.history.clear();
|
this.excalidrawApi.history.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
@ -261,7 +259,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const libraryItems = blobs.map(blob => blob.getJsonContentSafely()).filter(item => !!item);
|
const libraryItems = blobs.map(blob => blob.getJsonContentSafely()).filter(item => !!item);
|
||||||
this.excalidrawRef.current.updateLibrary({libraryItems, merge: false});
|
this.excalidrawApi.updateLibrary({libraryItems, merge: false});
|
||||||
});
|
});
|
||||||
|
|
||||||
// set initial scene version
|
// set initial scene version
|
||||||
@ -275,17 +273,17 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
* this is automatically called after this.saveData();
|
* this is automatically called after this.saveData();
|
||||||
*/
|
*/
|
||||||
async getData() {
|
async getData() {
|
||||||
const elements = this.excalidrawRef.current.getSceneElements();
|
const elements = this.excalidrawApi.getSceneElements();
|
||||||
const appState = this.excalidrawRef.current.getAppState();
|
const appState = this.excalidrawApi.getAppState();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A file is not deleted, even though removed from canvas. Therefore, we only keep
|
* A file is not deleted, even though removed from canvas. Therefore, we only keep
|
||||||
* files that are referenced by an element. Maybe this will change with a new excalidraw version?
|
* files that are referenced by an element. Maybe this will change with a new excalidraw version?
|
||||||
*/
|
*/
|
||||||
const files = this.excalidrawRef.current.getFiles();
|
const files = this.excalidrawApi.getFiles();
|
||||||
|
|
||||||
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
||||||
const svg = await window.ExcalidrawLib.exportToSvg({
|
const svg = await ExcalidrawLib.exportToSvg({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
exportPadding: 5, // 5 px padding
|
exportPadding: 5, // 5 px padding
|
||||||
@ -321,7 +319,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
// this.libraryChanged is unset in dataSaved()
|
// this.libraryChanged is unset in dataSaved()
|
||||||
|
|
||||||
// there's no separate method to get library items, so have to abuse this one
|
// there's no separate method to get library items, so have to abuse this one
|
||||||
const libraryItems = await this.excalidrawRef.current.updateLibrary({merge: true});
|
const libraryItems = await this.excalidrawApi.updateLibrary({merge: true});
|
||||||
|
|
||||||
let position = 10;
|
let position = 10;
|
||||||
|
|
||||||
@ -379,9 +377,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
createExcalidrawReactApp() {
|
createExcalidrawReactApp() {
|
||||||
const React = window.React;
|
const React = window.React;
|
||||||
const { Excalidraw } = window.ExcalidrawLib;
|
const { Excalidraw } = window.ExcalidrawLib;
|
||||||
|
|
||||||
const excalidrawRef = React.useRef(null);
|
|
||||||
this.excalidrawRef = excalidrawRef;
|
|
||||||
const excalidrawWrapperRef = React.useRef(null);
|
const excalidrawWrapperRef = React.useRef(null);
|
||||||
this.excalidrawWrapperRef = excalidrawWrapperRef;
|
this.excalidrawWrapperRef = excalidrawWrapperRef;
|
||||||
const [dimensions, setDimensions] = React.useState({
|
const [dimensions, setDimensions] = React.useState({
|
||||||
@ -439,7 +434,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
React.createElement(Excalidraw, {
|
React.createElement(Excalidraw, {
|
||||||
// this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu
|
// this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu
|
||||||
theme: this.themeStyle,
|
theme: this.themeStyle,
|
||||||
ref: excalidrawRef,
|
excalidrawAPI: api => { this.excalidrawApi = api; },
|
||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
height: dimensions.height,
|
height: dimensions.height,
|
||||||
onPaste: (data, event) => {
|
onPaste: (data, event) => {
|
||||||
@ -483,8 +478,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSceneVersion() {
|
getSceneVersion() {
|
||||||
if (this.excalidrawRef) {
|
if (this.excalidrawApi) {
|
||||||
const elements = this.excalidrawRef.current.getSceneElements();
|
const elements = this.excalidrawApi.getSceneElements();
|
||||||
return window.ExcalidrawLib.getSceneVersion(elements);
|
return window.ExcalidrawLib.getSceneVersion(elements);
|
||||||
} else {
|
} else {
|
||||||
return this.SCENE_VERSION_ERROR;
|
return this.SCENE_VERSION_ERROR;
|
||||||
|
@ -88,3 +88,7 @@ body .CodeMirror {
|
|||||||
.excalidraw.theme--dark {
|
.excalidraw.theme--dark {
|
||||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body .todo-list input[type="checkbox"]:not(:checked):before {
|
||||||
|
border-color: var(--muted-text-color) !important;
|
||||||
|
}
|
||||||
|
@ -20,7 +20,7 @@ function getBlobPojo(entityName: string, entityId: string) {
|
|||||||
if (!entity.hasStringContent()) {
|
if (!entity.hasStringContent()) {
|
||||||
pojo.content = null;
|
pojo.content = null;
|
||||||
} else {
|
} else {
|
||||||
pojo.content = processContent(pojo.content, entity.isProtected, true);
|
pojo.content = processContent(pojo.content, !!entity.isProtected, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pojo;
|
return pojo;
|
||||||
|
@ -1 +1 @@
|
|||||||
export = { buildDate:"2024-01-21T23:49:23+01:00", buildRevision: "4f8073daa7cff1b8b6737ae45792b2e87c2adf33" };
|
export = { buildDate:"2024-03-28T07:11:39+01:00", buildRevision: "399458b52f250b22be22d980a78de0b3390d7521" };
|
||||||
|
@ -20,6 +20,6 @@ export interface EntityRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityChangeRecord {
|
export interface EntityChangeRecord {
|
||||||
entityChange: EntityChange;
|
entityChange: EntityChange;
|
||||||
entity?: EntityRow;
|
entity?: EntityRow;
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
interface DefinitionObject {
|
interface DefinitionObject {
|
||||||
isPromoted: boolean;
|
isPromoted?: boolean;
|
||||||
labelType: string;
|
labelType?: string;
|
||||||
multiplicity: string;
|
multiplicity?: string;
|
||||||
numberPrecision: number;
|
numberPrecision?: number;
|
||||||
promotedAlias: string;
|
promotedAlias?: string;
|
||||||
inverseRelation: string;
|
inverseRelation?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(value: string): DefinitionObject {
|
function parse(value: string): DefinitionObject {
|
||||||
const tokens = value.split(',').map(t => t.trim());
|
const tokens = value.split(',').map(t => t.trim());
|
||||||
const defObj: Partial<DefinitionObject> = {};
|
const defObj: DefinitionObject = {};
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (token === 'promoted') {
|
if (token === 'promoted') {
|
||||||
@ -41,7 +41,7 @@ function parse(value: string): DefinitionObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return defObj as DefinitionObject;
|
return defObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
|
@ -128,11 +128,7 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
|
|
||||||
if (type === 'text' && mime === 'text/html') {
|
if (type === 'text' && mime === 'text/html') {
|
||||||
if (!this.raw && content.length < 20000) { // striptags is slow for very large notes
|
if (!this.raw && content.length < 20000) { // striptags is slow for very large notes
|
||||||
// allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412
|
content = this.stripTags(content);
|
||||||
content = striptags(content, ['a'], ' ');
|
|
||||||
|
|
||||||
// at least the closing tag can be easily stripped
|
|
||||||
content = content.replace(/<\/a>/ig, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.replace(/ /g, ' ');
|
content = content.replace(/ /g, ' ');
|
||||||
@ -140,6 +136,23 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
|
|
||||||
return content.trim();
|
return content.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stripTags(content: string) {
|
||||||
|
// we want to allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412
|
||||||
|
// we want to insert space in place of block tags (because they imply text separation)
|
||||||
|
// but we don't want to insert text for typical formatting inline tags which can occur within one word
|
||||||
|
const linkTag = 'a';
|
||||||
|
const inlineFormattingTags = ['b', 'strong', 'em', 'i', 'span', 'big', 'small', 'font', 'sub', 'sup'];
|
||||||
|
|
||||||
|
// replace tags which imply text separation with a space
|
||||||
|
content = striptags(content, [linkTag, ...inlineFormattingTags], ' ');
|
||||||
|
|
||||||
|
// replace the inline formatting tags (but not links) without a space
|
||||||
|
content = striptags(content, [linkTag], '');
|
||||||
|
|
||||||
|
// at least the closing link tag can be easily stripped
|
||||||
|
return content.replace(/<\/a>/ig, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = NoteContentFulltextExp;
|
export = NoteContentFulltextExp;
|
||||||
|
@ -25,7 +25,7 @@ class OrderByAndLimitExp extends Expression {
|
|||||||
constructor(orderDefinitions: Pick<OrderDefinition, "direction" | "valueExtractor">[], limit?: number) {
|
constructor(orderDefinitions: Pick<OrderDefinition, "direction" | "valueExtractor">[], limit?: number) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.orderDefinitions = orderDefinitions as unknown as OrderDefinition[];
|
this.orderDefinitions = orderDefinitions as OrderDefinition[];
|
||||||
|
|
||||||
for (const od of this.orderDefinitions) {
|
for (const od of this.orderDefinitions) {
|
||||||
od.smaller = od.direction === "asc" ? -1 : 1;
|
od.smaller = od.direction === "asc" ? -1 : 1;
|
||||||
|
@ -51,9 +51,7 @@ class SearchResult {
|
|||||||
addScoreForStrings(tokens: string[], str: string, factor: number) {
|
addScoreForStrings(tokens: string[], str: string, factor: number) {
|
||||||
const chunks = str.toLowerCase().split(" ");
|
const chunks = str.toLowerCase().split(" ");
|
||||||
|
|
||||||
if (!this.score) {
|
this.score = 0;
|
||||||
this.score = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
|
@ -113,7 +113,7 @@ class ValueExtractor {
|
|||||||
i++;
|
i++;
|
||||||
|
|
||||||
const attr = cursor.getAttributeCaseInsensitive('relation', cur());
|
const attr = cursor.getAttributeCaseInsensitive('relation', cur());
|
||||||
cursor = (attr ? attr.targetNote || null : null);
|
cursor = attr?.targetNote || null;
|
||||||
}
|
}
|
||||||
else if (cur() === 'parents') {
|
else if (cur() === 'parents') {
|
||||||
cursor = cursor.parents[0];
|
cursor = cursor.parents[0];
|
||||||
|
@ -57,7 +57,7 @@ class TaskContext {
|
|||||||
type: 'taskProgressCount',
|
type: 'taskProgressCount',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
taskType: this.taskType,
|
taskType: this.taskType,
|
||||||
data: this.data || undefined,
|
data: this.data,
|
||||||
progressCount: this.progressCount
|
progressCount: this.progressCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ class TaskContext {
|
|||||||
type: 'taskError',
|
type: 'taskError',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
taskType: this.taskType,
|
taskType: this.taskType,
|
||||||
data: this.data || undefined,
|
data: this.data,
|
||||||
message: message
|
message: message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ class TaskContext {
|
|||||||
type: 'taskSucceeded',
|
type: 'taskSucceeded',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
taskType: this.taskType,
|
taskType: this.taskType,
|
||||||
data: this.data || undefined,
|
data: this.data,
|
||||||
result: result
|
result: result
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,7 @@ import path = require('path');
|
|||||||
import windowService = require('./window');
|
import windowService = require('./window');
|
||||||
import optionService = require('./options');
|
import optionService = require('./options');
|
||||||
|
|
||||||
const UPDATE_TRAY_EVENTS = [
|
let tray: Tray;
|
||||||
'minimize', 'maximize', 'show', 'hide'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
let tray: Tray | null = null;
|
|
||||||
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
||||||
// is minimized
|
// is minimized
|
||||||
let isVisible = true;
|
let isVisible = true;
|
||||||
@ -42,14 +38,15 @@ const registerVisibilityListener = () => {
|
|||||||
// They need to be registered before the tray updater is registered
|
// They need to be registered before the tray updater is registered
|
||||||
mainWindow.on('show', () => {
|
mainWindow.on('show', () => {
|
||||||
isVisible = true;
|
isVisible = true;
|
||||||
|
updateTrayMenu();
|
||||||
});
|
});
|
||||||
mainWindow.on('hide', () => {
|
mainWindow.on('hide', () => {
|
||||||
isVisible = false;
|
isVisible = false;
|
||||||
|
updateTrayMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
UPDATE_TRAY_EVENTS.forEach((eventName) => {
|
mainWindow.on("minimize", updateTrayMenu);
|
||||||
mainWindow.on(eventName as any, updateTrayMenu)
|
mainWindow.on("maximize", updateTrayMenu);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTrayMenu = () => {
|
const updateTrayMenu = () => {
|
||||||
|
@ -17,7 +17,7 @@ let setupWindow: BrowserWindow | null;
|
|||||||
async function createExtraWindow(extraWindowHash: string) {
|
async function createExtraWindow(extraWindowHash: string) {
|
||||||
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
|
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
|
||||||
|
|
||||||
const {BrowserWindow} = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
|
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
width: 1000,
|
width: 1000,
|
||||||
@ -53,7 +53,7 @@ async function createMainWindow(app: App) {
|
|||||||
|
|
||||||
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
|
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
|
||||||
|
|
||||||
const {BrowserWindow} = require('electron'); // should not be statically imported
|
const { BrowserWindow } = require('electron'); // should not be statically imported
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
@ -128,7 +128,7 @@ function getIcon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createSetupWindow() {
|
async function createSetupWindow() {
|
||||||
const {BrowserWindow} = require('electron'); // should not be statically imported
|
const { BrowserWindow } = require('electron'); // should not be statically imported
|
||||||
setupWindow = new BrowserWindow({
|
setupWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 800,
|
height: 800,
|
||||||
@ -152,7 +152,7 @@ function closeSetupWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function registerGlobalShortcuts() {
|
async function registerGlobalShortcuts() {
|
||||||
const {globalShortcut} = require('electron');
|
const { globalShortcut } = require('electron');
|
||||||
|
|
||||||
await sqlInit.dbReady;
|
await sqlInit.dbReady;
|
||||||
|
|
||||||
|
@ -29,11 +29,11 @@ let lastSyncedPush: number | null = null;
|
|||||||
interface Message {
|
interface Message {
|
||||||
type: string;
|
type: string;
|
||||||
data?: {
|
data?: {
|
||||||
lastSyncedPush?: number,
|
lastSyncedPush?: number | null,
|
||||||
entityChanges?: any[],
|
entityChanges?: any[],
|
||||||
safeImport?: boolean
|
safeImport?: boolean
|
||||||
},
|
},
|
||||||
lastSyncedPush?: number,
|
lastSyncedPush?: number | null,
|
||||||
|
|
||||||
progressCount?: number;
|
progressCount?: number;
|
||||||
taskId?: string;
|
taskId?: string;
|
||||||
@ -143,7 +143,7 @@ function fillInAdditionalProperties(entityChange: EntityChange) {
|
|||||||
if (!entityChange.entity) {
|
if (!entityChange.entity) {
|
||||||
entityChange.entity = sql.getRow(`SELECT * FROM notes WHERE noteId = ?`, [entityChange.entityId]);
|
entityChange.entity = sql.getRow(`SELECT * FROM notes WHERE noteId = ?`, [entityChange.entityId]);
|
||||||
|
|
||||||
if (entityChange.entity && entityChange.entity.isProtected) {
|
if (entityChange.entity?.isProtected) {
|
||||||
entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title || "");
|
entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title || "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ function fillInAdditionalProperties(entityChange: EntityChange) {
|
|||||||
|
|
||||||
if (parentNote) {
|
if (parentNote) {
|
||||||
for (const childBranch of parentNote.getChildBranches()) {
|
for (const childBranch of parentNote.getChildBranches()) {
|
||||||
if (childBranch && childBranch.branchId) {
|
if (childBranch?.branchId) {
|
||||||
entityChange.positions[childBranch.branchId] = childBranch.notePosition;
|
entityChange.positions[childBranch.branchId] = childBranch.notePosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +223,7 @@ function sendPing(client: WebSocket, entityChangeIds = []) {
|
|||||||
sendMessage(client, {
|
sendMessage(client, {
|
||||||
type: 'frontend-update',
|
type: 'frontend-update',
|
||||||
data: {
|
data: {
|
||||||
lastSyncedPush: lastSyncedPush || undefined,
|
lastSyncedPush,
|
||||||
entityChanges
|
entityChanges
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -238,19 +238,19 @@ function sendTransactionEntityChangesToAllClients() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncPullInProgress() {
|
function syncPullInProgress() {
|
||||||
sendMessageToAllClients({ type: 'sync-pull-in-progress', lastSyncedPush: lastSyncedPush || undefined });
|
sendMessageToAllClients({ type: 'sync-pull-in-progress', lastSyncedPush });
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncPushInProgress() {
|
function syncPushInProgress() {
|
||||||
sendMessageToAllClients({ type: 'sync-push-in-progress', lastSyncedPush: lastSyncedPush || undefined });
|
sendMessageToAllClients({ type: 'sync-push-in-progress', lastSyncedPush });
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncFinished() {
|
function syncFinished() {
|
||||||
sendMessageToAllClients({ type: 'sync-finished', lastSyncedPush: lastSyncedPush || undefined });
|
sendMessageToAllClients({ type: 'sync-finished', lastSyncedPush });
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncFailed() {
|
function syncFailed() {
|
||||||
sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush: lastSyncedPush || undefined });
|
sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush });
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadFrontend(reason: string) {
|
function reloadFrontend(reason: string) {
|
||||||
|
@ -105,10 +105,10 @@ function renderText(result, note) {
|
|||||||
|
|
||||||
if (result.content.includes(`<span class="math-tex">`)) {
|
if (result.content.includes(`<span class="math-tex">`)) {
|
||||||
result.header += `
|
result.header += `
|
||||||
<script src="../../${assetPath}/node_modules/katex/dist/katex.min.js"></script>
|
<script src="../${assetPath}/node_modules/katex/dist/katex.min.js"></script>
|
||||||
<link rel="stylesheet" href="../../${assetPath}/node_modules/katex/dist/katex.min.css">
|
<link rel="stylesheet" href="../${assetPath}/node_modules/katex/dist/katex.min.css">
|
||||||
<script src="../../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
|
<script src="../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
|
||||||
<script src="../../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
|
<script src="../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
renderMathInElement(document.getElementById('content'));
|
renderMathInElement(document.getElementById('content'));
|
||||||
|
40
src/www.js
40
src/www.js
@ -45,7 +45,7 @@ function startTrilium() {
|
|||||||
* instead of the new one. This is complicated by the fact that it is possible to run multiple instances of Trilium
|
* instead of the new one. This is complicated by the fact that it is possible to run multiple instances of Trilium
|
||||||
* if port and data dir are configured separately. This complication is the source of the following weird usage.
|
* if port and data dir are configured separately. This complication is the source of the following weird usage.
|
||||||
*
|
*
|
||||||
* The line below makes sure that the "second-instance" (process in window) is fired. Normally it returns a boolean
|
* The line below makes sure that the "second-instance" (process in window.ts) is fired. Normally it returns a boolean
|
||||||
* indicating whether another instance is running or not, but we ignore that and kill the app only based on the port conflict.
|
* indicating whether another instance is running or not, but we ignore that and kill the app only based on the port conflict.
|
||||||
*
|
*
|
||||||
* A bit weird is that "second-instance" is triggered also on the valid usecases (different port/data dir) and
|
* A bit weird is that "second-instance" is triggered also on the valid usecases (different port/data dir) and
|
||||||
@ -126,26 +126,26 @@ function startHttpServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpServer.on('error', error => {
|
httpServer.on('error', error => {
|
||||||
if (!listenOnTcp || error.syscall !== 'listen') {
|
if (!listenOnTcp || error.syscall !== 'listen') {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
|
||||||
|
|
||||||
// handle specific listen errors with friendly messages
|
|
||||||
switch (error.code) {
|
|
||||||
case 'EACCES':
|
|
||||||
console.error(`Port ${port} requires elevated privileges. It's recommended to use port above 1024.`);
|
|
||||||
process.exit(1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'EADDRINUSE':
|
|
||||||
console.error(`Port ${port} is already in use. Most likely, another Trilium process is already running. You might try to find it, kill it, and try again.`);
|
|
||||||
process.exit(1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(`Port ${port} requires elevated privileges. It's recommended to use port above 1024.`);
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(`Port ${port} is already in use. Most likely, another Trilium process is already running. You might try to find it, kill it, and try again.`);
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
httpServer.on('listening', () => {
|
httpServer.on('listening', () => {
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./build",
|
"outDir": "./dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"lib": ["ES2021"],
|
"lib": ["ES2022"],
|
||||||
"downlevelIteration": true
|
"downlevelIteration": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user