mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into excali-17-2
This commit is contained in:
commit
6fad5f2b51
403
.eslintrc.js
403
.eslintrc.js
@ -1,212 +1,207 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'airbnb-base', 'plugin:jsonc/recommended-with-jsonc'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.json', '*.json5', '*.jsonc'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
},
|
||||
// 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
|
||||
{
|
||||
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',
|
||||
'lint-staged',
|
||||
'eslintConfig',
|
||||
],
|
||||
},
|
||||
{
|
||||
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
|
||||
order: { type: 'asc' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
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',
|
||||
],
|
||||
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',
|
||||
|
||||
// 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',
|
||||
|
||||
'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',
|
||||
|
||||
'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'
|
||||
},
|
||||
'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
|
@ -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',
|
||||
};
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -1,6 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
]
|
||||
}
|
||||
|
4
LICENSE
4
LICENSE
@ -643,7 +643,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.js.gnu.org/licenses/>.
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@ -658,4 +658,4 @@ specific requirements.
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.js.gnu.org/licenses/>.
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
@ -1,8 +1,14 @@
|
||||
# Trilium Notes
|
||||
|
||||
## Trilium is in maintenance mode - see details in https://github.com/zadam/trilium/issues/4620
|
||||
|
||||
Preliminary disccusions on the successor organization are taking place in [Trilium Next discussions](https://github.com/orgs/TriliumNext/discussions).
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
|
||||
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases. See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
|
||||
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.
|
||||
|
||||
See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
|
||||
|
||||
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
|
@ -2,49 +2,37 @@
|
||||
|
||||
SRC_DIR=./dist/trilium-linux-x64-src
|
||||
|
||||
if [ "$1" != "DONTCOPY" ]
|
||||
then
|
||||
./bin/copy-trilium.sh $SRC_DIR
|
||||
fi
|
||||
[ "$1" != "DONTCOPY" ] && ./bin/copy-trilium.sh "$SRC_DIR"
|
||||
|
||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
||||
rm -r "$SRC_DIR"/src/public/app-dist/*.mobile.*
|
||||
|
||||
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"
|
||||
|
||||
./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
|
||||
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/
|
||||
rm -rf $BUILD_DIR/dump-db/node_modules
|
||||
|
||||
cp bin/tpl/trilium-portable.sh $BUILD_DIR/
|
||||
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
|
||||
for f in 'trilium-portable' 'trilium-safe-mode' 'trilium-no-cert-check'; do
|
||||
cp bin/tpl/"$f".sh "$BUILD_DIR"/
|
||||
chmod 755 "$BUILD_DIR"/"$f".sh
|
||||
done
|
||||
|
||||
echo "Packaging linux x64 electron distribution..."
|
||||
VERSION=`jq -r ".version" package.json`
|
||||
|
||||
cd dist
|
||||
|
||||
tar cJf trilium-linux-x64-${VERSION}.tar.xz trilium-linux-x64
|
||||
|
||||
cd ..
|
||||
pushd dist
|
||||
tar cJf "trilium-linux-x64-${VERSION}.tar.xz" trilium-linux-x64
|
||||
popd
|
||||
|
||||
bin/build-debian.sh
|
||||
|
@ -4,47 +4,49 @@ if [[ $# -eq 0 ]] ; then
|
||||
echo "Missing argument of target directory"
|
||||
exit 1
|
||||
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
|
||||
mkdir $DIR
|
||||
rm -rf "$DIR"
|
||||
mkdir -pv "$DIR"
|
||||
|
||||
echo "Copying Trilium to build directory $DIR"
|
||||
|
||||
cp -r images $DIR/
|
||||
cp -r libraries $DIR/
|
||||
cp -r src $DIR/
|
||||
cp -r db $DIR/
|
||||
cp -r package.json $DIR/
|
||||
cp -r package-lock.json $DIR/
|
||||
cp -r README.md $DIR/
|
||||
cp -r LICENSE $DIR/
|
||||
cp -r config-sample.ini $DIR/
|
||||
cp -r electron.js $DIR/
|
||||
cp webpack-* $DIR/
|
||||
for d in 'images' 'libraries' 'src' 'db'; do
|
||||
cp -r "$d" "$DIR"/
|
||||
done
|
||||
for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample.ini' 'electron.js'; do
|
||||
cp "$f" "$DIR"/
|
||||
done
|
||||
cp webpack-* "$DIR"/ # here warning because there is no 'webpack-*', but webpack.config.js only
|
||||
|
||||
# run in subshell (so we return to original dir)
|
||||
(cd $DIR && n exec 18.18.2 npm install --only=prod)
|
||||
|
||||
if [[ -d "$DIR"/node_modules ]]; then
|
||||
# cleanup of useless files in dependencies
|
||||
rm -r $DIR/node_modules/image-q/demo
|
||||
rm -r $DIR/node_modules/better-sqlite3/Release
|
||||
rm -r $DIR/node_modules/better-sqlite3/deps/sqlite3.tar.gz
|
||||
rm -r $DIR/node_modules/@jimp/plugin-print/fonts
|
||||
rm -r $DIR/node_modules/jimp/browser
|
||||
rm -r $DIR/node_modules/jimp/fonts
|
||||
for d in 'image-q/demo' 'better-sqlite3/Release' 'better-sqlite3/deps/sqlite3.tar.gz' '@jimp/plugin-print/fonts' 'jimp/browser' 'jimp/fonts'; do
|
||||
[[ -e "$DIR"/node_modules/"$d" ]] && rm -rv "$DIR"/node_modules/"$d"
|
||||
done
|
||||
|
||||
# delete all tests (there are often large images as test file for jimp etc.)
|
||||
find $DIR/node_modules -name test -exec rm -rf {} \;
|
||||
find $DIR/node_modules -name docs -exec rm -rf {} \;
|
||||
find $DIR/node_modules -name demo -exec rm -rf {} \;
|
||||
for d in 'test' 'docs' 'demo'; do
|
||||
find "$DIR"/node_modules -name "$d" -exec rm -rf {} \;
|
||||
done
|
||||
fi
|
||||
|
||||
find $DIR/libraries -name "*.map" -type f -delete
|
||||
|
||||
cp $DIR/src/public/app/share.js $DIR/src/public/app-dist/
|
||||
cp -r $DIR/src/public/app/doc_notes $DIR/src/public/app-dist/
|
||||
d="$DIR"/src/public
|
||||
[[ -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
|
||||
|
@ -8,3 +8,6 @@ CREATE TABLE IF NOT EXISTS "blobs" (
|
||||
|
||||
ALTER TABLE notes ADD blobId TEXT DEFAULT NULL;
|
||||
ALTER TABLE note_revisions ADD blobId TEXT DEFAULT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS IDX_notes_blobId on notes (blobId);
|
||||
CREATE INDEX IF NOT EXISTS IDX_note_revisions_blobId on note_revisions (blobId);
|
||||
|
@ -21,5 +21,6 @@ CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`);
|
||||
CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`);
|
||||
CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`);
|
||||
CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`);
|
||||
CREATE INDEX IF NOT EXISTS IDX_revisions_blobId on revisions (blobId);
|
||||
|
||||
UPDATE entity_changes SET entityName = 'revisions' WHERE entityName = 'note_revisions';
|
||||
|
@ -19,3 +19,5 @@ CREATE INDEX IDX_attachments_ownerId_role
|
||||
|
||||
CREATE INDEX IDX_attachments_utcDateScheduledForErasureSince
|
||||
on attachments (utcDateScheduledForErasureSince);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS IDX_attachments_blobId on attachments (blobId);
|
||||
|
17
db/migrations/0228__fix_blobIds.sql
Normal file
17
db/migrations/0228__fix_blobIds.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- + is normally replaced by X and / by Y, but this can temporarily cause UNIQUE key exception
|
||||
-- this might create blob duplicates, but cleanup will eventually take care of it
|
||||
|
||||
UPDATE blobs SET blobId = REPLACE(blobId, '+', 'A');
|
||||
UPDATE blobs SET blobId = REPLACE(blobId, '/', 'B');
|
||||
|
||||
UPDATE notes SET blobId = REPLACE(blobId, '+', 'A');
|
||||
UPDATE notes SET blobId = REPLACE(blobId, '/', 'B');
|
||||
|
||||
UPDATE attachments SET blobId = REPLACE(blobId, '+', 'A');
|
||||
UPDATE attachments SET blobId = REPLACE(blobId, '/', 'B');
|
||||
|
||||
UPDATE revisions SET blobId = REPLACE(blobId, '+', 'A');
|
||||
UPDATE revisions SET blobId = REPLACE(blobId, '/', 'B');
|
||||
|
||||
UPDATE entity_changes SET entityId = REPLACE(entityId, '+', 'A') WHERE entityName = 'blobs';
|
||||
UPDATE entity_changes SET entityId = REPLACE(entityId, '/', 'B') WHERE entityName = 'blobs';
|
@ -5,8 +5,8 @@
|
||||
}
|
||||
|
||||
/*
|
||||
* CKEditor 5 (v40.1.0) content styles.
|
||||
* Generated on Mon, 20 Nov 2023 08:41:49 GMT.
|
||||
* CKEditor 5 (v41.0.0) content styles.
|
||||
* Generated on Fri, 26 Jan 2024 10:23:49 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
||||
*/
|
||||
|
||||
@ -42,18 +42,6 @@
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-selector-caption-text);
|
||||
background-color: var(--ck-color-selector-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 0.9em auto;
|
||||
@ -87,12 +75,17 @@
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 0.9em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-selector-caption-text);
|
||||
background-color: var(--ck-color-selector-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
@ -130,6 +123,13 @@
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 0.9em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list {
|
||||
list-style: none;
|
||||
@ -280,6 +280,42 @@
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
||||
position: absolute;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol {
|
||||
list-style-type: lower-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol {
|
||||
list-style-type: upper-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol ol {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
@ -318,17 +354,6 @@
|
||||
flex-shrink: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: var(--ck-color-image-caption-text);
|
||||
background-color: var(--ck-color-image-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content img.image_resized {
|
||||
height: auto;
|
||||
@ -347,67 +372,16 @@
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-yellow {
|
||||
background-color: var(--ck-highlight-marker-yellow);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-green {
|
||||
background-color: var(--ck-highlight-marker-green);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-pink {
|
||||
background-color: var(--ck-highlight-marker-pink);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-blue {
|
||||
background-color: var(--ck-highlight-marker-blue);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-red {
|
||||
color: var(--ck-highlight-pen-red);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-green {
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol {
|
||||
list-style-type: lower-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol {
|
||||
list-style-type: upper-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol ol {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul ul {
|
||||
list-style-type: square;
|
||||
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: var(--ck-color-image-caption-text);
|
||||
background-color: var(--ck-color-image-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left,
|
||||
@ -470,6 +444,32 @@
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-left: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-yellow {
|
||||
background-color: var(--ck-highlight-marker-yellow);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-green {
|
||||
background-color: var(--ck-highlight-marker-green);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-pink {
|
||||
background-color: var(--ck-highlight-marker-pink);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-blue {
|
||||
background-color: var(--ck-highlight-marker-blue);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-red {
|
||||
color: var(--ck-highlight-pen-red);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-green {
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
|
6
libraries/ckeditor/ckeditor.js
vendored
6
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
3770
package-lock.json
generated
3770
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
65
package.json
65
package.json
@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.62.4",
|
||||
"version": "0.63.3",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@ -32,19 +32,18 @@
|
||||
"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",
|
||||
"lint": "eslint . --cache",
|
||||
"prepare": "husky install || echo 'Husky install failed, expected on flatpak build'"
|
||||
"lint": "eslint . --cache"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@electron/remote": "2.1.1",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@excalidraw/excalidraw": "0.16.1",
|
||||
"archiver": "6.0.1",
|
||||
"async-mutex": "0.4.0",
|
||||
"axios": "1.6.3",
|
||||
"archiver": "7.0.0",
|
||||
"async-mutex": "0.4.1",
|
||||
"axios": "1.6.7",
|
||||
"better-sqlite3": "8.4.0",
|
||||
"boxicons": "2.1.4",
|
||||
"chokidar": "3.5.3",
|
||||
"chokidar": "3.6.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"compression": "1.7.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
@ -54,35 +53,35 @@
|
||||
"debounce": "1.2.1",
|
||||
"ejs": "3.1.9",
|
||||
"electron-debug": "3.2.0",
|
||||
"electron-dl": "3.5.1",
|
||||
"electron-dl": "3.5.2",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"express": "4.18.2",
|
||||
"express": "4.18.3",
|
||||
"express-partial-content": "1.0.2",
|
||||
"express-rate-limit": "7.1.5",
|
||||
"express-session": "1.17.3",
|
||||
"force-graph": "1.43.4",
|
||||
"express-rate-limit": "7.2.0",
|
||||
"express-session": "1.18.0",
|
||||
"force-graph": "1.43.5",
|
||||
"fs-extra": "11.2.0",
|
||||
"helmet": "7.1.0",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.0",
|
||||
"https-proxy-agent": "7.0.2",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.4",
|
||||
"image-type": "4.1.0",
|
||||
"ini": "3.0.1",
|
||||
"is-animated": "2.0.2",
|
||||
"is-svg": "4.3.2",
|
||||
"jimp": "0.22.10",
|
||||
"jimp": "0.22.12",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
"jsdom": "23.0.1",
|
||||
"jsdom": "24.0.0",
|
||||
"katex": "0.16.9",
|
||||
"marked": "9.1.6",
|
||||
"mermaid": "10.6.1",
|
||||
"marked": "12.0.0",
|
||||
"mermaid": "10.9.0",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "3.52.0",
|
||||
"node-abi": "3.56.0",
|
||||
"normalize-strings": "1.1.1",
|
||||
"open": "8.4.1",
|
||||
"panzoom": "9.4.3",
|
||||
@ -94,45 +93,41 @@
|
||||
"rimraf": "5.0.5",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.11.0",
|
||||
"sanitize-html": "2.12.1",
|
||||
"sax": "1.3.0",
|
||||
"semver": "7.5.4",
|
||||
"semver": "7.6.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
"split.js": "1.6.5",
|
||||
"stream-throttle": "0.1.3",
|
||||
"striptags": "3.2.0",
|
||||
"tmp": "0.2.1",
|
||||
"tmp": "0.2.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"turndown": "7.1.2",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "8.16.0",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "2.10.0"
|
||||
"yauzl": "3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "28.1.0",
|
||||
"electron-builder": "24.9.1",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"eslint": "8.56.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsonc": "2.11.2",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"eslint-plugin-jsonc": "2.13.0",
|
||||
"esm": "3.2.25",
|
||||
"husky": "8.0.3",
|
||||
"jasmine": "5.1.0",
|
||||
"jsdoc": "4.0.2",
|
||||
"jsonc-eslint-parser": "2.4.0",
|
||||
"lint-staged": "15.2.0",
|
||||
"lint-staged": "15.2.2",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"nodemon": "3.0.2",
|
||||
"prettier": "3.1.1",
|
||||
"nodemon": "3.1.0",
|
||||
"rcedit": "4.0.1",
|
||||
"webpack": "5.89.0",
|
||||
"webpack": "5.90.3",
|
||||
"webpack-cli": "5.1.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
@ -161,6 +161,13 @@ class BRevision extends AbstractBeccaEntity {
|
||||
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() {
|
||||
super.beforeSaving();
|
||||
|
||||
|
@ -427,6 +427,116 @@ paths:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
/attachments:
|
||||
post:
|
||||
description: create an attachment
|
||||
operationId: postAttachment
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateAttachment'
|
||||
responses:
|
||||
'201':
|
||||
description: attachment created
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Attachment'
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
/attachments/{attachmentId}:
|
||||
parameters:
|
||||
- name: attachmentId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
get:
|
||||
description: Returns an attachment identified by its ID
|
||||
operationId: getAttachmentById
|
||||
responses:
|
||||
'200':
|
||||
description: attachment response
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Attachment'
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
patch:
|
||||
description: patch an attachment identified by the attachmentId with changes in the body. Only role, mime, title, and position are patchable.
|
||||
operationId: patchAttachmentById
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Attachment'
|
||||
responses:
|
||||
'200':
|
||||
description: attribute updated
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Attachment'
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
delete:
|
||||
description: deletes an attachment based on the attachmentId supplied.
|
||||
operationId: deleteAttachmentById
|
||||
responses:
|
||||
'204':
|
||||
description: attachment deleted
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
/attachments/{attachmentId}/content:
|
||||
parameters:
|
||||
- name: attachmentId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
get:
|
||||
description: Returns attachment content identified by its ID
|
||||
operationId: getAttachmentContent
|
||||
responses:
|
||||
'200':
|
||||
description: attachment content response
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
description: Updates attachment content identified by its ID
|
||||
operationId: putAttachmentContentById
|
||||
requestBody:
|
||||
description: html content of attachment
|
||||
required: true
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: attachment content updated
|
||||
/attributes:
|
||||
post:
|
||||
description: create an attribute for a given note
|
||||
@ -474,7 +584,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
patch:
|
||||
description: patch a attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one.
|
||||
description: patch an attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one.
|
||||
operationId: patchAttributeById
|
||||
requestBody:
|
||||
required: true
|
||||
@ -496,7 +606,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
delete:
|
||||
description: deletes a attribute based on the attributeId supplied.
|
||||
description: deletes an attribute based on the attributeId supplied.
|
||||
operationId: deleteAttributeById
|
||||
responses:
|
||||
'204':
|
||||
@ -884,6 +994,57 @@ components:
|
||||
$ref: '#/components/schemas/Note'
|
||||
branch:
|
||||
$ref: '#/components/schemas/Branch'
|
||||
Attachment:
|
||||
type: object
|
||||
description: Attachment is owned by a note, has title and content
|
||||
properties:
|
||||
attachmentId:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
readOnly: true
|
||||
ownerId:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
description: identifies the owner of the attachment, is either noteId or revisionId
|
||||
role:
|
||||
type: string
|
||||
mime:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
position:
|
||||
type: integer
|
||||
format: int32
|
||||
blobId:
|
||||
type: string
|
||||
description: ID of the blob object which effectively serves as a content hash
|
||||
dateModified:
|
||||
$ref: '#/components/schemas/LocalDateTime'
|
||||
readOnly: true
|
||||
utcDateModified:
|
||||
$ref: '#/components/schemas/UtcDateTime'
|
||||
readOnly: true
|
||||
utcDateScheduledForErasureSince:
|
||||
$ref: '#/components/schemas/UtcDateTime'
|
||||
readOnly: true
|
||||
contentLength:
|
||||
type: integer
|
||||
format: int32
|
||||
CreateAttachment:
|
||||
type: object
|
||||
properties:
|
||||
ownerId:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
description: identifies the owner of the attachment, is either noteId or revisionId
|
||||
role:
|
||||
type: string
|
||||
mime:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
position:
|
||||
type: integer
|
||||
format: int32
|
||||
Attribute:
|
||||
type: object
|
||||
description: Attribute (Label, Relation) is a key-value record attached to a note.
|
||||
|
44
src/public/app/menus/image_context_menu.js
Normal file
44
src/public/app/menus/image_context_menu.js
Normal file
@ -0,0 +1,44 @@
|
||||
import utils from "../services/utils.js";
|
||||
import contextMenu from "./context_menu.js";
|
||||
import imageService from "../services/image.js";
|
||||
|
||||
const PROP_NAME = "imageContextMenuInstalled";
|
||||
|
||||
function setupContextMenu($image) {
|
||||
if (!utils.isElectron() || $image.prop(PROP_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image.prop(PROP_NAME, true);
|
||||
$image.on('contextmenu', e => {
|
||||
e.preventDefault();
|
||||
|
||||
contextMenu.show({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
{
|
||||
title: "Copy reference to clipboard",
|
||||
command: "copyImageReferenceToClipboard",
|
||||
uiIcon: "bx bx-empty"
|
||||
},
|
||||
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
|
||||
],
|
||||
selectMenuItemHandler: ({command}) => {
|
||||
if (command === 'copyImageReferenceToClipboard') {
|
||||
imageService.copyImageReferenceToClipboard($image);
|
||||
} else if (command === 'copyImageToClipboard') {
|
||||
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
|
||||
utils.dynamicRequire('electron');
|
||||
webContents.copyImageAt(e.pageX, e.pageY);
|
||||
} else {
|
||||
throw new Error(`Unrecognized command '${command}'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
setupContextMenu
|
||||
};
|
@ -107,6 +107,13 @@ async function deleteNotes(branchIdsToDelete, forceDeleteAllClones = false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await activateParentNotePath();
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const taskId = utils.randomString(10);
|
||||
|
||||
let counter = 0;
|
||||
@ -134,6 +141,16 @@ async function deleteNotes(branchIdsToDelete, forceDeleteAllClones = false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function activateParentNotePath() {
|
||||
// this is not perfect, maybe we should find the next/previous sibling, but that's more complex
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
const parentNotePathArr = activeContext.notePathArray.slice(0, -1);
|
||||
|
||||
if (parentNotePathArr.length > 0) {
|
||||
activeContext.setNote(parentNotePathArr.join("/"));
|
||||
}
|
||||
}
|
||||
|
||||
async function moveNodeUpInHierarchy(node) {
|
||||
if (hoistedNoteService.isHoistedNode(node)
|
||||
|| hoistedNoteService.isTopLevelNode(node)
|
||||
|
@ -9,6 +9,7 @@ import linkService from "./link.js";
|
||||
import treeService from "./tree.js";
|
||||
import FNote from "../entities/fnote.js";
|
||||
import FAttachment from "../entities/fattachment.js";
|
||||
import imageContextMenuService from "../menus/image_context_menu.js";
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
@ -148,6 +149,8 @@ function renderImage(entity, $renderedContent, options = {}) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
imageContextMenuService.setupContextMenu($img);
|
||||
}
|
||||
|
||||
function renderFile(entity, type, $renderedContent) {
|
||||
|
@ -48,11 +48,11 @@ async function checkNoteAccess(notePath, noteContext) {
|
||||
|
||||
const hoistedNoteId = noteContext.hoistedNoteId;
|
||||
|
||||
if (!resolvedNotePath.includes(hoistedNoteId) && !resolvedNotePath.includes('_hidden')) {
|
||||
if (!resolvedNotePath.includes(hoistedNoteId) && (!resolvedNotePath.includes('_hidden') || resolvedNotePath.includes('_lbBookmarks'))) {
|
||||
const requestedNote = await froca.getNote(treeService.getNoteIdFromUrl(resolvedNotePath));
|
||||
const hoistedNote = await froca.getNote(hoistedNoteId);
|
||||
|
||||
if (!hoistedNote.hasAncestor('_hidden')
|
||||
if ((!hoistedNote.hasAncestor('_hidden') || resolvedNotePath.includes('_lbBookmarks'))
|
||||
&& !await dialogService.confirm(`Requested note '${requestedNote.title}' is outside of hoisted note '${hoistedNote.title}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?`)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import froca from "./froca.js";
|
||||
import attributeRenderer from "./attribute_renderer.js";
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import treeService from "./tree.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-list">
|
||||
@ -215,7 +216,11 @@ class NoteListRenderer {
|
||||
if (highlightedTokens.length > 0) {
|
||||
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 {
|
||||
this.highlightRegex = null;
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
|
||||
this.attributeDetailWidget.hide();
|
||||
});
|
||||
|
||||
this.$editor.on('blur', () => this.save());
|
||||
this.$editor.on('blur', () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160
|
||||
|
||||
this.$addNewAttributeButton = this.$widget.find('.add-new-attribute-button');
|
||||
this.$addNewAttributeButton.on('click', e => this.addNewAttribute(e));
|
||||
|
@ -8,10 +8,13 @@ export default class RightPaneContainer extends FlexContainer {
|
||||
this.id('right-pane');
|
||||
this.css('height', '100%');
|
||||
this.collapsible();
|
||||
|
||||
this.rightPaneHidden = false;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& !this.rightPaneHidden
|
||||
&& this.children.length > 0
|
||||
&& !!this.children.find(ch => ch.isEnabled() && ch.canBeShown());
|
||||
}
|
||||
@ -44,4 +47,10 @@ export default class RightPaneContainer extends FlexContainer {
|
||||
splitService.setupRightPaneResizer();
|
||||
}
|
||||
}
|
||||
|
||||
toggleRightPaneEvent() {
|
||||
this.rightPaneHidden = !this.rightPaneHidden;
|
||||
|
||||
this.reEvaluateRightPaneVisibilityCommand();
|
||||
}
|
||||
}
|
||||
|
@ -1091,7 +1091,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeCtx = this.#getActiveNodeCtx();
|
||||
const activeNode = this.getActiveNode();
|
||||
const activeNodeFocused = activeNode?.hasFocus();
|
||||
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
|
||||
const refreshCtx = {
|
||||
noteIdsToUpdate: new Set(),
|
||||
@ -1108,7 +1110,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
await this.#executeTreeUpdates(refreshCtx, loadResults);
|
||||
|
||||
await this.#setActiveNode(nodeCtx, movedActiveNode, parentsOfAddedNodes);
|
||||
await this.#setActiveNode(activeNotePath, activeNodeFocused, movedActiveNode, parentsOfAddedNodes);
|
||||
|
||||
if (refreshCtx.noteIdsToReload.size > 0 || refreshCtx.noteIdsToUpdate.size > 0) {
|
||||
// workaround for https://github.com/mar10/fancytree/issues/1054
|
||||
@ -1257,73 +1259,42 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
#getActiveNodeCtx() {
|
||||
const nodeCtx = {
|
||||
activeNotePath: null,
|
||||
activeNodeFocused: null,
|
||||
nextNotePath: null
|
||||
};
|
||||
|
||||
const activeNode = this.getActiveNode();
|
||||
nodeCtx.activeNodeFocused = activeNode?.hasFocus();
|
||||
nodeCtx.activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
|
||||
nodeCtx.nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
|
||||
return nodeCtx;
|
||||
}
|
||||
|
||||
async #setActiveNode(nodeCtx, movedActiveNode, parentsOfAddedNodes) {
|
||||
async #setActiveNode(activeNotePath, activeNodeFocused, movedActiveNode, parentsOfAddedNodes) {
|
||||
if (movedActiveNode) {
|
||||
for (const parentNode of parentsOfAddedNodes) {
|
||||
const foundNode = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId);
|
||||
if (foundNode) {
|
||||
nodeCtx.activeNotePath = treeService.getNotePath(foundNode);
|
||||
activeNotePath = treeService.getNotePath(foundNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodeCtx.activeNotePath) {
|
||||
if (!activeNotePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
let node = await this.expandToNote(nodeCtx.activeNotePath, false);
|
||||
if (node && node.data.noteId !== treeService.getNoteIdFromUrl(nodeCtx.activeNotePath)) {
|
||||
let node = await this.expandToNote(activeNotePath, false);
|
||||
|
||||
if (node && node.data.noteId !== treeService.getNoteIdFromUrl(activeNotePath)) {
|
||||
// if the active note has been moved elsewhere then it won't be found by the path,
|
||||
// so we switch to the alternative of trying to find it by noteId
|
||||
const notesById = this.getNodesByNoteId(treeService.getNoteIdFromUrl(nodeCtx.activeNotePath));
|
||||
const notesById = this.getNodesByNoteId(treeService.getNoteIdFromUrl(activeNotePath));
|
||||
|
||||
// if there are multiple clones, then we'd rather not activate anyone
|
||||
node = notesById.length === 1 ? notesById[0] : null;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
if (nodeCtx.activeNodeFocused) {
|
||||
// needed by Firefox: https://github.com/zadam/trilium/issues/1865
|
||||
this.tree.$container.focus();
|
||||
}
|
||||
|
||||
await node.setActive(true, {noEvents: true, noFocus: !nodeCtx.activeNodeFocused});
|
||||
} else {
|
||||
// this is used when the original note has been deleted, and we want to move the focus to the note above/below
|
||||
node = await this.expandToNote(nodeCtx.nextNotePath, false);
|
||||
|
||||
if (node) {
|
||||
// FIXME: this is conceptually wrong
|
||||
// here note tree is responsible for updating global state of the application
|
||||
// this should be done by NoteContext / TabManager and note tree should only listen to
|
||||
// changes in active note and just set the "active" state
|
||||
// We don't await since that can bring up infinite cycles when e.g. custom widget does some backend requests which wait for max sync ID processed
|
||||
appContext.tabManager.getActiveContext().setNote(nodeCtx.nextNotePath).then(() => {
|
||||
const newActiveNode = this.getActiveNode();
|
||||
|
||||
// return focus if the previously active node was also focused
|
||||
if (newActiveNode && nodeCtx.activeNodeFocused) {
|
||||
newActiveNode.setFocus(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeNodeFocused) {
|
||||
// needed by Firefox: https://github.com/zadam/trilium/issues/1865
|
||||
this.tree.$container.focus();
|
||||
}
|
||||
|
||||
await node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
|
||||
}
|
||||
|
||||
sortChildren(node) {
|
||||
|
@ -30,9 +30,14 @@ const TPL = `
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.title-bar-buttons .top-btn.active{
|
||||
background-color:var(--accented-background-color);
|
||||
}
|
||||
|
||||
.title-bar-buttons .btn.focus, .title-bar-buttons .btn:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- divs act as a hitbox for the buttons, making them clickable on corners -->
|
||||
|
@ -1,7 +1,7 @@
|
||||
import utils from "../../services/utils.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import contextMenu from "../../menus/context_menu.js";
|
||||
import imageContextMenuService from "../../menus/image_context_menu.js";
|
||||
import imageService from "../../services/image.js";
|
||||
|
||||
const TPL = `
|
||||
@ -55,36 +55,7 @@ class ImageTypeWidget extends TypeWidget {
|
||||
});
|
||||
});
|
||||
|
||||
if (utils.isElectron()) {
|
||||
// for browser, we want to let the native menu
|
||||
this.$imageView.on('contextmenu', e => {
|
||||
e.preventDefault();
|
||||
|
||||
contextMenu.show({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
{
|
||||
title: "Copy reference to clipboard",
|
||||
command: "copyImageReferenceToClipboard",
|
||||
uiIcon: "bx bx-empty"
|
||||
},
|
||||
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
|
||||
],
|
||||
selectMenuItemHandler: ({command}) => {
|
||||
if (command === 'copyImageReferenceToClipboard') {
|
||||
imageService.copyImageReferenceToClipboard(this.$imageWrapper);
|
||||
} else if (command === 'copyImageToClipboard') {
|
||||
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
|
||||
utils.dynamicRequire('electron');
|
||||
webContents.copyImageAt(e.pageX, e.pageY);
|
||||
} else {
|
||||
throw new Error(`Unrecognized command '${command}'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
imageContextMenuService.setupContextMenu(this.$imageView);
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ const TPL = `
|
||||
<h5>Highlights List visibility</h5>
|
||||
|
||||
<p>You can hide the highlights widget per-note by adding a <code>#hideHighlightWidget</code> label.</p>
|
||||
|
||||
<p>You can configure a keyboard shortcut for quickly toggling the right pane (including Highlights) in the Options -> Shortcuts (name "toggleRightPane").</p>
|
||||
</div>`;
|
||||
|
||||
export default class HighlightsListOptions extends OptionsWidget {
|
||||
|
@ -11,6 +11,8 @@ const TPL = `
|
||||
</div>
|
||||
|
||||
<p>You can also use this option to effectively disable TOC by setting a very high number.</p>
|
||||
|
||||
<p>You can configure a keyboard shortcut for quickly toggling the right pane (including TOC) in the Options -> Shortcuts (name "toggleRightPane").</p>
|
||||
</div>`;
|
||||
|
||||
export default class TableOfContentsOptions extends OptionsWidget {
|
||||
|
@ -9,14 +9,9 @@
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/vX/images/app-icons/ios/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/vX/images/app-icons/png/512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
"src": "favicon.ico",
|
||||
"sizes": "180x180 512x512",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ body {
|
||||
|
||||
--ck-color-base-text: var(--main-text-color);
|
||||
--ck-color-base-foreground: var(--accented-background-color);
|
||||
--ck-color-base-background: var(--main-background-color);
|
||||
--ck-color-focus-border: var(--main-border-color);
|
||||
--ck-color-text: var(--main-text-color);
|
||||
--ck-color-shadow-drop: var(--main-background-color);
|
||||
|
@ -88,3 +88,7 @@ body .CodeMirror {
|
||||
.excalidraw.theme--dark {
|
||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||
}
|
||||
|
||||
body .todo-list input[type="checkbox"]:not(:checked):before {
|
||||
border-color: var(--muted-text-color) !important;
|
||||
}
|
||||
|
@ -39,7 +39,13 @@ function getLightAnonymizationScript() {
|
||||
SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
|
||||
UNION ALL
|
||||
SELECT blobId FROM revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
|
||||
);`;
|
||||
);
|
||||
|
||||
UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentId', 'documentSecret', 'encryptedDataKey',
|
||||
'passwordVerificationHash', 'passwordVerificationSalt',
|
||||
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
|
||||
AND value != '';`;
|
||||
}
|
||||
|
||||
async function createAnonymizedCopy(type) {
|
||||
|
@ -4,8 +4,8 @@ const build = require('./build.js');
|
||||
const packageJson = require('../../package.json');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir.js');
|
||||
|
||||
const APP_DB_VERSION = 227;
|
||||
const SYNC_VERSION = 31;
|
||||
const APP_DB_VERSION = 228;
|
||||
const SYNC_VERSION = 32;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
module.exports = {
|
||||
|
@ -43,12 +43,16 @@ const optionsService = require('./options.js');
|
||||
*/
|
||||
function BackendScriptApi(currentNote, apiParams) {
|
||||
/**
|
||||
* Note where the script started executing
|
||||
* Note where the script started executing (entrypoint).
|
||||
* As an analogy, in C this would be the file which contains the main() function of the current process.
|
||||
* @type {BNote}
|
||||
*/
|
||||
this.startNote = apiParams.startNote;
|
||||
/**
|
||||
* Note where the script is currently executing. Don't mix this up with the concept of active note
|
||||
* Note where the script is currently executing. This comes into play when your script is spread in multiple code
|
||||
* notes, the script starts in "startNote", but then through function calls may jump into another note (currentNote).
|
||||
* A similar concept in C would be __FILE__
|
||||
* Don't mix this up with the concept of active note.
|
||||
* @type {BNote}
|
||||
*/
|
||||
this.currentNote = currentNote;
|
||||
@ -288,7 +292,7 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
* @param {string} params.parentNoteId
|
||||
* @param {string} params.title
|
||||
* @param {string|Buffer} params.content
|
||||
* @param {NoteType} params.type - text, code, file, image, search, book, relationMap, canvas
|
||||
* @param {NoteType} params.type - text, code, file, image, search, book, relationMap, canvas, webView
|
||||
* @param {string} [params.mime] - value is derived from default mimes for type
|
||||
* @param {boolean} [params.isProtected=false]
|
||||
* @param {boolean} [params.isExpanded=false]
|
||||
|
@ -5,12 +5,14 @@ const utils = require('./utils.js');
|
||||
|
||||
function getBlobPojo(entityName, entityId) {
|
||||
const entity = becca.getEntity(entityName, entityId);
|
||||
|
||||
if (!entity) {
|
||||
throw new NotFoundError(`Entity ${entityName} '${entityId}' was not found.`);
|
||||
}
|
||||
|
||||
const blob = becca.getBlob(entity);
|
||||
if (!blob) {
|
||||
throw new NotFoundError(`Blob ${entity.blobId} for ${entityName} '${entityId}' was not found.`);
|
||||
}
|
||||
|
||||
const pojo = blob.getPojo();
|
||||
|
||||
|
@ -1 +1 @@
|
||||
module.exports = { buildDate:"2023-12-07T00:03:59+01:00", buildRevision: "2e23c521c356c2305124f5df0f474532fa5f34ce" };
|
||||
module.exports = { buildDate:"2024-03-03T06:58:18+01:00", buildRevision: "0ad337c8e806ba84d48d7b97aa46df52d9f236a8" };
|
||||
|
@ -48,6 +48,14 @@ function isEntityEventsDisabled() {
|
||||
return !!namespace.get('disableEntityEvents');
|
||||
}
|
||||
|
||||
function setMigrationRunning(running) {
|
||||
namespace.set('migrationRunning', !!running);
|
||||
}
|
||||
|
||||
function isMigrationRunning() {
|
||||
return !!namespace.get('migrationRunning');
|
||||
}
|
||||
|
||||
function disableSlowQueryLogging(disable) {
|
||||
namespace.set('disableSlowQueryLogging', disable);
|
||||
}
|
||||
@ -102,5 +110,7 @@ module.exports = {
|
||||
putEntityChange,
|
||||
ignoreEntityChangeIds,
|
||||
disableSlowQueryLogging,
|
||||
isSlowQueryLoggingDisabled
|
||||
isSlowQueryLoggingDisabled,
|
||||
setMigrationRunning,
|
||||
isMigrationRunning
|
||||
};
|
||||
|
@ -17,6 +17,9 @@ const {sanitizeAttributeName} = require('./sanitize_attribute_name.js');
|
||||
const noteTypes = require('../services/note_types.js').getNoteTypeNames();
|
||||
|
||||
class ConsistencyChecks {
|
||||
/**
|
||||
* @param autoFix - automatically fix all encountered problems. False is only for debugging during development (fail fast)
|
||||
*/
|
||||
constructor(autoFix) {
|
||||
this.autoFix = autoFix;
|
||||
this.unrecoveredConsistencyErrors = false;
|
||||
|
@ -37,6 +37,8 @@ function eraseNotes(noteIdsToErase) {
|
||||
function setEntityChangesAsErased(entityChanges) {
|
||||
for (const ec of entityChanges) {
|
||||
ec.isErased = true;
|
||||
// we're not changing hash here, not sure if good or not
|
||||
// content hash check takes isErased flag into account, though
|
||||
ec.utcDateChanged = dateUtils.utcNowDateTime();
|
||||
|
||||
entityChangesService.putEntityChangeWithForcedChange(ec);
|
||||
|
@ -181,6 +181,8 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
||||
|
||||
noteIdToMeta[note.noteId] = meta;
|
||||
|
||||
// sort children for having a stable / reproducible export format
|
||||
note.sortChildren();
|
||||
const childBranches = note.getChildBranches()
|
||||
.filter(branch => branch.noteId !== '_hidden');
|
||||
|
||||
|
@ -219,7 +219,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
{
|
||||
actionName: "reopenLastTab",
|
||||
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+T"] : [],
|
||||
description: "Repoens the last closed tab",
|
||||
description: "Reopens the last closed tab",
|
||||
scope: "window"
|
||||
},
|
||||
{
|
||||
@ -291,7 +291,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
{
|
||||
actionName: "eigthTab",
|
||||
defaultShortcuts: ["CommandOrControl+8"],
|
||||
description: "Activates the eigth tab in the list",
|
||||
description: "Activates the eighth tab in the list",
|
||||
scope: "window"
|
||||
},
|
||||
{
|
||||
@ -302,7 +302,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
},
|
||||
{
|
||||
actionName: "lastTab",
|
||||
defaultShortcuts: ["CommandOrControl+0"],
|
||||
defaultShortcuts: [],
|
||||
description: "Activates the last tab in the list",
|
||||
scope: "window"
|
||||
},
|
||||
@ -494,9 +494,16 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
separator: "Other"
|
||||
},
|
||||
|
||||
{
|
||||
actionName: "toggleRightPane",
|
||||
defaultShortcuts: [],
|
||||
description: "Toggle the display of the right pane, which includes Table of Contents and Highlights",
|
||||
scope: "window"
|
||||
},
|
||||
{
|
||||
actionName: "printActiveNote",
|
||||
defaultShortcuts: [],
|
||||
description: "Print active note",
|
||||
scope: "window"
|
||||
},
|
||||
{
|
||||
|
@ -5,12 +5,13 @@ const log = require('./log.js');
|
||||
const utils = require('./utils.js');
|
||||
const resourceDir = require('./resource_dir.js');
|
||||
const appInfo = require('./app_info.js');
|
||||
const cls = require('./cls.js');
|
||||
|
||||
async function migrate() {
|
||||
const currentDbVersion = getDbVersion();
|
||||
|
||||
if (currentDbVersion < 214) {
|
||||
log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.X first and only then to this version.");
|
||||
log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.4 first and only then to this version.");
|
||||
|
||||
utils.crash();
|
||||
return;
|
||||
@ -18,7 +19,7 @@ async function migrate() {
|
||||
|
||||
// backup before attempting migration
|
||||
await backupService.backupNow(
|
||||
// creating a special backup for versions 0.60.X, the changes in 0.61 are major.
|
||||
// creating a special backup for version 0.60.4, the changes in 0.61 are major.
|
||||
currentDbVersion === 214
|
||||
? `before-migration-v060`
|
||||
: 'before-migration'
|
||||
@ -51,6 +52,9 @@ async function migrate() {
|
||||
// all migrations are executed in one transaction - upgrade either succeeds, or the user can stay at the old version
|
||||
// otherwise if half of the migrations succeed, user can't use any version - DB is too "new" for the old app,
|
||||
// and too old for the new app version.
|
||||
|
||||
cls.setMigrationRunning(true);
|
||||
|
||||
sql.transactional(() => {
|
||||
for (const mig of migrations) {
|
||||
try {
|
||||
@ -73,8 +77,11 @@ async function migrate() {
|
||||
}
|
||||
});
|
||||
|
||||
log.info("VACUUMing database, this might take a while ...");
|
||||
sql.execute("VACUUM");
|
||||
if (currentDbVersion === 214) {
|
||||
// special VACUUM after the big migration
|
||||
log.info("VACUUMing database, this might take a while ...");
|
||||
sql.execute("VACUUM");
|
||||
}
|
||||
}
|
||||
|
||||
function executeMigration(mig) {
|
||||
|
@ -471,6 +471,8 @@ function findRelationMapLinks(content, foundLinks) {
|
||||
const imageUrlToAttachmentIdMapping = {};
|
||||
|
||||
async function downloadImage(noteId, imageUrl) {
|
||||
const unescapedUrl = utils.unescapeHtml(imageUrl);
|
||||
|
||||
try {
|
||||
let imageBuffer;
|
||||
|
||||
@ -487,14 +489,14 @@ async function downloadImage(noteId, imageUrl) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
imageBuffer = await request.getImage(imageUrl);
|
||||
imageBuffer = await request.getImage(unescapedUrl);
|
||||
}
|
||||
|
||||
const parsedUrl = url.parse(imageUrl);
|
||||
const parsedUrl = url.parse(unescapedUrl);
|
||||
const title = path.basename(parsedUrl.pathname);
|
||||
|
||||
const imageService = require('../services/image.js');
|
||||
const {attachment} = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
|
||||
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
|
||||
|
||||
imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId;
|
||||
|
||||
@ -511,7 +513,7 @@ const downloadImagePromises = {};
|
||||
function replaceUrl(content, url, attachment) {
|
||||
const quotedUrl = utils.quoteRegex(url);
|
||||
|
||||
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/attachments/${encodeURIComponent(attachment.title)}/image"`);
|
||||
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/attachments/${attachment.attachmentId}/image/${encodeURIComponent(attachment.title)}"`);
|
||||
}
|
||||
|
||||
function downloadImages(noteId, content) {
|
||||
@ -636,6 +638,10 @@ function saveAttachments(note, content) {
|
||||
content = `${content.substr(0, attachmentMatch.index)}<a class="reference-link" href="#root/${note.noteId}?viewMode=attachments&attachmentId=${attachment.attachmentId}">${title}</a>${content.substr(attachmentMatch.index + attachmentMatch[0].length)}`;
|
||||
}
|
||||
|
||||
// removing absolute references to server to keep it working between instances,
|
||||
// we also omit / at the beginning to keep the paths relative
|
||||
content = content.replace(/src="[^"]*\/api\/attachments\//g, 'src="api/attachments/');
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@ -889,6 +895,11 @@ function scanForLinks(note, content) {
|
||||
* Things which have to be executed after updating content, but asynchronously (separate transaction)
|
||||
*/
|
||||
async function asyncPostProcessContent(note, content) {
|
||||
if (cls.isMigrationRunning()) {
|
||||
// this is rarely needed for migrations, but can cause trouble by e.g. triggering downloads
|
||||
return;
|
||||
}
|
||||
|
||||
if (note.hasStringContent() && !utils.isString(content)) {
|
||||
content = content.toString();
|
||||
}
|
||||
|
@ -63,10 +63,15 @@ function exec(opts) {
|
||||
}
|
||||
|
||||
let responseStr = '';
|
||||
let chunks = [];
|
||||
|
||||
response.on('data', chunk => responseStr += chunk);
|
||||
response.on('data', chunk => chunks.push(chunk));
|
||||
|
||||
response.on('end', () => {
|
||||
// use Buffer instead of string concatenation to avoid implicit decoding for each chunk
|
||||
// decode the entire data chunks explicitly as utf-8
|
||||
responseStr = Buffer.concat(chunks).toString('utf-8')
|
||||
|
||||
if ([200, 201, 204].includes(response.statusCode)) {
|
||||
try {
|
||||
const jsonObj = responseStr.trim() ? JSON.parse(responseStr) : null;
|
||||
|
@ -111,11 +111,7 @@ class NoteContentFulltextExp extends Expression {
|
||||
|
||||
if (type === 'text' && mime === 'text/html') {
|
||||
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 = striptags(content, ['a'], ' ');
|
||||
|
||||
// at least the closing tag can be easily stripped
|
||||
content = content.replace(/<\/a>/ig, "");
|
||||
content = this.stripTags(content);
|
||||
}
|
||||
|
||||
content = content.replace(/ /g, ' ');
|
||||
@ -123,6 +119,23 @@ class NoteContentFulltextExp extends Expression {
|
||||
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
stripTags(content) {
|
||||
// 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, "");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NoteContentFulltextExp;
|
||||
|
@ -73,7 +73,6 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext
|
||||
if (localEC?.isErased) {
|
||||
eraseEntity(remoteEC); // make sure it's erased anyway
|
||||
updateContext.alreadyErased++;
|
||||
return false; // we won't save entitychange in this case
|
||||
} else {
|
||||
eraseEntity(remoteEC);
|
||||
updateContext.erased++;
|
||||
@ -91,12 +90,17 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext
|
||||
updateContext.updated[remoteEC.entityName].push(remoteEC.entityId);
|
||||
}
|
||||
|
||||
if (!localEC || localEC.utcDateChanged < remoteEC.utcDateChanged || localEC.hash !== remoteEC.hash) {
|
||||
if (!localEC
|
||||
|| localEC.utcDateChanged < remoteEC.utcDateChanged
|
||||
|| localEC.hash !== remoteEC.hash
|
||||
|| localEC.isErased !== remoteEC.isErased
|
||||
) {
|
||||
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (localEC.hash !== remoteEC.hash && localEC.utcDateChanged > remoteEC.utcDateChanged) {
|
||||
} else if ((localEC.hash !== remoteEC.hash || localEC.isErased !== remoteEC.isErased)
|
||||
&& localEC.utcDateChanged > remoteEC.utcDateChanged) {
|
||||
// the change on our side is newer than on the other side, so the other side should update
|
||||
entityChangesService.putEntityChangeForOtherInstances(localEC);
|
||||
|
||||
@ -148,7 +152,7 @@ function eraseEntity(entityChange) {
|
||||
];
|
||||
|
||||
if (!entityNames.includes(entityName)) {
|
||||
log.error(`Cannot erase entity '${entityName}', id '${entityId}'.`);
|
||||
log.error(`Cannot erase ${entityName} '${entityId}'.`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,8 @@ function hashedBlobId(content) {
|
||||
|
||||
// we don't want such + and / in the IDs
|
||||
const kindaBase62Hash = base64Hash
|
||||
.replace('+', 'X')
|
||||
.replace('/', 'Y');
|
||||
.replaceAll('+', 'X')
|
||||
.replaceAll('/', 'Y');
|
||||
|
||||
// 20 characters of base62 gives us ~120 bit of entropy which is plenty enough
|
||||
return kindaBase62Hash.substr(0, 20);
|
||||
|
@ -105,10 +105,10 @@ function renderText(result, note) {
|
||||
|
||||
if (result.content.includes(`<span class="math-tex">`)) {
|
||||
result.header += `
|
||||
<script src="../../${assetPath}/libraries/katex/katex.min.js"></script>
|
||||
<link rel="stylesheet" href="../../${assetPath}/libraries/katex/katex.min.css">
|
||||
<script src="../../${assetPath}/libraries/katex/auto-render.min.js"></script>
|
||||
<script src="../../${assetPath}/libraries/katex/mhchem.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">
|
||||
<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>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.getElementById('content'));
|
||||
@ -137,7 +137,7 @@ function renderCode(result) {
|
||||
|
||||
function renderMermaid(result, note) {
|
||||
result.content = `
|
||||
<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">
|
||||
<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">
|
||||
<hr>
|
||||
<details>
|
||||
<summary>Chart source</summary>
|
||||
@ -146,7 +146,7 @@ function renderMermaid(result, note) {
|
||||
}
|
||||
|
||||
function renderImage(result, note) {
|
||||
result.content = `<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">`;
|
||||
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`;
|
||||
}
|
||||
|
||||
function renderFile(note, result) {
|
||||
|
@ -490,6 +490,10 @@ class SNote extends AbstractShacaEntity {
|
||||
return escape(this.title);
|
||||
}
|
||||
|
||||
get encodedTitle() {
|
||||
return encodeURIComponent(this.title);
|
||||
}
|
||||
|
||||
getPojo() {
|
||||
return {
|
||||
noteId: this.noteId,
|
||||
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
|
||||
<title>Trilium Notes</title>
|
||||
</head>
|
||||
<body class="desktop heading-style-<%= headingStyle %>">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="theme-color" content="#fff">
|
||||
<title>Trilium Notes</title>
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
|
||||
|
||||
<style>
|
||||
.lds-roller {
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
[[ ! -z "${USER_UID}" ]] && usermod -u ${USER_UID} node || echo "No USER_UID specified, leaving 1000"
|
||||
[[ ! -z "${USER_GID}" ]] && groupmod -g ${USER_GID} node || echo "No USER_GID specified, leaving 1000"
|
||||
[[ ! -z "${USER_GID}" ]] && groupmod -og ${USER_GID} node || echo "No USER_GID specified, leaving 1000"
|
||||
|
||||
chown -R node:node /home/node
|
||||
exec su-exec node node ./src/www
|
||||
|
Loading…
x
Reference in New Issue
Block a user