You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-01-05 00:12:33 +02:00
Compare commits
62 Commits
v2.4.4
...
theme_to_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99b5847244 | ||
|
|
4ad7e1b245 | ||
|
|
b6f889baca | ||
|
|
54fb4b0946 | ||
|
|
80762572cf | ||
|
|
8e5d209d3c | ||
|
|
f71dad6d09 | ||
|
|
f5891dfae8 | ||
|
|
736bbbd8ed | ||
|
|
973121addd | ||
|
|
95ad4c3177 | ||
|
|
71c470f59d | ||
|
|
c529b972e3 | ||
|
|
e6bff3f2e0 | ||
|
|
1bc674a1f9 | ||
|
|
f371bb8e59 | ||
|
|
7c85889c1f | ||
|
|
ab134807ea | ||
|
|
a5b3bb6058 | ||
|
|
8ab1cd984c | ||
|
|
e387d9a91b | ||
|
|
0793b1be59 | ||
|
|
19225abbcf | ||
|
|
85fa3288ab | ||
|
|
17f82c426a | ||
|
|
82331c9b93 | ||
|
|
886b6d1126 | ||
|
|
1a703c4ecd | ||
|
|
1126899769 | ||
|
|
a571d38862 | ||
|
|
4ab2fb73b7 | ||
|
|
78c7b79299 | ||
|
|
d97ba57dda | ||
|
|
9c44133bd0 | ||
|
|
dc008ecf64 | ||
|
|
93a4ad09bb | ||
|
|
9c1dc7898a | ||
|
|
6520a481ca | ||
|
|
5805a41249 | ||
|
|
b88b747ba6 | ||
|
|
b269c2fdb9 | ||
|
|
f42fd0ecce | ||
|
|
62c5f433d7 | ||
|
|
e73a4b7286 | ||
|
|
6c18c6ddc7 | ||
|
|
20d1f74ee4 | ||
|
|
2386abea3e | ||
|
|
90621a8417 | ||
|
|
b02baa6891 | ||
|
|
ee46978389 | ||
|
|
2b6b4dd916 | ||
|
|
f0361bf80d | ||
|
|
ecf718005d | ||
|
|
305d0ffc49 | ||
|
|
f454c4e33b | ||
|
|
047883bd27 | ||
|
|
f118f5250f | ||
|
|
61161039c8 | ||
|
|
00504898f2 | ||
|
|
0a6390ed96 | ||
|
|
f909fe6670 | ||
|
|
b17d8bc533 |
@@ -64,6 +64,7 @@ packages/tools/PortableAppsLauncher
|
||||
packages/fork-*
|
||||
plugin_types/
|
||||
readme/
|
||||
**/commands/index.ts
|
||||
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
packages/app-cli/app/LinkSelector.d.ts
|
||||
@@ -117,6 +118,12 @@ packages/app-desktop/InteropServiceHelper.js.map
|
||||
packages/app-desktop/app.d.ts
|
||||
packages/app-desktop/app.js
|
||||
packages/app-desktop/app.js.map
|
||||
packages/app-desktop/app.reducer.d.ts
|
||||
packages/app-desktop/app.reducer.js
|
||||
packages/app-desktop/app.reducer.js.map
|
||||
packages/app-desktop/app.reducer.test.d.ts
|
||||
packages/app-desktop/app.reducer.test.js
|
||||
packages/app-desktop/app.reducer.test.js.map
|
||||
packages/app-desktop/bridge.d.ts
|
||||
packages/app-desktop/bridge.js
|
||||
packages/app-desktop/bridge.js.map
|
||||
@@ -135,6 +142,9 @@ packages/app-desktop/commands/exportNotes.js.map
|
||||
packages/app-desktop/commands/focusElement.d.ts
|
||||
packages/app-desktop/commands/focusElement.js
|
||||
packages/app-desktop/commands/focusElement.js.map
|
||||
packages/app-desktop/commands/index.d.ts
|
||||
packages/app-desktop/commands/index.js
|
||||
packages/app-desktop/commands/index.js.map
|
||||
packages/app-desktop/commands/openProfileDirectory.d.ts
|
||||
packages/app-desktop/commands/openProfileDirectory.js
|
||||
packages/app-desktop/commands/openProfileDirectory.js.map
|
||||
@@ -240,6 +250,9 @@ packages/app-desktop/gui/MainScreen/commands/gotoAnything.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js
|
||||
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/index.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/index.js
|
||||
packages/app-desktop/gui/MainScreen/commands/index.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/moveToFolder.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js
|
||||
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js.map
|
||||
@@ -396,21 +409,24 @@ packages/app-desktop/gui/NoteEditor/NoteEditor.js.map
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js.map
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js.map
|
||||
packages/app-desktop/gui/NoteEditor/styles/index.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/styles/index.js
|
||||
packages/app-desktop/gui/NoteEditor/styles/index.js.map
|
||||
@@ -465,12 +481,18 @@ packages/app-desktop/gui/NoteList/NoteList.js.map
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.d.ts
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
|
||||
packages/app-desktop/gui/NoteList/commands/index.d.ts
|
||||
packages/app-desktop/gui/NoteList/commands/index.js
|
||||
packages/app-desktop/gui/NoteList/commands/index.js.map
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.js
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.d.ts
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js.map
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.d.ts
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.js.map
|
||||
packages/app-desktop/gui/NoteListItem.d.ts
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem.js.map
|
||||
@@ -567,12 +589,18 @@ packages/app-desktop/gui/Sidebar/Sidebar.js.map
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.d.ts
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js.map
|
||||
packages/app-desktop/gui/Sidebar/commands/index.d.ts
|
||||
packages/app-desktop/gui/Sidebar/commands/index.js
|
||||
packages/app-desktop/gui/Sidebar/commands/index.js.map
|
||||
packages/app-desktop/gui/Sidebar/styles/index.d.ts
|
||||
packages/app-desktop/gui/Sidebar/styles/index.js
|
||||
packages/app-desktop/gui/Sidebar/styles/index.js.map
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.d.ts
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js.map
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.d.ts
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.js
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.js.map
|
||||
@@ -861,6 +889,9 @@ packages/lib/JoplinServerApi.js.map
|
||||
packages/lib/Logger.d.ts
|
||||
packages/lib/Logger.js
|
||||
packages/lib/Logger.js.map
|
||||
packages/lib/ObjectUtils.d.ts
|
||||
packages/lib/ObjectUtils.js
|
||||
packages/lib/ObjectUtils.js.map
|
||||
packages/lib/PoorManIntervals.d.ts
|
||||
packages/lib/PoorManIntervals.js
|
||||
packages/lib/PoorManIntervals.js.map
|
||||
@@ -894,6 +925,9 @@ packages/lib/commands/historyBackward.js.map
|
||||
packages/lib/commands/historyForward.d.ts
|
||||
packages/lib/commands/historyForward.js
|
||||
packages/lib/commands/historyForward.js.map
|
||||
packages/lib/commands/index.d.ts
|
||||
packages/lib/commands/index.js
|
||||
packages/lib/commands/index.js.map
|
||||
packages/lib/commands/synchronize.d.ts
|
||||
packages/lib/commands/synchronize.js
|
||||
packages/lib/commands/synchronize.js.map
|
||||
@@ -936,6 +970,9 @@ packages/lib/fs-driver-node.js.map
|
||||
packages/lib/fsDriver.test.d.ts
|
||||
packages/lib/fsDriver.test.js
|
||||
packages/lib/fsDriver.test.js.map
|
||||
packages/lib/hooks/useAsyncEffect.d.ts
|
||||
packages/lib/hooks/useAsyncEffect.js
|
||||
packages/lib/hooks/useAsyncEffect.js.map
|
||||
packages/lib/hooks/useElementSize.d.ts
|
||||
packages/lib/hooks/useElementSize.js
|
||||
packages/lib/hooks/useElementSize.js.map
|
||||
@@ -990,6 +1027,9 @@ packages/lib/models/Folder.test.js.map
|
||||
packages/lib/models/ItemChange.d.ts
|
||||
packages/lib/models/ItemChange.js
|
||||
packages/lib/models/ItemChange.js.map
|
||||
packages/lib/models/ItemChange.test.d.ts
|
||||
packages/lib/models/ItemChange.test.js
|
||||
packages/lib/models/ItemChange.test.js.map
|
||||
packages/lib/models/MasterKey.d.ts
|
||||
packages/lib/models/MasterKey.js
|
||||
packages/lib/models/MasterKey.js.map
|
||||
@@ -1404,6 +1444,12 @@ packages/lib/services/rest/actionApi.desktop.js.map
|
||||
packages/lib/services/rest/routes/auth.d.ts
|
||||
packages/lib/services/rest/routes/auth.js
|
||||
packages/lib/services/rest/routes/auth.js.map
|
||||
packages/lib/services/rest/routes/events.d.ts
|
||||
packages/lib/services/rest/routes/events.js
|
||||
packages/lib/services/rest/routes/events.js.map
|
||||
packages/lib/services/rest/routes/events.test.d.ts
|
||||
packages/lib/services/rest/routes/events.test.js
|
||||
packages/lib/services/rest/routes/events.test.js.map
|
||||
packages/lib/services/rest/routes/folders.d.ts
|
||||
packages/lib/services/rest/routes/folders.js
|
||||
packages/lib/services/rest/routes/folders.js.map
|
||||
@@ -1485,6 +1531,21 @@ packages/lib/services/spellChecker/SpellCheckerService.js.map
|
||||
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.d.ts
|
||||
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
|
||||
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js.map
|
||||
packages/lib/services/style/cssToTheme.d.ts
|
||||
packages/lib/services/style/cssToTheme.js
|
||||
packages/lib/services/style/cssToTheme.js.map
|
||||
packages/lib/services/style/cssToTheme.test.d.ts
|
||||
packages/lib/services/style/cssToTheme.test.js
|
||||
packages/lib/services/style/cssToTheme.test.js.map
|
||||
packages/lib/services/style/loadCssToTheme.d.ts
|
||||
packages/lib/services/style/loadCssToTheme.js
|
||||
packages/lib/services/style/loadCssToTheme.js.map
|
||||
packages/lib/services/style/themeToCss.d.ts
|
||||
packages/lib/services/style/themeToCss.js
|
||||
packages/lib/services/style/themeToCss.js.map
|
||||
packages/lib/services/style/themeToCss.test.d.ts
|
||||
packages/lib/services/style/themeToCss.test.js
|
||||
packages/lib/services/style/themeToCss.test.js.map
|
||||
packages/lib/services/synchronizer/ItemUploader.d.ts
|
||||
packages/lib/services/synchronizer/ItemUploader.js
|
||||
packages/lib/services/synchronizer/ItemUploader.js.map
|
||||
@@ -1635,6 +1696,12 @@ packages/plugin-repo-cli/lib/gitCompareUrl.js.map
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.test.d.ts
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.test.js
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.test.js.map
|
||||
packages/plugin-repo-cli/lib/overrideUtils.d.ts
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js.map
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.d.ts
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js.map
|
||||
packages/plugin-repo-cli/lib/types.d.ts
|
||||
packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/types.js.map
|
||||
@@ -1644,6 +1711,9 @@ packages/plugin-repo-cli/lib/updateReadme.js.map
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.d.ts
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js.map
|
||||
packages/plugin-repo-cli/lib/utils.d.ts
|
||||
packages/plugin-repo-cli/lib/utils.js
|
||||
packages/plugin-repo-cli/lib/utils.js.map
|
||||
packages/plugins/ToggleSidebars/api/index.d.ts
|
||||
packages/plugins/ToggleSidebars/api/index.js
|
||||
packages/plugins/ToggleSidebars/api/index.js.map
|
||||
@@ -1716,6 +1786,9 @@ packages/renderer/MdToHtml/setupLinkify.js.map
|
||||
packages/renderer/MdToHtml/validateLinks.d.ts
|
||||
packages/renderer/MdToHtml/validateLinks.js
|
||||
packages/renderer/MdToHtml/validateLinks.js.map
|
||||
packages/renderer/headerAnchor.d.ts
|
||||
packages/renderer/headerAnchor.js
|
||||
packages/renderer/headerAnchor.js.map
|
||||
packages/renderer/htmlUtils.d.ts
|
||||
packages/renderer/htmlUtils.js
|
||||
packages/renderer/htmlUtils.js.map
|
||||
@@ -1734,6 +1807,9 @@ packages/renderer/utils.js.map
|
||||
packages/tools/buildServerDocker.d.ts
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.js.map
|
||||
packages/tools/convertThemesToCss.d.ts
|
||||
packages/tools/convertThemesToCss.js
|
||||
packages/tools/convertThemesToCss.js.map
|
||||
packages/tools/generate-database-types.d.ts
|
||||
packages/tools/generate-database-types.js
|
||||
packages/tools/generate-database-types.js.map
|
||||
|
||||
8
.github/workflows/github-actions-main.yml
vendored
8
.github/workflows/github-actions-main.yml
vendored
@@ -35,6 +35,14 @@ jobs:
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# the next line enables multi-architecture support for docker, it basically makes it use qemu for non native platforms
|
||||
# See https://hub.docker.com/r/tonistiigi/binfmt for more info
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
|
||||
# this just prints the info about what platforms are supported in the builder (can help debugging if something isn't working right)
|
||||
# and also proves the above worked properly
|
||||
sudo docker buildx ls
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: olegtarasov/get-tag@v2.1
|
||||
- uses: actions/setup-node@v2
|
||||
|
||||
82
.gitignore
vendored
82
.gitignore
vendored
@@ -49,6 +49,7 @@ packages/tools/commit_hook.txt
|
||||
packages/tools/github_oauth_token.txt
|
||||
lerna-debug.log
|
||||
.env
|
||||
docs/**/*.mustache
|
||||
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
packages/app-cli/app/LinkSelector.d.ts
|
||||
@@ -102,6 +103,12 @@ packages/app-desktop/InteropServiceHelper.js.map
|
||||
packages/app-desktop/app.d.ts
|
||||
packages/app-desktop/app.js
|
||||
packages/app-desktop/app.js.map
|
||||
packages/app-desktop/app.reducer.d.ts
|
||||
packages/app-desktop/app.reducer.js
|
||||
packages/app-desktop/app.reducer.js.map
|
||||
packages/app-desktop/app.reducer.test.d.ts
|
||||
packages/app-desktop/app.reducer.test.js
|
||||
packages/app-desktop/app.reducer.test.js.map
|
||||
packages/app-desktop/bridge.d.ts
|
||||
packages/app-desktop/bridge.js
|
||||
packages/app-desktop/bridge.js.map
|
||||
@@ -120,6 +127,9 @@ packages/app-desktop/commands/exportNotes.js.map
|
||||
packages/app-desktop/commands/focusElement.d.ts
|
||||
packages/app-desktop/commands/focusElement.js
|
||||
packages/app-desktop/commands/focusElement.js.map
|
||||
packages/app-desktop/commands/index.d.ts
|
||||
packages/app-desktop/commands/index.js
|
||||
packages/app-desktop/commands/index.js.map
|
||||
packages/app-desktop/commands/openProfileDirectory.d.ts
|
||||
packages/app-desktop/commands/openProfileDirectory.js
|
||||
packages/app-desktop/commands/openProfileDirectory.js.map
|
||||
@@ -225,6 +235,9 @@ packages/app-desktop/gui/MainScreen/commands/gotoAnything.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js
|
||||
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/index.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/index.js
|
||||
packages/app-desktop/gui/MainScreen/commands/index.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/moveToFolder.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js
|
||||
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js.map
|
||||
@@ -381,21 +394,24 @@ packages/app-desktop/gui/NoteEditor/NoteEditor.js.map
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js.map
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js.map
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js.map
|
||||
packages/app-desktop/gui/NoteEditor/styles/index.d.ts
|
||||
packages/app-desktop/gui/NoteEditor/styles/index.js
|
||||
packages/app-desktop/gui/NoteEditor/styles/index.js.map
|
||||
@@ -450,12 +466,18 @@ packages/app-desktop/gui/NoteList/NoteList.js.map
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.d.ts
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
|
||||
packages/app-desktop/gui/NoteList/commands/index.d.ts
|
||||
packages/app-desktop/gui/NoteList/commands/index.js
|
||||
packages/app-desktop/gui/NoteList/commands/index.js.map
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.js
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.d.ts
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js.map
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.d.ts
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.js.map
|
||||
packages/app-desktop/gui/NoteListItem.d.ts
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem.js.map
|
||||
@@ -552,12 +574,18 @@ packages/app-desktop/gui/Sidebar/Sidebar.js.map
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.d.ts
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js.map
|
||||
packages/app-desktop/gui/Sidebar/commands/index.d.ts
|
||||
packages/app-desktop/gui/Sidebar/commands/index.js
|
||||
packages/app-desktop/gui/Sidebar/commands/index.js.map
|
||||
packages/app-desktop/gui/Sidebar/styles/index.d.ts
|
||||
packages/app-desktop/gui/Sidebar/styles/index.js
|
||||
packages/app-desktop/gui/Sidebar/styles/index.js.map
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.d.ts
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js.map
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.d.ts
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.js
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.js.map
|
||||
@@ -846,6 +874,9 @@ packages/lib/JoplinServerApi.js.map
|
||||
packages/lib/Logger.d.ts
|
||||
packages/lib/Logger.js
|
||||
packages/lib/Logger.js.map
|
||||
packages/lib/ObjectUtils.d.ts
|
||||
packages/lib/ObjectUtils.js
|
||||
packages/lib/ObjectUtils.js.map
|
||||
packages/lib/PoorManIntervals.d.ts
|
||||
packages/lib/PoorManIntervals.js
|
||||
packages/lib/PoorManIntervals.js.map
|
||||
@@ -879,6 +910,9 @@ packages/lib/commands/historyBackward.js.map
|
||||
packages/lib/commands/historyForward.d.ts
|
||||
packages/lib/commands/historyForward.js
|
||||
packages/lib/commands/historyForward.js.map
|
||||
packages/lib/commands/index.d.ts
|
||||
packages/lib/commands/index.js
|
||||
packages/lib/commands/index.js.map
|
||||
packages/lib/commands/synchronize.d.ts
|
||||
packages/lib/commands/synchronize.js
|
||||
packages/lib/commands/synchronize.js.map
|
||||
@@ -921,6 +955,9 @@ packages/lib/fs-driver-node.js.map
|
||||
packages/lib/fsDriver.test.d.ts
|
||||
packages/lib/fsDriver.test.js
|
||||
packages/lib/fsDriver.test.js.map
|
||||
packages/lib/hooks/useAsyncEffect.d.ts
|
||||
packages/lib/hooks/useAsyncEffect.js
|
||||
packages/lib/hooks/useAsyncEffect.js.map
|
||||
packages/lib/hooks/useElementSize.d.ts
|
||||
packages/lib/hooks/useElementSize.js
|
||||
packages/lib/hooks/useElementSize.js.map
|
||||
@@ -975,6 +1012,9 @@ packages/lib/models/Folder.test.js.map
|
||||
packages/lib/models/ItemChange.d.ts
|
||||
packages/lib/models/ItemChange.js
|
||||
packages/lib/models/ItemChange.js.map
|
||||
packages/lib/models/ItemChange.test.d.ts
|
||||
packages/lib/models/ItemChange.test.js
|
||||
packages/lib/models/ItemChange.test.js.map
|
||||
packages/lib/models/MasterKey.d.ts
|
||||
packages/lib/models/MasterKey.js
|
||||
packages/lib/models/MasterKey.js.map
|
||||
@@ -1389,6 +1429,12 @@ packages/lib/services/rest/actionApi.desktop.js.map
|
||||
packages/lib/services/rest/routes/auth.d.ts
|
||||
packages/lib/services/rest/routes/auth.js
|
||||
packages/lib/services/rest/routes/auth.js.map
|
||||
packages/lib/services/rest/routes/events.d.ts
|
||||
packages/lib/services/rest/routes/events.js
|
||||
packages/lib/services/rest/routes/events.js.map
|
||||
packages/lib/services/rest/routes/events.test.d.ts
|
||||
packages/lib/services/rest/routes/events.test.js
|
||||
packages/lib/services/rest/routes/events.test.js.map
|
||||
packages/lib/services/rest/routes/folders.d.ts
|
||||
packages/lib/services/rest/routes/folders.js
|
||||
packages/lib/services/rest/routes/folders.js.map
|
||||
@@ -1470,6 +1516,21 @@ packages/lib/services/spellChecker/SpellCheckerService.js.map
|
||||
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.d.ts
|
||||
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
|
||||
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js.map
|
||||
packages/lib/services/style/cssToTheme.d.ts
|
||||
packages/lib/services/style/cssToTheme.js
|
||||
packages/lib/services/style/cssToTheme.js.map
|
||||
packages/lib/services/style/cssToTheme.test.d.ts
|
||||
packages/lib/services/style/cssToTheme.test.js
|
||||
packages/lib/services/style/cssToTheme.test.js.map
|
||||
packages/lib/services/style/loadCssToTheme.d.ts
|
||||
packages/lib/services/style/loadCssToTheme.js
|
||||
packages/lib/services/style/loadCssToTheme.js.map
|
||||
packages/lib/services/style/themeToCss.d.ts
|
||||
packages/lib/services/style/themeToCss.js
|
||||
packages/lib/services/style/themeToCss.js.map
|
||||
packages/lib/services/style/themeToCss.test.d.ts
|
||||
packages/lib/services/style/themeToCss.test.js
|
||||
packages/lib/services/style/themeToCss.test.js.map
|
||||
packages/lib/services/synchronizer/ItemUploader.d.ts
|
||||
packages/lib/services/synchronizer/ItemUploader.js
|
||||
packages/lib/services/synchronizer/ItemUploader.js.map
|
||||
@@ -1620,6 +1681,12 @@ packages/plugin-repo-cli/lib/gitCompareUrl.js.map
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.test.d.ts
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.test.js
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.test.js.map
|
||||
packages/plugin-repo-cli/lib/overrideUtils.d.ts
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js.map
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.d.ts
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js.map
|
||||
packages/plugin-repo-cli/lib/types.d.ts
|
||||
packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/types.js.map
|
||||
@@ -1629,6 +1696,9 @@ packages/plugin-repo-cli/lib/updateReadme.js.map
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.d.ts
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js.map
|
||||
packages/plugin-repo-cli/lib/utils.d.ts
|
||||
packages/plugin-repo-cli/lib/utils.js
|
||||
packages/plugin-repo-cli/lib/utils.js.map
|
||||
packages/plugins/ToggleSidebars/api/index.d.ts
|
||||
packages/plugins/ToggleSidebars/api/index.js
|
||||
packages/plugins/ToggleSidebars/api/index.js.map
|
||||
@@ -1701,6 +1771,9 @@ packages/renderer/MdToHtml/setupLinkify.js.map
|
||||
packages/renderer/MdToHtml/validateLinks.d.ts
|
||||
packages/renderer/MdToHtml/validateLinks.js
|
||||
packages/renderer/MdToHtml/validateLinks.js.map
|
||||
packages/renderer/headerAnchor.d.ts
|
||||
packages/renderer/headerAnchor.js
|
||||
packages/renderer/headerAnchor.js.map
|
||||
packages/renderer/htmlUtils.d.ts
|
||||
packages/renderer/htmlUtils.js
|
||||
packages/renderer/htmlUtils.js.map
|
||||
@@ -1719,6 +1792,9 @@ packages/renderer/utils.js.map
|
||||
packages/tools/buildServerDocker.d.ts
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.js.map
|
||||
packages/tools/convertThemesToCss.d.ts
|
||||
packages/tools/convertThemesToCss.js
|
||||
packages/tools/convertThemesToCss.js.map
|
||||
packages/tools/generate-database-types.d.ts
|
||||
packages/tools/generate-database-types.js
|
||||
packages/tools/generate-database-types.js.map
|
||||
|
||||
@@ -29,7 +29,7 @@ ol, ul {
|
||||
#main-container {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 225px;
|
||||
padding-bottom: 127px; /* Needs to be the height of the footer */
|
||||
}
|
||||
|
||||
.help-page-container img {
|
||||
@@ -583,7 +583,7 @@ div.navbar-mobile-content a.sponsor-button {
|
||||
|
||||
/* footer section */
|
||||
footer {
|
||||
padding-top: 50px;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -594,6 +594,7 @@ footer a,
|
||||
footer p {
|
||||
color: #90b1d9;
|
||||
text-decoration: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
@@ -617,6 +618,35 @@ footer .right-links a {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
footer .footer-right {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
footer .social-links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #315885;
|
||||
}
|
||||
|
||||
footer .social-links a:hover {
|
||||
color: #e8f3ff;
|
||||
}
|
||||
|
||||
footer .social-links i {
|
||||
margin-left: 15px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
footer .bottom-links-row {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
footer .bottom-links-row p {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/*****************************************************************
|
||||
IN THE PRESS
|
||||
The "In the press" section height needs to be adjusted as the
|
||||
@@ -679,7 +709,7 @@ footer .right-links a {
|
||||
#main-container {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 260px;
|
||||
padding-bottom: 130px;
|
||||
}
|
||||
|
||||
.front-page h1 {
|
||||
@@ -866,7 +896,7 @@ footer .right-links a {
|
||||
|
||||
.help-page-container {
|
||||
padding-top: 90px;
|
||||
padding-bottom: 50px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.donate-links {
|
||||
@@ -880,7 +910,7 @@ footer .right-links a {
|
||||
border-radius: 20px;
|
||||
padding: 30px 20px;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 50px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
@@ -923,6 +953,11 @@ footer .right-links a {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.plan-group .joplin-cloud-login-info {
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plan-group .plan-price-yearly-per-year {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -368,40 +368,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="darkblue-bg">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-3 d-none d-md-block">
|
||||
<img src="{{imageBaseUrl}}/logo-text.svg" alt="" width="150" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<a href="{{baseUrl}}">
|
||||
<img
|
||||
src="{{imageBaseUrl}}/logo-text.svg"
|
||||
width="120"
|
||||
class="img-center d-block d-md-none"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
<br class="d-block d-md-none" />
|
||||
<p class="text-center-sm">Copyright © 2016-{{yyyy}} Laurent Cozic</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p class="text-right text-center-sm right-links">
|
||||
<a href="https://github.com/laurent22/joplin/" class="github-link"><i class="fab fa-github"></i> GitHub Repository</a>
|
||||
<a href="{{baseUrl}}/privacy/">Privacy Policy</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{> footer}}
|
||||
</div>
|
||||
|
||||
<script
|
||||
|
||||
@@ -75,38 +75,8 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="darkblue-bg">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-3 d-none d-md-block">
|
||||
<img src="{{imageBaseUrl}}/logo-text.svg" alt="" width="150" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<img
|
||||
src="{{imageBaseUrl}}/logo-text.svg"
|
||||
width="120"
|
||||
class="img-center d-block d-md-none"
|
||||
alt=""
|
||||
/>
|
||||
<br class="d-block d-md-none" />
|
||||
<p class="text-center-sm">Copyright © 2016-{{yyyy}} Laurent Cozic</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 right-links">
|
||||
<p class="text-right text-center-sm">
|
||||
<a href="https://github.com/laurent22/joplin/" class="github-link"><i class="fab fa-github"></i> GitHub Repository</a>
|
||||
<a href="{{baseUrl}}/privacy/">Privacy Policy</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{{> footer}}
|
||||
</div>
|
||||
|
||||
<script src="{{jsBaseUrl}}/script.js?t={{buildTime}}"></script>
|
||||
|
||||
26
Assets/WebsiteAssets/templates/partials/footer.mustache
Normal file
26
Assets/WebsiteAssets/templates/partials/footer.mustache
Normal file
@@ -0,0 +1,26 @@
|
||||
<footer class="darkblue-bg">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-12 social-links">
|
||||
<a href="https://twitter.com/joplinapp" title="Twitter feed"><i class="fab fa-twitter"></i></a>
|
||||
<a href="https://github.com/laurent22/joplin/" title="GitHub repository"><i class="fab fa-github"></i></a>
|
||||
<a href="https://www.patreon.com/joplin" title="Patreon blog"><i class="fab fa-patreon"></i></a>
|
||||
<a href="https://discordapp.com/invite/d2HMPwE" title="Discord chat"><i class="fab fa-discord"></i></a>
|
||||
<a href="https://www.reddit.com/r/joplinapp/" title="Subreddit"><i class="fab fa-reddit"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-links-row">
|
||||
<div class="col-12 col-md-6">
|
||||
<p class="text-center-sm">Copyright © 2016-{{yyyy}} Laurent Cozic</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p class="text-right text-center-sm right-links">
|
||||
<span class="footer-right">
|
||||
<a href="{{baseUrl}}/privacy/">Privacy Policy</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -6,7 +6,7 @@
|
||||
Joplin Cloud <span class="frame-bg frame-bg-yellow">plans</span>
|
||||
</h1>
|
||||
<p class="text-center sub-title">
|
||||
Joplin Cloud allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
|
||||
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,6 +45,8 @@
|
||||
{{#plans.business}}
|
||||
{{> plan}}
|
||||
{{/plans.business}}
|
||||
|
||||
<p class="joplin-cloud-login-info">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
@@ -184,7 +184,7 @@ if command -v lsb_release &> /dev/null; then
|
||||
# Check for "The SUID sandbox helper binary was found, but is not configured correctly" problem.
|
||||
# It is present in Debian 1X. A (temporary) patch will be applied at .desktop file
|
||||
# Linux Mint 4 Debbie is based on Debian 10 and requires the same param handling.
|
||||
if [ $DISTVER =~ Debian1. ] || [ "$DISTVER" = "Linuxmint4" ] && [ "$DISTCODENAME" = "debbie" ]
|
||||
if [[ $DISTVER =~ Debian1. ]] || [ "$DISTVER" = "Linuxmint4" ] && [ "$DISTCODENAME" = "debbie" ]
|
||||
then
|
||||
SANDBOXPARAM=" --no-sandbox"
|
||||
fi
|
||||
|
||||
@@ -5,6 +5,7 @@ const tasks = {
|
||||
// copyLib: require('./packages/tools/gulp/tasks/copyLib'),
|
||||
// tsc: require('./packages/tools/gulp/tasks/tsc'),
|
||||
updateIgnoredTypeScriptBuild: require('./packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
|
||||
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
|
||||
// deleteBuildDirs: require('./packages/tools/gulp/tasks/deleteBuildDirs'),
|
||||
completePublishAll: {
|
||||
fn: async () => {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"bootstrapServerOnly": "lerna bootstrap --force-local --no-ci --include-dependents --include-dependencies --scope @joplin/server",
|
||||
"build": "lerna run build && npm run tsc",
|
||||
"buildApiDoc": "npm start --prefix=packages/app-cli -- apidoc ../../readme/api/references/rest_api.md",
|
||||
"buildCommandIndex": "gulp buildCommandIndex",
|
||||
"buildDoc": "./packages/tools/build-all.sh",
|
||||
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out docs/api/references/plugin_api packages/lib/services/plugins/api/",
|
||||
"buildSettingJsonSchema": "npm start --prefix=packages/app-cli -- settingschema ../../docs/schema/settings.json",
|
||||
|
||||
@@ -381,6 +381,30 @@ async function fetchAllNotes() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const tableFields = reg.db().tableFields('item_changes', { includeDescription: true });
|
||||
|
||||
lines.push('# Events');
|
||||
lines.push('');
|
||||
lines.push('This end point can be used to retrieve the latest note changes. Currently only note changes are tracked.');
|
||||
lines.push('');
|
||||
lines.push('## Properties');
|
||||
lines.push('');
|
||||
lines.push(this.createPropertiesTable(tableFields));
|
||||
lines.push('');
|
||||
lines.push('## GET /events');
|
||||
lines.push('');
|
||||
lines.push('Returns a paginated list of recent events. A `cursor` property should be provided, which tells from what point in time the events should be returned. The API will return a `cursor` property, to tell from where to resume retrieving events, as well as an `has_more` (tells if more changes can be retrieved) and `items` property, which will contain the list of events. Events are kept for up to 90 days.');
|
||||
lines.push('');
|
||||
lines.push('If no `cursor` property is provided, the API will respond with the latest change ID. That can be used to retrieve future events later on.');
|
||||
lines.push('');
|
||||
lines.push('The results are paginated so will need to may multiple calls to retrieve all the events. Use the `has_more` property to know if more can be retrieved.');
|
||||
lines.push('');
|
||||
lines.push('## GET /events/:id');
|
||||
lines.push('');
|
||||
lines.push('Returns the event with the given ID.');
|
||||
}
|
||||
|
||||
const outFilePath = args['file'];
|
||||
|
||||
await shim.fsDriver().writeFile(outFilePath, lines.join('\n'), 'utf8');
|
||||
|
||||
@@ -237,7 +237,7 @@ export default class ElectronAppWrapper {
|
||||
const iid = setInterval(() => {
|
||||
if (this.electronApp().isReady()) {
|
||||
clearInterval(iid);
|
||||
resolve();
|
||||
resolve(null);
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
47
packages/app-desktop/app.reducer.test.ts
Normal file
47
packages/app-desktop/app.reducer.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { AppState } from './app.reducer';
|
||||
import appReducer, { createAppDefaultState } from './app.reducer';
|
||||
|
||||
describe('app.reducer', function() {
|
||||
|
||||
it('DIALOG_OPEN', async () => {
|
||||
const state: AppState = createAppDefaultState({}, {});
|
||||
|
||||
let newState = appReducer(state, {
|
||||
type: 'DIALOG_OPEN',
|
||||
name: 'syncWizard',
|
||||
});
|
||||
|
||||
expect(newState.dialogs.length).toBe(1);
|
||||
expect(newState.dialogs[0].name).toBe('syncWizard');
|
||||
|
||||
expect(() => appReducer(newState, {
|
||||
type: 'DIALOG_OPEN',
|
||||
name: 'syncWizard',
|
||||
})).toThrow();
|
||||
|
||||
newState = appReducer(newState, {
|
||||
type: 'DIALOG_CLOSE',
|
||||
name: 'syncWizard',
|
||||
});
|
||||
|
||||
expect(newState.dialogs.length).toBe(0);
|
||||
|
||||
expect(() => appReducer(newState, {
|
||||
type: 'DIALOG_CLOSE',
|
||||
name: 'syncWizard',
|
||||
})).toThrow();
|
||||
|
||||
newState = appReducer(newState, {
|
||||
type: 'DIALOG_OPEN',
|
||||
name: 'syncWizard',
|
||||
});
|
||||
|
||||
newState = appReducer(newState, {
|
||||
type: 'DIALOG_OPEN',
|
||||
name: 'setPassword',
|
||||
});
|
||||
|
||||
expect(newState.dialogs).toEqual([{ name: 'syncWizard' }, { name: 'setPassword' }]);
|
||||
});
|
||||
|
||||
});
|
||||
316
packages/app-desktop/app.reducer.ts
Normal file
316
packages/app-desktop/app.reducer.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import produce from 'immer';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { defaultState, State } from '@joplin/lib/reducer';
|
||||
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
|
||||
import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
||||
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
||||
|
||||
export interface AppStateRoute {
|
||||
type: string;
|
||||
routeName: string;
|
||||
props: any;
|
||||
}
|
||||
|
||||
export enum AppStateDialogName {
|
||||
SyncWizard = 'syncWizard',
|
||||
MasterPassword = 'masterPassword',
|
||||
}
|
||||
|
||||
export interface AppStateDialog {
|
||||
name: AppStateDialogName;
|
||||
}
|
||||
|
||||
export interface AppState extends State {
|
||||
route: AppStateRoute;
|
||||
navHistory: any[];
|
||||
noteVisiblePanes: string[];
|
||||
windowContentSize: any;
|
||||
watchedNoteFiles: string[];
|
||||
lastEditorScrollPercents: any;
|
||||
devToolsVisible: boolean;
|
||||
visibleDialogs: any; // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: string;
|
||||
layoutMoveMode: boolean;
|
||||
startupPluginsLoaded: boolean;
|
||||
|
||||
// Extra reducer keys go here
|
||||
watchedResources: any;
|
||||
mainLayout: LayoutItem;
|
||||
dialogs: AppStateDialog[];
|
||||
}
|
||||
|
||||
export function createAppDefaultState(windowContentSize: any, resourceEditWatcherDefaultState: any): AppState {
|
||||
return {
|
||||
...defaultState,
|
||||
route: {
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Main',
|
||||
props: {},
|
||||
},
|
||||
navHistory: [],
|
||||
noteVisiblePanes: ['editor', 'viewer'],
|
||||
windowContentSize, // bridge().windowContentSize(),
|
||||
watchedNoteFiles: [],
|
||||
lastEditorScrollPercents: {},
|
||||
devToolsVisible: false,
|
||||
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: null,
|
||||
layoutMoveMode: false,
|
||||
mainLayout: null,
|
||||
startupPluginsLoaded: false,
|
||||
dialogs: [],
|
||||
...resourceEditWatcherDefaultState,
|
||||
};
|
||||
}
|
||||
|
||||
export default function(state: AppState, action: any) {
|
||||
let newState = state;
|
||||
|
||||
try {
|
||||
switch (action.type) {
|
||||
|
||||
case 'NAV_BACK':
|
||||
case 'NAV_GO':
|
||||
|
||||
{
|
||||
const goingBack = action.type === 'NAV_BACK';
|
||||
|
||||
if (goingBack && !state.navHistory.length) break;
|
||||
|
||||
const currentRoute = state.route;
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
const newNavHistory = state.navHistory.slice();
|
||||
|
||||
if (goingBack) {
|
||||
let newAction = null;
|
||||
while (newNavHistory.length) {
|
||||
newAction = newNavHistory.pop();
|
||||
if (newAction.routeName !== state.route.routeName) break;
|
||||
}
|
||||
|
||||
if (!newAction) break;
|
||||
|
||||
action = newAction;
|
||||
}
|
||||
|
||||
if (!goingBack) newNavHistory.push(currentRoute);
|
||||
newState.navHistory = newNavHistory;
|
||||
newState.route = action;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'STARTUP_PLUGINS_LOADED':
|
||||
|
||||
// When all startup plugins have loaded, we also recreate the
|
||||
// main layout to ensure that it is updated in the UI. There's
|
||||
// probably a cleaner way to do this, but for now that will do.
|
||||
if (state.startupPluginsLoaded !== action.value) {
|
||||
newState = {
|
||||
...newState,
|
||||
startupPluginsLoaded: action.value,
|
||||
mainLayout: JSON.parse(JSON.stringify(newState.mainLayout)),
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WINDOW_CONTENT_SIZE_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.windowContentSize = action.size;
|
||||
break;
|
||||
|
||||
case 'NOTE_VISIBLE_PANES_TOGGLE':
|
||||
|
||||
{
|
||||
const getNextLayout = (currentLayout: any) => {
|
||||
currentLayout = panes.length === 2 ? 'both' : currentLayout[0];
|
||||
|
||||
let paneOptions;
|
||||
if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_VIEWER) {
|
||||
paneOptions = ['editor', 'viewer'];
|
||||
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_SPLIT) {
|
||||
paneOptions = ['editor', 'both'];
|
||||
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
|
||||
paneOptions = ['viewer', 'both'];
|
||||
} else {
|
||||
paneOptions = ['editor', 'viewer', 'both'];
|
||||
}
|
||||
|
||||
const currentLayoutIndex = paneOptions.indexOf(currentLayout);
|
||||
const nextLayoutIndex = currentLayoutIndex === paneOptions.length - 1 ? 0 : currentLayoutIndex + 1;
|
||||
|
||||
const nextLayout = paneOptions[nextLayoutIndex];
|
||||
return nextLayout === 'both' ? ['editor', 'viewer'] : [nextLayout];
|
||||
};
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
|
||||
const panes = state.noteVisiblePanes.slice();
|
||||
newState.noteVisiblePanes = getNextLayout(panes);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_VISIBLE_PANES_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteVisiblePanes = action.panes;
|
||||
break;
|
||||
|
||||
case 'MAIN_LAYOUT_SET':
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: action.value,
|
||||
};
|
||||
break;
|
||||
|
||||
case 'MAIN_LAYOUT_SET_ITEM_PROP':
|
||||
|
||||
{
|
||||
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
|
||||
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||
if (item.key === action.itemKey) {
|
||||
(item as any)[action.propName] = action.propValue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: newLayout,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_ADD':
|
||||
|
||||
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
|
||||
newState = Object.assign({}, state);
|
||||
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
||||
watchedNoteFiles.push(action.id);
|
||||
newState.watchedNoteFiles = watchedNoteFiles;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_REMOVE':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
const idx = newState.watchedNoteFiles.indexOf(action.id);
|
||||
if (idx >= 0) {
|
||||
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
||||
watchedNoteFiles.splice(idx, 1);
|
||||
newState.watchedNoteFiles = watchedNoteFiles;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_CLEAR':
|
||||
|
||||
if (state.watchedNoteFiles.length) {
|
||||
newState = Object.assign({}, state);
|
||||
newState.watchedNoteFiles = [];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EDITOR_SCROLL_PERCENT_SET':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
|
||||
newPercents[action.noteId] = action.percent;
|
||||
newState.lastEditorScrollPercents = newPercents;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_TOGGLE':
|
||||
newState = Object.assign({}, state);
|
||||
newState.devToolsVisible = !newState.devToolsVisible;
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_SET':
|
||||
newState = Object.assign({}, state);
|
||||
newState.devToolsVisible = action.value;
|
||||
break;
|
||||
|
||||
case 'VISIBLE_DIALOGS_ADD':
|
||||
newState = Object.assign({}, state);
|
||||
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
||||
newState.visibleDialogs[action.name] = true;
|
||||
break;
|
||||
|
||||
case 'VISIBLE_DIALOGS_REMOVE':
|
||||
newState = Object.assign({}, state);
|
||||
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
||||
delete newState.visibleDialogs[action.name];
|
||||
break;
|
||||
|
||||
case 'FOCUS_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.focusedField = action.field;
|
||||
break;
|
||||
|
||||
case 'FOCUS_CLEAR':
|
||||
|
||||
// A field can only clear its own state
|
||||
if (action.field === state.focusedField) {
|
||||
newState = Object.assign({}, state);
|
||||
newState.focusedField = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DIALOG_OPEN':
|
||||
case 'DIALOG_CLOSE':
|
||||
|
||||
{
|
||||
let isOpen = true;
|
||||
|
||||
if (action.type === 'DIALOG_CLOSE') {
|
||||
isOpen = false;
|
||||
} else { // DIALOG_OPEN
|
||||
isOpen = action.isOpen !== false;
|
||||
}
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
|
||||
if (isOpen) {
|
||||
const newDialogs = newState.dialogs.slice();
|
||||
|
||||
if (newDialogs.find(d => d.name === action.name)) throw new Error(`Trying to open a dialog is already open: ${action.name}`);
|
||||
|
||||
newDialogs.push({
|
||||
name: action.name,
|
||||
});
|
||||
|
||||
newState.dialogs = newDialogs;
|
||||
} else {
|
||||
if (!newState.dialogs.find(d => d.name === action.name)) throw new Error(`Trying to close a dialog that is not open: ${action.name}`);
|
||||
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
|
||||
newState.dialogs = newDialogs;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'LAYOUT_MOVE_MODE_SET':
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
layoutMoveMode: action.value,
|
||||
};
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import CommandService from '@joplin/lib/services/CommandService';
|
||||
import KeymapService from '@joplin/lib/services/KeymapService';
|
||||
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import resourceEditWatcherReducer, { defaultState as resourceEditWatcherDefaultState } from '@joplin/lib/services/ResourceEditWatcher/reducer';
|
||||
import { defaultState, State } from '@joplin/lib/reducer';
|
||||
import PluginRunner from './services/plugins/PluginRunner';
|
||||
import PlatformImplementation from './services/plugins/PlatformImplementation';
|
||||
import shim from '@joplin/lib/shim';
|
||||
@@ -19,13 +18,10 @@ import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerS
|
||||
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
|
||||
import bridge from './services/bridge';
|
||||
import menuCommandNames from './gui/menuCommandNames';
|
||||
import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
||||
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
|
||||
import ResourceService from '@joplin/lib/services/ResourceService';
|
||||
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
||||
import produce from 'immer';
|
||||
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
|
||||
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
||||
import appReducer, { createAppDefaultState } from './app.reducer';
|
||||
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const fs = require('fs-extra');
|
||||
@@ -40,125 +36,38 @@ const PluginManager = require('@joplin/lib/services/PluginManager');
|
||||
import RevisionService from '@joplin/lib/services/RevisionService';
|
||||
import MigrationService from '@joplin/lib/services/MigrationService';
|
||||
import { loadCustomCss, injectCustomStyles } from '@joplin/lib/CssUtils';
|
||||
import mainScreenCommands from './gui/MainScreen/commands/index';
|
||||
import noteEditorCommands from './gui/NoteEditor/commands/index';
|
||||
import noteListCommands from './gui/NoteList/commands/index';
|
||||
import noteListControlsCommands from './gui/NoteListControls/commands/index';
|
||||
import sidebarCommands from './gui/Sidebar/commands/index';
|
||||
import appCommands from './commands/index';
|
||||
import libCommands from '@joplin/lib/commands/index';
|
||||
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
|
||||
|
||||
const commands = [
|
||||
require('./gui/MainScreen/commands/editAlarm'),
|
||||
require('./gui/MainScreen/commands/exportPdf'),
|
||||
require('./gui/MainScreen/commands/gotoAnything'),
|
||||
require('./gui/MainScreen/commands/commandPalette'),
|
||||
require('./gui/MainScreen/commands/hideModalMessage'),
|
||||
require('./gui/MainScreen/commands/moveToFolder'),
|
||||
require('./gui/MainScreen/commands/newFolder'),
|
||||
require('./gui/MainScreen/commands/newNote'),
|
||||
require('./gui/MainScreen/commands/newSubFolder'),
|
||||
require('./gui/MainScreen/commands/newTodo'),
|
||||
require('./gui/MainScreen/commands/openFolder'),
|
||||
require('./gui/MainScreen/commands/openNote'),
|
||||
require('./gui/MainScreen/commands/openTag'),
|
||||
require('./gui/MainScreen/commands/print'),
|
||||
require('./gui/MainScreen/commands/renameFolder'),
|
||||
require('./gui/MainScreen/commands/renameTag'),
|
||||
require('./gui/MainScreen/commands/search'),
|
||||
require('./gui/MainScreen/commands/setTags'),
|
||||
require('./gui/MainScreen/commands/showModalMessage'),
|
||||
require('./gui/MainScreen/commands/showNoteContentProperties'),
|
||||
require('./gui/MainScreen/commands/showNoteProperties'),
|
||||
require('./gui/MainScreen/commands/showPrompt'),
|
||||
require('./gui/MainScreen/commands/showShareFolderDialog'),
|
||||
require('./gui/MainScreen/commands/showShareNoteDialog'),
|
||||
require('./gui/MainScreen/commands/showSpellCheckerMenu'),
|
||||
require('./gui/MainScreen/commands/toggleEditors'),
|
||||
require('./gui/MainScreen/commands/toggleLayoutMoveMode'),
|
||||
require('./gui/MainScreen/commands/toggleNoteList'),
|
||||
require('./gui/MainScreen/commands/toggleSideBar'),
|
||||
require('./gui/MainScreen/commands/toggleVisiblePanes'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteBody'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
|
||||
require('./gui/NoteEditor/commands/showLocalSearch'),
|
||||
require('./gui/NoteEditor/commands/showRevisions'),
|
||||
require('./gui/NoteList/commands/focusElementNoteList'),
|
||||
require('./gui/NoteListControls/commands/focusSearch'),
|
||||
require('./gui/Sidebar/commands/focusElementSideBar'),
|
||||
];
|
||||
const commands = mainScreenCommands
|
||||
.concat(noteEditorCommands)
|
||||
.concat(noteListCommands)
|
||||
.concat(noteListControlsCommands)
|
||||
.concat(sidebarCommands);
|
||||
|
||||
// Commands that are not tied to any particular component.
|
||||
// The runtime for these commands can be loaded when the app starts.
|
||||
const globalCommands = [
|
||||
require('./commands/copyDevCommand'),
|
||||
require('./commands/exportFolders'),
|
||||
require('./commands/exportNotes'),
|
||||
require('./commands/focusElement'),
|
||||
require('./commands/openProfileDirectory'),
|
||||
require('./commands/replaceMisspelling'),
|
||||
require('./commands/startExternalEditing'),
|
||||
require('./commands/stopExternalEditing'),
|
||||
require('./commands/toggleExternalEditing'),
|
||||
require('./commands/toggleSafeMode'),
|
||||
require('./commands/restoreNoteRevision'),
|
||||
require('@joplin/lib/commands/historyBackward'),
|
||||
require('@joplin/lib/commands/historyForward'),
|
||||
require('@joplin/lib/commands/synchronize'),
|
||||
];
|
||||
const globalCommands = appCommands.concat(libCommands);
|
||||
|
||||
import editorCommandDeclarations from './gui/NoteEditor/commands/editorCommandDeclarations';
|
||||
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import checkForUpdates from './checkForUpdates';
|
||||
import { AppState } from './app.reducer';
|
||||
|
||||
const pluginClasses = [
|
||||
require('./plugins/GotoAnything').default,
|
||||
];
|
||||
|
||||
interface AppStateRoute {
|
||||
type: string;
|
||||
routeName: string;
|
||||
props: any;
|
||||
}
|
||||
|
||||
export interface AppStateDialog {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AppState extends State {
|
||||
route: AppStateRoute;
|
||||
navHistory: any[];
|
||||
noteVisiblePanes: string[];
|
||||
windowContentSize: any;
|
||||
watchedNoteFiles: string[];
|
||||
lastEditorScrollPercents: any;
|
||||
devToolsVisible: boolean;
|
||||
visibleDialogs: any; // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: string;
|
||||
layoutMoveMode: boolean;
|
||||
startupPluginsLoaded: boolean;
|
||||
|
||||
// Extra reducer keys go here
|
||||
watchedResources: any;
|
||||
mainLayout: LayoutItem;
|
||||
dialogs: AppStateDialog[];
|
||||
}
|
||||
|
||||
const appDefaultState: AppState = {
|
||||
...defaultState,
|
||||
route: {
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Main',
|
||||
props: {},
|
||||
},
|
||||
navHistory: [],
|
||||
noteVisiblePanes: ['editor', 'viewer'],
|
||||
windowContentSize: bridge().windowContentSize(),
|
||||
watchedNoteFiles: [],
|
||||
lastEditorScrollPercents: {},
|
||||
devToolsVisible: false,
|
||||
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: null,
|
||||
layoutMoveMode: false,
|
||||
mainLayout: null,
|
||||
startupPluginsLoaded: false,
|
||||
dialogs: [],
|
||||
...resourceEditWatcherDefaultState,
|
||||
};
|
||||
const appDefaultState = createAppDefaultState(
|
||||
bridge().windowContentSize(),
|
||||
resourceEditWatcherDefaultState
|
||||
);
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
@@ -175,249 +84,9 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
reducer(state: AppState = appDefaultState, action: any) {
|
||||
let newState = state;
|
||||
|
||||
try {
|
||||
switch (action.type) {
|
||||
|
||||
case 'NAV_BACK':
|
||||
case 'NAV_GO':
|
||||
|
||||
{
|
||||
const goingBack = action.type === 'NAV_BACK';
|
||||
|
||||
if (goingBack && !state.navHistory.length) break;
|
||||
|
||||
const currentRoute = state.route;
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
const newNavHistory = state.navHistory.slice();
|
||||
|
||||
if (goingBack) {
|
||||
let newAction = null;
|
||||
while (newNavHistory.length) {
|
||||
newAction = newNavHistory.pop();
|
||||
if (newAction.routeName !== state.route.routeName) break;
|
||||
}
|
||||
|
||||
if (!newAction) break;
|
||||
|
||||
action = newAction;
|
||||
}
|
||||
|
||||
if (!goingBack) newNavHistory.push(currentRoute);
|
||||
newState.navHistory = newNavHistory;
|
||||
newState.route = action;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'STARTUP_PLUGINS_LOADED':
|
||||
|
||||
// When all startup plugins have loaded, we also recreate the
|
||||
// main layout to ensure that it is updated in the UI. There's
|
||||
// probably a cleaner way to do this, but for now that will do.
|
||||
if (state.startupPluginsLoaded !== action.value) {
|
||||
newState = {
|
||||
...newState,
|
||||
startupPluginsLoaded: action.value,
|
||||
mainLayout: JSON.parse(JSON.stringify(newState.mainLayout)),
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WINDOW_CONTENT_SIZE_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.windowContentSize = action.size;
|
||||
break;
|
||||
|
||||
case 'NOTE_VISIBLE_PANES_TOGGLE':
|
||||
|
||||
{
|
||||
const getNextLayout = (currentLayout: any) => {
|
||||
currentLayout = panes.length === 2 ? 'both' : currentLayout[0];
|
||||
|
||||
let paneOptions;
|
||||
if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_VIEWER) {
|
||||
paneOptions = ['editor', 'viewer'];
|
||||
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_SPLIT) {
|
||||
paneOptions = ['editor', 'both'];
|
||||
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
|
||||
paneOptions = ['viewer', 'both'];
|
||||
} else {
|
||||
paneOptions = ['editor', 'viewer', 'both'];
|
||||
}
|
||||
|
||||
const currentLayoutIndex = paneOptions.indexOf(currentLayout);
|
||||
const nextLayoutIndex = currentLayoutIndex === paneOptions.length - 1 ? 0 : currentLayoutIndex + 1;
|
||||
|
||||
const nextLayout = paneOptions[nextLayoutIndex];
|
||||
return nextLayout === 'both' ? ['editor', 'viewer'] : [nextLayout];
|
||||
};
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
|
||||
const panes = state.noteVisiblePanes.slice();
|
||||
newState.noteVisiblePanes = getNextLayout(panes);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_VISIBLE_PANES_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteVisiblePanes = action.panes;
|
||||
break;
|
||||
|
||||
case 'MAIN_LAYOUT_SET':
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: action.value,
|
||||
};
|
||||
break;
|
||||
|
||||
case 'MAIN_LAYOUT_SET_ITEM_PROP':
|
||||
|
||||
{
|
||||
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
|
||||
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||
if (item.key === action.itemKey) {
|
||||
(item as any)[action.propName] = action.propValue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: newLayout,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_ADD':
|
||||
|
||||
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
|
||||
newState = Object.assign({}, state);
|
||||
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
||||
watchedNoteFiles.push(action.id);
|
||||
newState.watchedNoteFiles = watchedNoteFiles;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_REMOVE':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
const idx = newState.watchedNoteFiles.indexOf(action.id);
|
||||
if (idx >= 0) {
|
||||
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
||||
watchedNoteFiles.splice(idx, 1);
|
||||
newState.watchedNoteFiles = watchedNoteFiles;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_CLEAR':
|
||||
|
||||
if (state.watchedNoteFiles.length) {
|
||||
newState = Object.assign({}, state);
|
||||
newState.watchedNoteFiles = [];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EDITOR_SCROLL_PERCENT_SET':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
|
||||
newPercents[action.noteId] = action.percent;
|
||||
newState.lastEditorScrollPercents = newPercents;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_TOGGLE':
|
||||
newState = Object.assign({}, state);
|
||||
newState.devToolsVisible = !newState.devToolsVisible;
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_SET':
|
||||
newState = Object.assign({}, state);
|
||||
newState.devToolsVisible = action.value;
|
||||
break;
|
||||
|
||||
case 'VISIBLE_DIALOGS_ADD':
|
||||
newState = Object.assign({}, state);
|
||||
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
||||
newState.visibleDialogs[action.name] = true;
|
||||
break;
|
||||
|
||||
case 'VISIBLE_DIALOGS_REMOVE':
|
||||
newState = Object.assign({}, state);
|
||||
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
||||
delete newState.visibleDialogs[action.name];
|
||||
break;
|
||||
|
||||
case 'FOCUS_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.focusedField = action.field;
|
||||
break;
|
||||
|
||||
case 'FOCUS_CLEAR':
|
||||
|
||||
// A field can only clear its own state
|
||||
if (action.field === state.focusedField) {
|
||||
newState = Object.assign({}, state);
|
||||
newState.focusedField = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DIALOG_OPEN':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
const newDialogs = newState.dialogs.slice();
|
||||
|
||||
if (newDialogs.find(d => d.name === action.name)) throw new Error(`This dialog is already opened: ${action.name}`);
|
||||
|
||||
newDialogs.push({
|
||||
name: action.name,
|
||||
});
|
||||
newState.dialogs = newDialogs;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DIALOG_CLOSE':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
|
||||
newState.dialogs = newDialogs;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'LAYOUT_MOVE_MODE_SET':
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
layoutMoveMode: action.value,
|
||||
};
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||
throw error;
|
||||
}
|
||||
|
||||
let newState = appReducer(state, action);
|
||||
newState = resourceEditWatcherReducer(newState, action);
|
||||
newState = super.reducer(newState, action);
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
@@ -873,6 +542,16 @@ class Application extends BaseApplication {
|
||||
// });
|
||||
// }, 2000);
|
||||
|
||||
// setTimeout(() => {
|
||||
// this.dispatch({
|
||||
// type: 'NAV_GO',
|
||||
// routeName: 'Config',
|
||||
// props: {
|
||||
// defaultSection: 'plugins',
|
||||
// },
|
||||
// });
|
||||
// }, 2000);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
29
packages/app-desktop/commands/index.ts
Normal file
29
packages/app-desktop/commands/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as copyDevCommand from './copyDevCommand';
|
||||
import * as exportFolders from './exportFolders';
|
||||
import * as exportNotes from './exportNotes';
|
||||
import * as focusElement from './focusElement';
|
||||
import * as openProfileDirectory from './openProfileDirectory';
|
||||
import * as replaceMisspelling from './replaceMisspelling';
|
||||
import * as restoreNoteRevision from './restoreNoteRevision';
|
||||
import * as startExternalEditing from './startExternalEditing';
|
||||
import * as stopExternalEditing from './stopExternalEditing';
|
||||
import * as toggleExternalEditing from './toggleExternalEditing';
|
||||
import * as toggleSafeMode from './toggleSafeMode';
|
||||
|
||||
const index:any[] = [
|
||||
copyDevCommand,
|
||||
exportFolders,
|
||||
exportNotes,
|
||||
focusElement,
|
||||
openProfileDirectory,
|
||||
replaceMisspelling,
|
||||
restoreNoteRevision,
|
||||
startExternalEditing,
|
||||
stopExternalEditing,
|
||||
toggleExternalEditing,
|
||||
toggleSafeMode,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
@@ -1,5 +1,5 @@
|
||||
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { AppState } from '../app';
|
||||
import { AppState } from '../app.reducer';
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { _ } from '@joplin/lib/locale';
|
||||
import ClipperServer from '@joplin/lib/ClipperServer';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
|
||||
import { AppState } from '../app';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
class ClipperConfigScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
|
||||
@@ -133,6 +133,20 @@ const StyledDescription = styled.div`
|
||||
line-height: 1.6em;
|
||||
`;
|
||||
|
||||
const RecommendedBadge = styled.a`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
color: ${props => props.theme.colorWarn};
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
border: 1px solid ${props => props.theme.colorWarn};
|
||||
padding: 5px;
|
||||
border-radius: 50px;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function(props: Props) {
|
||||
const item = useMemo(() => {
|
||||
return props.item ? props.item : manifestToItem(props.manifest);
|
||||
@@ -144,6 +158,10 @@ export default function(props: Props) {
|
||||
bridge().openExternal(manifest.homepage_url);
|
||||
}, [item]);
|
||||
|
||||
const onRecommendedClick = useCallback(() => {
|
||||
bridge().openExternal('https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins');
|
||||
}, []);
|
||||
|
||||
// For plugins in dev mode things like enabling/disabling or
|
||||
// uninstalling them doesn't make sense, as that should be done by
|
||||
// adding/removing them from wherever they were loaded from.
|
||||
@@ -222,11 +240,18 @@ export default function(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderRecommendedBadge() {
|
||||
if (props.onToggle) return null;
|
||||
if (!item.manifest._recommended) return null;
|
||||
return <RecommendedBadge href="#" title={_('The Joplin team has vetted this plugin and it meets our standards for security and performance.')} onClick={onRecommendedClick}><i className="fas fa-crown"></i></RecommendedBadge>;
|
||||
}
|
||||
|
||||
return (
|
||||
<CellRoot isCompatible={props.isCompatible}>
|
||||
<CellTop>
|
||||
<StyledNameAndVersion mb={'5px'}><StyledName onClick={onNameClick} href="#" style={{ marginRight: 5 }}>{item.manifest.name} {item.deleted ? _('(%s)', 'Deleted') : ''}</StyledName><StyledVersion>v{item.manifest.version}</StyledVersion></StyledNameAndVersion>
|
||||
{renderToggleButton()}
|
||||
{renderRecommendedBadge()}
|
||||
</CellTop>
|
||||
<CellContent>
|
||||
<StyledDescription>{item.manifest.description}</StyledDescription>
|
||||
|
||||
@@ -30,6 +30,14 @@ interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
function sortManifestResults(results: PluginManifest[]): PluginManifest[] {
|
||||
return results.sort((m1, m2) => {
|
||||
if (m1._recommended && !m2._recommended) return -1;
|
||||
if (!m1._recommended && m2._recommended) return +1;
|
||||
return m1.name.toLowerCase() < m2.name.toLowerCase() ? -1 : +1;
|
||||
});
|
||||
}
|
||||
|
||||
export default function(props: Props) {
|
||||
const [searchStarted, setSearchStarted] = useState(false);
|
||||
const [manifests, setManifests] = useState<PluginManifest[]>([]);
|
||||
@@ -47,7 +55,7 @@ export default function(props: Props) {
|
||||
setSearchResultCount(null);
|
||||
} else {
|
||||
const r = await props.repoApi().search(props.searchQuery);
|
||||
setManifests(r);
|
||||
setManifests(sortManifestResults(r));
|
||||
setSearchResultCount(r.length);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import { stateUtils } from '@joplin/lib/reducer';
|
||||
import InteropServiceHelper from '../../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import NoteListWrapper from '../NoteListWrapper/NoteListWrapper';
|
||||
import { AppState } from '../../app';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { saveLayout, loadLayout } from '../ResizableLayout/utils/persist';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import produce from 'immer';
|
||||
@@ -37,6 +37,7 @@ import { reg } from '@joplin/lib/registry';
|
||||
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
|
||||
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
|
||||
import commands from './commands/index';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { PromptDialog } = require('../PromptDialog.min.js');
|
||||
@@ -110,39 +111,6 @@ const defaultLayout: LayoutItem = {
|
||||
],
|
||||
};
|
||||
|
||||
const commands = [
|
||||
require('./commands/editAlarm'),
|
||||
require('./commands/exportPdf'),
|
||||
require('./commands/gotoAnything'),
|
||||
require('./commands/commandPalette'),
|
||||
require('./commands/hideModalMessage'),
|
||||
require('./commands/moveToFolder'),
|
||||
require('./commands/newFolder'),
|
||||
require('./commands/newNote'),
|
||||
require('./commands/newSubFolder'),
|
||||
require('./commands/newTodo'),
|
||||
require('./commands/openFolder'),
|
||||
require('./commands/openNote'),
|
||||
require('./commands/openTag'),
|
||||
require('./commands/print'),
|
||||
require('./commands/renameFolder'),
|
||||
require('./commands/renameTag'),
|
||||
require('./commands/search'),
|
||||
require('./commands/setTags'),
|
||||
require('./commands/showModalMessage'),
|
||||
require('./commands/showNoteContentProperties'),
|
||||
require('./commands/showNoteProperties'),
|
||||
require('./commands/showPrompt'),
|
||||
require('./commands/showShareFolderDialog'),
|
||||
require('./commands/showShareNoteDialog'),
|
||||
require('./commands/showSpellCheckerMenu'),
|
||||
require('./commands/toggleEditors'),
|
||||
require('./commands/toggleLayoutMoveMode'),
|
||||
require('./commands/toggleNoteList'),
|
||||
require('./commands/toggleSideBar'),
|
||||
require('./commands/toggleVisiblePanes'),
|
||||
];
|
||||
|
||||
class MainScreenComponent extends React.Component<Props, State> {
|
||||
|
||||
private waitForNotesSavedIID_: any;
|
||||
|
||||
67
packages/app-desktop/gui/MainScreen/commands/index.ts
Normal file
67
packages/app-desktop/gui/MainScreen/commands/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as commandPalette from './commandPalette';
|
||||
import * as editAlarm from './editAlarm';
|
||||
import * as exportPdf from './exportPdf';
|
||||
import * as gotoAnything from './gotoAnything';
|
||||
import * as hideModalMessage from './hideModalMessage';
|
||||
import * as moveToFolder from './moveToFolder';
|
||||
import * as newFolder from './newFolder';
|
||||
import * as newNote from './newNote';
|
||||
import * as newSubFolder from './newSubFolder';
|
||||
import * as newTodo from './newTodo';
|
||||
import * as openFolder from './openFolder';
|
||||
import * as openNote from './openNote';
|
||||
import * as openTag from './openTag';
|
||||
import * as print from './print';
|
||||
import * as renameFolder from './renameFolder';
|
||||
import * as renameTag from './renameTag';
|
||||
import * as search from './search';
|
||||
import * as setTags from './setTags';
|
||||
import * as showModalMessage from './showModalMessage';
|
||||
import * as showNoteContentProperties from './showNoteContentProperties';
|
||||
import * as showNoteProperties from './showNoteProperties';
|
||||
import * as showPrompt from './showPrompt';
|
||||
import * as showShareFolderDialog from './showShareFolderDialog';
|
||||
import * as showShareNoteDialog from './showShareNoteDialog';
|
||||
import * as showSpellCheckerMenu from './showSpellCheckerMenu';
|
||||
import * as toggleEditors from './toggleEditors';
|
||||
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
|
||||
import * as toggleNoteList from './toggleNoteList';
|
||||
import * as toggleSideBar from './toggleSideBar';
|
||||
import * as toggleVisiblePanes from './toggleVisiblePanes';
|
||||
|
||||
const index:any[] = [
|
||||
commandPalette,
|
||||
editAlarm,
|
||||
exportPdf,
|
||||
gotoAnything,
|
||||
hideModalMessage,
|
||||
moveToFolder,
|
||||
newFolder,
|
||||
newNote,
|
||||
newSubFolder,
|
||||
newTodo,
|
||||
openFolder,
|
||||
openNote,
|
||||
openTag,
|
||||
print,
|
||||
renameFolder,
|
||||
renameTag,
|
||||
search,
|
||||
setTags,
|
||||
showModalMessage,
|
||||
showNoteContentProperties,
|
||||
showNoteProperties,
|
||||
showPrompt,
|
||||
showShareFolderDialog,
|
||||
showShareNoteDialog,
|
||||
showSpellCheckerMenu,
|
||||
toggleEditors,
|
||||
toggleLayoutMoveMode,
|
||||
toggleNoteList,
|
||||
toggleSideBar,
|
||||
toggleVisiblePanes,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
@@ -2,7 +2,7 @@ import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../../../services/bridge';
|
||||
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
|
||||
import { AppState } from '../../../app';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
|
||||
const Menu = bridge().Menu;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { AppState } from '../../../app';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleLayoutMoveMode',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import setLayoutItemProps from '../../ResizableLayout/utils/setLayoutItemProps';
|
||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||
import { AppState } from '../../../app';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleNoteList',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import setLayoutItemProps from '../../ResizableLayout/utils/setLayoutItemProps';
|
||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||
import { AppState } from '../../../app';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleSideBar',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { AppState } from '../app';
|
||||
import { AppState } from '../app.reducer';
|
||||
import InteropService from '@joplin/lib/services/interop/InteropService';
|
||||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
|
||||
@@ -3,7 +3,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
||||
import ToolbarBase from '../../../ToolbarBase';
|
||||
import { utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../../../../app';
|
||||
import { AppState } from '../../../../app.reducer';
|
||||
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import stateToWhenClauseContext from '../../../../services/commands/stateToWhenClauseContext';
|
||||
const { buildStyle } = require('@joplin/lib/theme');
|
||||
|
||||
@@ -20,7 +20,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
||||
import ToolbarButton from '../ToolbarButton/ToolbarButton';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import eventManager from '@joplin/lib/eventManager';
|
||||
import { AppState } from '../../app';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import ToolbarButtonUtils from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import TagList from '../TagList';
|
||||
|
||||
15
packages/app-desktop/gui/NoteEditor/commands/index.ts
Normal file
15
packages/app-desktop/gui/NoteEditor/commands/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as focusElementNoteBody from './focusElementNoteBody';
|
||||
import * as focusElementNoteTitle from './focusElementNoteTitle';
|
||||
import * as showLocalSearch from './showLocalSearch';
|
||||
import * as showRevisions from './showRevisions';
|
||||
|
||||
const index:any[] = [
|
||||
focusElementNoteBody,
|
||||
focusElementNoteTitle,
|
||||
showLocalSearch,
|
||||
showRevisions,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { FormNote, ScrollOptionTypes } from './types';
|
||||
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
|
||||
import editorCommandDeclarations from '../editorCommandDeclarations';
|
||||
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import time from '@joplin/lib/time';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppState } from '../../app';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import eventManager from '@joplin/lib/eventManager';
|
||||
import NoteListUtils from '../utils/NoteListUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
9
packages/app-desktop/gui/NoteList/commands/index.ts
Normal file
9
packages/app-desktop/gui/NoteList/commands/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as focusElementNoteList from './focusElementNoteList';
|
||||
|
||||
const index:any[] = [
|
||||
focusElementNoteList,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as focusSearch from './focusSearch';
|
||||
|
||||
const index:any[] = [
|
||||
focusSearch,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
@@ -1,4 +1,5 @@
|
||||
import app, { AppState, AppStateDialog } from '../app';
|
||||
import app from '../app';
|
||||
import { AppState, AppStateDialog } from '../app.reducer';
|
||||
import MainScreen from './MainScreen/MainScreen';
|
||||
import ConfigScreen from './ConfigScreen/ConfigScreen';
|
||||
import StatusScreen from './StatusScreen/StatusScreen';
|
||||
@@ -19,6 +20,7 @@ import DialogTitle from './DialogTitle';
|
||||
import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './DialogButtonRow';
|
||||
import Dialog from './Dialog';
|
||||
import SyncWizardDialog from './SyncWizard/Dialog';
|
||||
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
const { Navigator } = require('./Navigator.min.js');
|
||||
@@ -207,6 +209,7 @@ class RootComponent extends React.Component<Props, any> {
|
||||
return (
|
||||
<StyleSheetManager disableVendorPrefixes>
|
||||
<ThemeProvider theme={theme}>
|
||||
<StyleSheetContainer themeId={this.props.themeId}></StyleSheetContainer>
|
||||
<MenuBar/>
|
||||
<GlobalStyle/>
|
||||
<Navigator style={navigatorStyle} screens={screens} />
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ShareUserStatus, StateShare, StateShareUser } from '@joplin/lib/service
|
||||
import { State } from '@joplin/lib/reducer';
|
||||
import { connect } from 'react-redux';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
|
||||
const logger = Logger.create('ShareFolderDialog');
|
||||
|
||||
@@ -100,20 +101,6 @@ interface RecipientDeleteEvent {
|
||||
shareUserId: string;
|
||||
}
|
||||
|
||||
interface AsyncEffectEvent {
|
||||
cancelled: boolean;
|
||||
}
|
||||
|
||||
function useAsyncEffect(effect: Function, dependencies: any[]) {
|
||||
useEffect(() => {
|
||||
const event = { cancelled: false };
|
||||
effect(event);
|
||||
return () => {
|
||||
event.cancelled = true;
|
||||
};
|
||||
}, dependencies);
|
||||
}
|
||||
|
||||
enum ShareState {
|
||||
Idle = 0,
|
||||
Synchronizing = 1,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { StateShare } from '@joplin/lib/services/share/reducer';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import Button from './Button/Button';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../app';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { getEncryptionEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import InteropServiceHelper from '../../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
|
||||
import { AppState } from '../../app';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { ModelType } from '@joplin/lib/BaseModel';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||
import { AppState } from '../../../app';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'focusElementSideBar',
|
||||
|
||||
9
packages/app-desktop/gui/Sidebar/commands/index.ts
Normal file
9
packages/app-desktop/gui/Sidebar/commands/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as focusElementSideBar from './focusElementSideBar';
|
||||
|
||||
const index:any[] = [
|
||||
focusElementSideBar,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
41
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.tsx
Normal file
41
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
// This component is perhaps a bit of a hack but the approach should be
|
||||
// reliable. It converts the current (JS) theme to CSS, and add it to the HEAD
|
||||
// tag. The component itself doesn't render anything where it's located (just an
|
||||
// empty invisible DIV), so it means it could be put anywhere and would have the
|
||||
// same effect.
|
||||
//
|
||||
// It's still reliable because the lifecyle of adding the CSS and removing on
|
||||
// unmout is handled properly. There should only be one such component on the
|
||||
// page.
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import themeToCss from '@joplin/lib/services/style/themeToCss';
|
||||
import { themeById } from '@joplin/lib/theme';
|
||||
|
||||
interface Props {
|
||||
themeId: any;
|
||||
}
|
||||
|
||||
export default function(props: Props): any {
|
||||
const [styleSheetContent, setStyleSheetContent] = useState('');
|
||||
|
||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||
const theme = themeById(props.themeId);
|
||||
const themeCss = themeToCss(theme);
|
||||
if (event.cancelled) return;
|
||||
setStyleSheetContent(themeCss);
|
||||
}, [props.themeId]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = document.createElement('style');
|
||||
element.setAttribute('id', 'main-theme-stylesheet-container');
|
||||
document.head.appendChild(element);
|
||||
element.appendChild(document.createTextNode(styleSheetContent));
|
||||
return () => {
|
||||
document.head.removeChild(element);
|
||||
};
|
||||
}, [styleSheetContent]);
|
||||
|
||||
return <div style={{ display: 'none' }}></div>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { AppState } from '../app';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
|
||||
@@ -19,6 +19,7 @@ const tasks = {
|
||||
},
|
||||
tsc: require('@joplin/tools/gulp/tasks/tsc'),
|
||||
updateIgnoredTypeScriptBuild: require('@joplin/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
|
||||
buildCommandIndex: require('@joplin/tools/gulp/tasks/buildCommandIndex'),
|
||||
};
|
||||
|
||||
utils.registerGulpTasks(gulp, tasks);
|
||||
@@ -28,7 +29,8 @@ const buildParallel = [
|
||||
'compilePackageInfo',
|
||||
'copyPluginAssets',
|
||||
'copyTinyMceLangs',
|
||||
// 'updateIgnoredTypeScriptBuild',
|
||||
'updateIgnoredTypeScriptBuild',
|
||||
'buildCommandIndex',
|
||||
];
|
||||
|
||||
gulp.task('build', gulp.parallel(...buildParallel));
|
||||
|
||||
4
packages/app-desktop/package-lock.json
generated
4
packages/app-desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.4",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { AppState } from '../app';
|
||||
import { AppState } from '../app.reducer';
|
||||
import CommandService, { SearchResult as CommandSearchResult } from '@joplin/lib/services/CommandService';
|
||||
import KeymapService from '@joplin/lib/services/KeymapService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
@@ -337,6 +337,10 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
// @ts-ignore
|
||||
const notesById = notes.reduce((obj, { id, body, markup_language }) => ((obj[[id]] = { id, body, markup_language }), obj), {});
|
||||
|
||||
// Filter out search results that are associated with non-existing notes.
|
||||
// https://github.com/laurent22/joplin/issues/5417
|
||||
results = results.filter(r => !!notesById[r.id]);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const row = results[i];
|
||||
const path = Folder.folderPathString(this.props.folders, row.parent_id);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// general, any desktop component should import this file, and not the lib
|
||||
// one.
|
||||
|
||||
import { AppState } from '../../app';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import libStateToWhenClauseContext, { WhenClauseContextOptions } from '@joplin/lib/services/commands/stateToWhenClauseContext';
|
||||
import layoutItemProp from '../../gui/ResizableLayout/utils/layoutItemProp';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppState } from '../../app';
|
||||
import { AppState } from '../../app.reducer';
|
||||
|
||||
export interface DesktopCommandContext {
|
||||
state: AppState;
|
||||
|
||||
@@ -64,28 +64,6 @@ a {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
.note-list .list-item-container:hover {
|
||||
background-color: rgba(0,160,255,0.1) !important;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
.editor-toolbar .button:not(.disabled):hover,
|
||||
.header .button:not(.disabled):hover {
|
||||
background-color: rgba(0,160,255,0.1);
|
||||
border: 1px solid rgba(0,160,255,0.5);
|
||||
box-sizing: 'border-box';
|
||||
}
|
||||
|
||||
.editor-toolbar .button:not(.disabled):active,
|
||||
.header .button:not(.disabled):active {
|
||||
background-color: rgba(0,160,255,0.2);
|
||||
border: 1px solid rgba(0,160,255,0.7);
|
||||
box-sizing: 'border-box';
|
||||
}
|
||||
*/
|
||||
|
||||
.editor-toolbar .button,
|
||||
.header .button {
|
||||
border: 1px solid rgba(0,160,255,0);
|
||||
@@ -163,11 +141,6 @@ a {
|
||||
to {transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
/* .joplin-tinymce .tox-editor-header {
|
||||
padding-left: 88px;
|
||||
padding-right: 150px;
|
||||
} */
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
@@ -141,8 +141,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097648
|
||||
versionName "2.4.0"
|
||||
versionCode 2097649
|
||||
versionName "2.4.1"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
|
||||
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
|
||||
FBReactNativeSpec: d2f54de51f69366bd1f5c1fb9270698dce678f8d
|
||||
FBReactNativeSpec: 6da2c8ff1ebe6b6cf4510fcca58c24c4d02b16fc
|
||||
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||
JoplinCommonShareExtension: 270b4f8eb4e22828eeda433a04ed689fc1fd09b5
|
||||
JoplinRNShareExtension: 7137e9787374e1b0797ecbef9103d1588d90e403
|
||||
|
||||
@@ -480,6 +480,7 @@ async function initialize(dispatch: Function) {
|
||||
}
|
||||
|
||||
reg.logger().info('Database is ready.');
|
||||
|
||||
reg.logger().info('Loading settings...');
|
||||
|
||||
await loadKeychainServiceAndSettings(KeychainServiceDriverMobile);
|
||||
@@ -641,7 +642,7 @@ async function initialize(dispatch: Function) {
|
||||
|
||||
class AppComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
@@ -684,7 +685,7 @@ class AppComponent extends React.Component {
|
||||
// https://github.com/laurent22/joplin/issues/3807
|
||||
// https://discourse.joplinapp.org/t/webdav-config-encryption-config-randomly-lost-on-android/11364
|
||||
// https://discourse.joplinapp.org/t/android-keeps-on-resetting-my-sync-and-theme/11443
|
||||
async componentDidMount() {
|
||||
public async componentDidMount() {
|
||||
if (this.props.appState == 'starting') {
|
||||
this.props.dispatch({
|
||||
type: 'APP_STATE_SET',
|
||||
@@ -737,13 +738,13 @@ class AppComponent extends React.Component {
|
||||
// setTimeout(() => NavService.go('EncryptionConfig'), 2000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this.onAppStateChange_);
|
||||
Linking.removeEventListener('url', this.handleOpenURL_);
|
||||
if (this.unsubscribeNetInfoHandler_) this.unsubscribeNetInfoHandler_();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: any) {
|
||||
public componentDidUpdate(prevProps: any) {
|
||||
if (this.props.showSideMenu !== prevProps.showSideMenu) {
|
||||
Animated.timing(this.state.sideMenuContentOpacity, {
|
||||
toValue: this.props.showSideMenu ? 0.5 : 0,
|
||||
@@ -752,7 +753,7 @@ class AppComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
async backButtonHandler() {
|
||||
private async backButtonHandler() {
|
||||
if (this.props.noteSelectionEnabled) {
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
return true;
|
||||
@@ -773,7 +774,7 @@ class AppComponent extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
async handleShareData() {
|
||||
private async handleShareData() {
|
||||
const sharedData = await ShareExtension.data();
|
||||
if (sharedData) {
|
||||
reg.logger().info('Received shared data');
|
||||
@@ -785,14 +786,14 @@ class AppComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps: any) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: any) {
|
||||
if (newProps.syncStarted != this.lastSyncStarted_) {
|
||||
if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders();
|
||||
this.lastSyncStarted_ = newProps.syncStarted;
|
||||
}
|
||||
}
|
||||
|
||||
sideMenu_change(isOpen: boolean) {
|
||||
private sideMenu_change(isOpen: boolean) {
|
||||
// Make sure showSideMenu property of state is updated
|
||||
// when the menu is open/closed.
|
||||
this.props.dispatch({
|
||||
@@ -800,7 +801,7 @@ class AppComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (this.props.appState != 'ready') return null;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
|
||||
2
packages/fork-htmlparser2/package-lock.json
generated
2
packages/fork-htmlparser2/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"version": "4.1.33",
|
||||
"version": "4.1.34",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"description": "Fast & forgiving HTML/XML/RSS parser",
|
||||
"version": "4.1.33",
|
||||
"version": "4.1.34",
|
||||
"author": "Felix Boehm <me@feedic.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
2
packages/fork-sax/package-lock.json
generated
2
packages/fork-sax/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-sax",
|
||||
"version": "1.2.37",
|
||||
"version": "1.2.38",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@joplin/fork-sax",
|
||||
"description": "An evented streaming XML parser in JavaScript",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
||||
"version": "1.2.37",
|
||||
"version": "1.2.38",
|
||||
"main": "lib/sax.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -259,6 +259,14 @@ export default class JoplinDatabase extends Database {
|
||||
folders: {},
|
||||
resources: {},
|
||||
tags: {},
|
||||
item_changes: {
|
||||
type: 'The type of change - either 1 (created), 2 (updated) or 3 (deleted)',
|
||||
created_time: 'When the event was generated',
|
||||
item_type: 'The item type (see table above for the list of item types)',
|
||||
item_id: 'The item ID',
|
||||
before_change_item: 'Unused',
|
||||
source: 'Unused',
|
||||
},
|
||||
};
|
||||
|
||||
const baseItems = ['notes', 'folders', 'tags', 'resources'];
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
const ObjectUtils = {};
|
||||
|
||||
ObjectUtils.sortByValue = function(object) {
|
||||
export function sortByValue(object: any) {
|
||||
const temp = [];
|
||||
for (const k in object) {
|
||||
if (!object.hasOwnProperty(k)) continue;
|
||||
@@ -19,16 +17,16 @@ ObjectUtils.sortByValue = function(object) {
|
||||
return v1 < v2 ? -1 : +1;
|
||||
});
|
||||
|
||||
const output = {};
|
||||
const output: any = {};
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
const item = temp[i];
|
||||
output[item.key] = item.value;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
||||
ObjectUtils.fieldsEqual = function(o1, o2) {
|
||||
export function fieldsEqual(o1: any, o2: any) {
|
||||
if ((!o1 || !o2) && o1 !== o2) return false;
|
||||
|
||||
for (const k in o1) {
|
||||
@@ -42,10 +40,10 @@ ObjectUtils.fieldsEqual = function(o1, o2) {
|
||||
if (c1.length !== c2.length) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
ObjectUtils.convertValuesToFunctions = function(o) {
|
||||
const output = {};
|
||||
export function convertValuesToFunctions(o: any) {
|
||||
const output: any = {};
|
||||
for (const n in o) {
|
||||
if (!o.hasOwnProperty(n)) continue;
|
||||
output[n] = () => {
|
||||
@@ -53,11 +51,26 @@ ObjectUtils.convertValuesToFunctions = function(o) {
|
||||
};
|
||||
}
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
||||
ObjectUtils.isEmpty = function(o) {
|
||||
export function isEmpty(o: any) {
|
||||
if (!o) return true;
|
||||
return Object.keys(o).length === 0 && o.constructor === Object;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ObjectUtils;
|
||||
// export function isStringifiable(o:any):boolean {
|
||||
// if (o === null || o === undefined) return true;
|
||||
|
||||
// if (Array.isArray(o)) {
|
||||
// for (const e of o) {
|
||||
// if (!isStringifiable(e)) return false;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (typeof o === 'object') {
|
||||
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// }
|
||||
13
packages/lib/commands/index.ts
Normal file
13
packages/lib/commands/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as historyBackward from './historyBackward';
|
||||
import * as historyForward from './historyForward';
|
||||
import * as synchronize from './synchronize';
|
||||
|
||||
const index:any[] = [
|
||||
historyBackward,
|
||||
historyForward,
|
||||
synchronize,
|
||||
];
|
||||
|
||||
export default index;
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
@@ -77,7 +77,7 @@ export default class Database {
|
||||
throw new Error(`Invalid field format: ${field}`);
|
||||
}
|
||||
|
||||
escapeFields(fields: string[] | string): string[] | string {
|
||||
public escapeFields(fields: string[] | string): string[] | string {
|
||||
if (fields == '*') return '*';
|
||||
|
||||
const output = [];
|
||||
@@ -87,6 +87,16 @@ export default class Database {
|
||||
return output;
|
||||
}
|
||||
|
||||
public escapeFieldsToString(fields: string[] | string): string {
|
||||
if (fields === '*') return '*';
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
output.push(this.escapeField(fields[i]));
|
||||
}
|
||||
return output.join(',');
|
||||
}
|
||||
|
||||
async tryCall(callName: string, inputSql: StringOrSqlQuery, inputParams: SqlParams) {
|
||||
let sql: string = null;
|
||||
let params: SqlParams = null;
|
||||
|
||||
@@ -21,6 +21,38 @@ export default class FsDriverBase {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async readFile(_path: string, _encoding: string = 'utf8'): Promise<any> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async copy(_source: string, _dest: string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async mkdir(_path: string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async unlink(_path: string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async move(_source: string, _dest: string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async readFileChunk(_handle: any, _length: number, _encoding: string = 'base64'): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async open(_path: string, _mode: any): Promise<any> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async close(_handle: any): Promise<any> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public async readDirStats(_path: string, _options: ReadDirStatsOptions = null): Promise<Stat[]> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
18
packages/lib/hooks/useAsyncEffect.ts
Normal file
18
packages/lib/hooks/useAsyncEffect.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import shim from '../shim';
|
||||
const { useEffect } = shim.react();
|
||||
|
||||
export interface AsyncEffectEvent {
|
||||
cancelled: boolean;
|
||||
}
|
||||
|
||||
export type EffectFunction = (event: AsyncEffectEvent)=> Promise<void>;
|
||||
|
||||
export default function(effect: EffectFunction, dependencies: any[]) {
|
||||
useEffect(() => {
|
||||
const event: AsyncEffectEvent = { cancelled: false };
|
||||
void effect(event);
|
||||
return () => {
|
||||
event.cancelled = true;
|
||||
};
|
||||
}, dependencies);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
const { revisionService, setupDatabaseAndSynchronizer, db, switchClient } = require('../testing/test-utils.js');
|
||||
const SearchEngine = require('../services/searchengine/SearchEngine').default;
|
||||
const ResourceService = require('../services/ResourceService').default;
|
||||
const ItemChangeUtils = require('../services/ItemChangeUtils').default;
|
||||
const Note = require('../models/Note').default;
|
||||
const ItemChange = require('../models/ItemChange').default;
|
||||
import { revisionService, setupDatabaseAndSynchronizer, db, switchClient, msleep } from '../testing/test-utils';
|
||||
import SearchEngine from '../services/searchengine/SearchEngine';
|
||||
import ResourceService from '../services/ResourceService';
|
||||
import ItemChangeUtils from '../services/ItemChangeUtils';
|
||||
import Note from '../models/Note';
|
||||
import ItemChange from '../models/ItemChange';
|
||||
|
||||
let searchEngine = null;
|
||||
let searchEngine: SearchEngine = null;
|
||||
|
||||
describe('models_ItemChange', function() {
|
||||
describe('models/ItemChange', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
@@ -27,17 +27,28 @@ describe('models_ItemChange', function() {
|
||||
const resourceService = new ResourceService();
|
||||
|
||||
await searchEngine.syncTables();
|
||||
|
||||
// If we run this now, it should not delete any change because
|
||||
// the resource service has not yet processed the change
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
await ItemChangeUtils.deleteProcessedChanges(0);
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
await resourceService.indexNoteResources();
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
await ItemChangeUtils.deleteProcessedChanges(0);
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
// If we don't set a TTL it will default to 90 days so it won't delete
|
||||
// either.
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
// All changes should be at least 4 ms old now
|
||||
await msleep(4);
|
||||
|
||||
// Now it should delete all changes older than 3 ms
|
||||
await ItemChangeUtils.deleteProcessedChanges(3);
|
||||
expect(await ItemChange.lastChangeId()).toBe(0);
|
||||
}));
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import BaseModel, { ModelType } from '../BaseModel';
|
||||
import shim from '../shim';
|
||||
import eventManager from '../eventManager';
|
||||
import { ItemChangeEntity } from '../services/database/types';
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
export interface ChangeSinceIdOptions {
|
||||
limit?: number;
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
export default class ItemChange extends BaseModel {
|
||||
|
||||
private static addChangeMutex_: any = new Mutex();
|
||||
@@ -24,7 +30,7 @@ export default class ItemChange extends BaseModel {
|
||||
return BaseModel.TYPE_ITEM_CHANGE;
|
||||
}
|
||||
|
||||
static async add(itemType: ModelType, itemId: string, type: number, changeSource: any = null, beforeChangeItemJson: string = null) {
|
||||
public static async add(itemType: ModelType, itemId: string, type: number, changeSource: any = null, beforeChangeItemJson: string = null) {
|
||||
if (changeSource === null) changeSource = ItemChange.SOURCE_UNSPECIFIED;
|
||||
if (!beforeChangeItemJson) beforeChangeItemJson = '';
|
||||
|
||||
@@ -57,14 +63,14 @@ export default class ItemChange extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static async lastChangeId() {
|
||||
public static async lastChangeId() {
|
||||
const row = await this.db().selectOne('SELECT max(id) as max_id FROM item_changes');
|
||||
return row && row.max_id ? row.max_id : 0;
|
||||
}
|
||||
|
||||
// Because item changes are recorded in the background, this function
|
||||
// can be used for synchronous code, in particular when unit testing.
|
||||
static async waitForAllSaved() {
|
||||
public static async waitForAllSaved() {
|
||||
return new Promise((resolve) => {
|
||||
const iid = shim.setInterval(() => {
|
||||
if (!ItemChange.saveCalls_.length) {
|
||||
@@ -75,8 +81,32 @@ export default class ItemChange extends BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static async deleteOldChanges(lowestChangeId: number) {
|
||||
public static async deleteOldChanges(lowestChangeId: number, itemMinTtl: number) {
|
||||
if (!lowestChangeId) return;
|
||||
return this.db().exec('DELETE FROM item_changes WHERE id <= ?', [lowestChangeId]);
|
||||
|
||||
const cutOffDate = Date.now() - itemMinTtl;
|
||||
|
||||
return this.db().exec(`
|
||||
DELETE FROM item_changes
|
||||
WHERE id <= ?
|
||||
AND created_time <= ?
|
||||
`, [lowestChangeId, cutOffDate]);
|
||||
}
|
||||
|
||||
public static async changesSinceId(changeId: number, options: ChangeSinceIdOptions = null): Promise<ItemChangeEntity[]> {
|
||||
options = {
|
||||
limit: 100,
|
||||
fields: ['id', 'item_type', 'item_id', 'type', 'created_time'],
|
||||
...options,
|
||||
};
|
||||
|
||||
return this.db().selectAll(`
|
||||
SELECT ${this.db().escapeFieldsToString(options.fields)}
|
||||
FROM item_changes
|
||||
WHERE id > ?
|
||||
ORDER BY id
|
||||
LIMIT ?
|
||||
`, [changeId, options.limit]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
packages/lib/package-lock.json
generated
17
packages/lib/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"async-mutex": "^0.1.3",
|
||||
@@ -64,6 +64,7 @@
|
||||
"xml2js": "^0.4.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/css": "^0.0.33",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
@@ -1003,6 +1004,12 @@
|
||||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/css": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/css/-/css-0.0.33.tgz",
|
||||
"integrity": "sha512-qjeDgh86R0LIeEM588q65yatc8Yyo/VvSIYFqq8JOIHDolhGNX0rz7k/OuxqDpnpqlefoHj8X4Ai/6hT9IWtKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/fs-extra": {
|
||||
"version": "9.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.11.tgz",
|
||||
@@ -9688,6 +9695,12 @@
|
||||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/css": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/css/-/css-0.0.33.tgz",
|
||||
"integrity": "sha512-qjeDgh86R0LIeEM588q65yatc8Yyo/VvSIYFqq8JOIHDolhGNX0rz7k/OuxqDpnpqlefoHj8X4Ai/6hT9IWtKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "9.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.11.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Joplin Core library",
|
||||
"author": "Laurent Cozic",
|
||||
"homepage": "",
|
||||
@@ -16,6 +16,7 @@
|
||||
"test-ci": "npm run test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/css": "^0.0.33",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
@@ -25,11 +26,11 @@
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.33",
|
||||
"@joplin/fork-sax": "^1.2.37",
|
||||
"@joplin/renderer": "~2.4",
|
||||
"@joplin/turndown": "^4.0.55",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.37",
|
||||
"@joplin/fork-htmlparser2": "^4.1.34",
|
||||
"@joplin/fork-sax": "^1.2.38",
|
||||
"@joplin/renderer": "^2.4.1",
|
||||
"@joplin/turndown": "^4.0.56",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.38",
|
||||
"async-mutex": "^0.1.3",
|
||||
"aws-sdk": "^2.588.0",
|
||||
"base-64": "^0.1.0",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Setting from '../models/Setting';
|
||||
import ItemChange from '../models/ItemChange';
|
||||
|
||||
const dayMs = 86400000;
|
||||
|
||||
export default class ItemChangeUtils {
|
||||
static async deleteProcessedChanges() {
|
||||
static async deleteProcessedChanges(itemMinTtl: number = dayMs * 90) {
|
||||
const lastProcessedChangeIds = [
|
||||
Setting.value('resourceService.lastProcessedChangeId'),
|
||||
Setting.value('searchEngine.lastProcessedChangeId'),
|
||||
@@ -10,6 +12,6 @@ export default class ItemChangeUtils {
|
||||
];
|
||||
|
||||
const lowestChangeId = Math.min(...lastProcessedChangeIds);
|
||||
await ItemChange.deleteOldChanges(lowestChangeId);
|
||||
await ItemChange.deleteOldChanges(lowestChangeId, itemMinTtl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,27 +5,28 @@ import { ImportExportResult } from './types';
|
||||
import Setting from '../../models/Setting';
|
||||
|
||||
export default class InteropService_Importer_Base {
|
||||
|
||||
private metadata_: any = null;
|
||||
protected sourcePath_: string = '';
|
||||
protected options_: any = {};
|
||||
|
||||
setMetadata(md: any) {
|
||||
public setMetadata(md: any) {
|
||||
this.metadata_ = md;
|
||||
}
|
||||
|
||||
metadata() {
|
||||
public metadata() {
|
||||
return this.metadata_;
|
||||
}
|
||||
|
||||
async init(sourcePath: string, options: any) {
|
||||
public async init(sourcePath: string, options: any) {
|
||||
this.sourcePath_ = sourcePath;
|
||||
this.options_ = options;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
async exec(result: ImportExportResult): Promise<ImportExportResult> {}
|
||||
public async exec(result: ImportExportResult): Promise<ImportExportResult> {}
|
||||
|
||||
async temporaryDirectory_(createIt: boolean) {
|
||||
protected async temporaryDirectory_(createIt: boolean) {
|
||||
const md5 = require('md5');
|
||||
const tempDir = `${Setting.value('tempDir')}/${md5(Math.random() + Date.now())}`;
|
||||
if (createIt) await require('fs-extra').mkdirp(tempDir);
|
||||
|
||||
@@ -5,15 +5,27 @@ export default class InteropService_Importer_Custom extends InteropService_Impor
|
||||
|
||||
private module_: Module = null;
|
||||
|
||||
constructor(handler: Module) {
|
||||
public constructor(handler: Module) {
|
||||
super();
|
||||
this.module_ = handler;
|
||||
}
|
||||
|
||||
async exec(result: ImportExportResult): Promise<ImportExportResult> {
|
||||
public async exec(result: ImportExportResult): Promise<ImportExportResult> {
|
||||
// When passing the options to the plugin, we strip off any function
|
||||
// because they won't serialized over ipc.
|
||||
|
||||
const processedOptions: any = {};
|
||||
|
||||
if (this.options_) {
|
||||
for (const [k, v] of Object.entries(this.options_)) {
|
||||
if (typeof v === 'function') continue;
|
||||
processedOptions[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return this.module_.onExec({
|
||||
sourcePath: this.sourcePath_,
|
||||
options: this.options_,
|
||||
options: processedOptions,
|
||||
warnings: result.warnings,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ export default function manifestFromObject(o: any): PluginManifest {
|
||||
return o[name];
|
||||
};
|
||||
|
||||
const getBoolean = (name: string, required: boolean = true, defaultValue: boolean = false): boolean => {
|
||||
if (required && !o[name]) throw new Error(`Missing required field: ${name}`);
|
||||
if (!o[name]) return defaultValue;
|
||||
if (typeof o[name] !== 'boolean') throw new Error(`Field must be a boolean: ${name}`);
|
||||
return o[name];
|
||||
};
|
||||
|
||||
const permissions: PluginPermission[] = [];
|
||||
|
||||
const manifest: PluginManifest = {
|
||||
@@ -39,6 +46,8 @@ export default function manifestFromObject(o: any): PluginManifest {
|
||||
repository_url: getString('repository_url', false),
|
||||
keywords: getStrings('keywords', false),
|
||||
permissions: permissions,
|
||||
|
||||
_recommended: getBoolean('_recommended', false, false),
|
||||
};
|
||||
|
||||
validatePluginId(manifest.id);
|
||||
|
||||
@@ -20,4 +20,6 @@ export interface PluginManifest {
|
||||
_publish_hash?: string;
|
||||
_publish_commit?: string;
|
||||
_npm_package_name?: string;
|
||||
_obsolete?: boolean;
|
||||
_recommended?: boolean;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import route_master_keys from './routes/master_keys';
|
||||
import route_search from './routes/search';
|
||||
import route_ping from './routes/ping';
|
||||
import route_auth from './routes/auth';
|
||||
import route_events from './routes/events';
|
||||
|
||||
const { ltrimSlashes } = require('../../path-utils');
|
||||
const md5 = require('md5');
|
||||
@@ -43,6 +44,9 @@ interface RequestQuery {
|
||||
|
||||
// Auth token
|
||||
auth_token?: string;
|
||||
|
||||
// Event cursor
|
||||
cursor?: string;
|
||||
}
|
||||
|
||||
export interface Request {
|
||||
@@ -104,6 +108,7 @@ export default class Api {
|
||||
search: route_search,
|
||||
services: this.action_services.bind(this),
|
||||
auth: route_auth,
|
||||
events: route_events,
|
||||
};
|
||||
|
||||
this.dispatch = this.dispatch.bind(this);
|
||||
|
||||
94
packages/lib/services/rest/routes/events.test.ts
Normal file
94
packages/lib/services/rest/routes/events.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { ModelType } from '../../../BaseModel';
|
||||
import ItemChange from '../../../models/ItemChange';
|
||||
import Note from '../../../models/Note';
|
||||
import { expectThrow, setupDatabaseAndSynchronizer, switchClient } from '../../../testing/test-utils';
|
||||
import { ItemChangeEntity } from '../../database/types';
|
||||
import Api, { RequestMethod } from '../Api';
|
||||
|
||||
let api: Api = null;
|
||||
|
||||
describe('routes/events', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
api = new Api();
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should retrieve the latest events', async () => {
|
||||
let cursor = '0';
|
||||
|
||||
{
|
||||
const response = await api.route(RequestMethod.GET, 'events', { cursor });
|
||||
expect(response.cursor).toBe('0');
|
||||
}
|
||||
|
||||
const note1 = await Note.save({ title: 'toto' });
|
||||
await Note.save({ id: note1.id, title: 'tutu' });
|
||||
const note2 = await Note.save({ title: 'tata' });
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
{
|
||||
const response = await api.route(RequestMethod.GET, 'events', { cursor });
|
||||
expect(response.cursor).toBe('3');
|
||||
expect(response.items.length).toBe(2);
|
||||
expect(response.has_more).toBe(false);
|
||||
expect(response.items.map((it: ItemChangeEntity) => it.item_id).sort()).toEqual([note1.id, note2.id].sort());
|
||||
|
||||
cursor = response.cursor;
|
||||
}
|
||||
|
||||
{
|
||||
const response = await api.route(RequestMethod.GET, 'events', { cursor });
|
||||
expect(response.cursor).toBe(cursor);
|
||||
expect(response.items.length).toBe(0);
|
||||
expect(response.has_more).toBe(false);
|
||||
}
|
||||
|
||||
await Note.save({ id: note2.id, title: 'titi' });
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
{
|
||||
const response = await api.route(RequestMethod.GET, 'events', { cursor });
|
||||
expect(response.cursor).toBe('4');
|
||||
expect(response.items.length).toBe(1);
|
||||
expect(response.items[0].item_id).toBe(note2.id);
|
||||
}
|
||||
});
|
||||
|
||||
it('should limit the number of response items', async () => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < 101; i++) {
|
||||
promises.push(Note.save({ title: 'toto' }));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
const response1 = await api.route(RequestMethod.GET, 'events', { cursor: '0' });
|
||||
expect(response1.items.length).toBe(100);
|
||||
expect(response1.has_more).toBe(true);
|
||||
|
||||
const response2 = await api.route(RequestMethod.GET, 'events', { cursor: response1.cursor });
|
||||
expect(response2.items.length).toBe(1);
|
||||
expect(response2.has_more).toBe(false);
|
||||
});
|
||||
|
||||
it('should retrieve a single item', async () => {
|
||||
const beforeTime = Date.now();
|
||||
|
||||
const note = await Note.save({ title: 'toto' });
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
const response = await api.route(RequestMethod.GET, 'events/1');
|
||||
|
||||
expect(response.item_type).toBe(ModelType.Note);
|
||||
expect(response.type).toBe(1);
|
||||
expect(response.item_id).toBe(note.id);
|
||||
expect(response.created_time).toBeGreaterThanOrEqual(beforeTime);
|
||||
|
||||
await expectThrow(async () => api.route(RequestMethod.GET, 'events/1234'));
|
||||
});
|
||||
|
||||
});
|
||||
39
packages/lib/services/rest/routes/events.ts
Normal file
39
packages/lib/services/rest/routes/events.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ModelType } from '../../../BaseModel';
|
||||
import { Request, RequestMethod } from '../Api';
|
||||
import { ErrorBadRequest, ErrorNotFound } from '../utils/errors';
|
||||
import ItemChange, { ChangeSinceIdOptions } from '../../../models/ItemChange';
|
||||
import requestFields from '../utils/requestFields';
|
||||
|
||||
export default async function(request: Request, id: string = null, _link: string = null) {
|
||||
if (request.method === RequestMethod.GET) {
|
||||
const options: ChangeSinceIdOptions = {
|
||||
limit: 100,
|
||||
fields: requestFields(request, ModelType.ItemChange, ['id', 'item_type', 'item_id', 'type', 'created_time']),
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
if (!('cursor' in request.query)) {
|
||||
return {
|
||||
items: [],
|
||||
has_more: false,
|
||||
cursor: (await ItemChange.lastChangeId()).toString(),
|
||||
};
|
||||
} else {
|
||||
const cursor = Number(request.query.cursor);
|
||||
if (isNaN(cursor)) throw new ErrorBadRequest(`Invalid cursor: ${request.query.cursor}`);
|
||||
|
||||
const changes = await ItemChange.changesSinceId(cursor, options);
|
||||
|
||||
return {
|
||||
items: changes,
|
||||
has_more: changes.length >= options.limit,
|
||||
cursor: (changes.length ? changes[changes.length - 1].id : cursor).toString(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const change = await ItemChange.load(id, { fields: options.fields });
|
||||
if (!change) throw new ErrorNotFound();
|
||||
return change;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,18 @@ function defaultFieldsByModelType(modelType: number): string[] {
|
||||
return output;
|
||||
}
|
||||
|
||||
export default function(request: Request, modelType: number) {
|
||||
export default function(request: Request, modelType: number, defaultFields: string[] = null) {
|
||||
const getDefaults = () => {
|
||||
if (defaultFields) return defaultFields;
|
||||
return defaultFieldsByModelType(modelType);
|
||||
};
|
||||
|
||||
const query = request.query;
|
||||
if (!query || !query.fields) return defaultFieldsByModelType(modelType);
|
||||
if (!query || !query.fields) return getDefaults();
|
||||
if (Array.isArray(query.fields)) return query.fields.slice();
|
||||
const fields = query.fields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
.filter((f: string) => !!f);
|
||||
return fields.length ? fields : defaultFieldsByModelType(modelType);
|
||||
return fields.length ? fields : getDefaults();
|
||||
}
|
||||
|
||||
28
packages/lib/services/style/cssToTheme.test.ts
Normal file
28
packages/lib/services/style/cssToTheme.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import cssToTheme from './cssToTheme';
|
||||
|
||||
describe('cssToTheme', function() {
|
||||
|
||||
it('should convert a CSS string to a theme', async () => {
|
||||
const input = `
|
||||
:root {
|
||||
--joplin-appearence: light;
|
||||
--joplin-color: #333333;
|
||||
--joplin-background-color: #778899;
|
||||
|
||||
/* Should skip this comment and empty lines */
|
||||
|
||||
--joplin-background-color-transparent: rgba(255,255,255,0.9);
|
||||
}`;
|
||||
|
||||
const expected = {
|
||||
appearence: 'light',
|
||||
color: '#333333',
|
||||
backgroundColor: '#778899',
|
||||
backgroundColorTransparent: 'rgba(255,255,255,0.9)',
|
||||
};
|
||||
|
||||
const actual = cssToTheme(input, 'test.css');
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
});
|
||||
46
packages/lib/services/style/cssToTheme.ts
Normal file
46
packages/lib/services/style/cssToTheme.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Theme } from '../../themes/type';
|
||||
|
||||
// Need to include it that way due to a bug in the lib:
|
||||
// https://github.com/reworkcss/css/pull/146#issuecomment-740412799
|
||||
const cssParse = require('css/lib/parse');
|
||||
|
||||
function formatCssToThemeVariable(cssVariable: string): string {
|
||||
const elements = cssVariable.substr(2).split('-');
|
||||
if (elements[0] !== 'joplin') throw new Error(`CSS variable name must start with "--joplin": ${cssVariable}`);
|
||||
|
||||
elements.splice(0, 1);
|
||||
|
||||
return elements.map((e, i) => {
|
||||
const c = i === 0 ? e[0] : e[0].toUpperCase();
|
||||
return c + e.substr(1);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// function unquoteValue(v:string):string {
|
||||
// if (v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"')) return v.substr(1, v.length - 2);
|
||||
// return v;
|
||||
// }
|
||||
|
||||
export default function cssToTheme(css: string, sourceFilePath: string): Theme {
|
||||
const o = cssParse(css, {
|
||||
silent: false,
|
||||
source: sourceFilePath,
|
||||
});
|
||||
|
||||
if (!o?.stylesheet?.rules?.length) throw new Error(`Invalid CSS color file: ${sourceFilePath}`);
|
||||
|
||||
// Need "as any" because outdated TS definition file
|
||||
|
||||
const rootRule = o.stylesheet.rules[0];
|
||||
if (!rootRule.selectors.includes(':root')) throw new Error('`:root` rule not found');
|
||||
|
||||
const declarations: any[] = rootRule.declarations;
|
||||
|
||||
const output: any = {};
|
||||
for (const declaration of declarations) {
|
||||
if (declaration.type !== 'declaration') continue; // Skip comment lines
|
||||
output[formatCssToThemeVariable(declaration.property)] = declaration.value;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
27
packages/lib/services/style/loadCssToTheme.ts
Normal file
27
packages/lib/services/style/loadCssToTheme.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Theme } from '../../themes/type';
|
||||
import { filename } from '../../path-utils';
|
||||
import shim from '../../shim';
|
||||
import cssToTheme from './cssToTheme';
|
||||
|
||||
export default async function(cssBaseDir: string): Promise<Record<string, Theme>> {
|
||||
const themeDirs = (await shim.fsDriver().readDirStats(cssBaseDir)).filter((f: any) => f.isDirectory());
|
||||
|
||||
const output: Record<string, Theme> = {};
|
||||
|
||||
for (const themeDir of themeDirs) {
|
||||
const themeName = filename(themeDir.path);
|
||||
const cssFile = `${cssBaseDir}/${themeDir.path}/colors.css`;
|
||||
const cssContent = await shim.fsDriver().readFile(cssFile, 'utf8');
|
||||
|
||||
let themeId = themeName;
|
||||
const manifestFile = `${cssBaseDir}/${themeDir.path}/manifest.json`;
|
||||
if (await shim.fsDriver().exists(manifestFile)) {
|
||||
const manifest = JSON.parse(await shim.fsDriver().readFile(manifestFile, 'utf8'));
|
||||
if (manifest.id) themeId = manifest.id;
|
||||
}
|
||||
|
||||
output[themeId] = cssToTheme(cssContent, cssFile);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
105
packages/lib/services/style/themeToCss.test.ts
Normal file
105
packages/lib/services/style/themeToCss.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Theme, ThemeAppearance } from '../../themes/type';
|
||||
import themeToCss from './themeToCss';
|
||||
|
||||
const input: Theme = {
|
||||
appearance: ThemeAppearance.Light,
|
||||
|
||||
// Color scheme "1" is the basic one, like used to display the note
|
||||
// content. It's basically dark gray text on white background
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColorTransparent: 'rgba(255,255,255,0.9)',
|
||||
oddBackgroundColor: '#eeeeee',
|
||||
color: '#32373F', // For regular text
|
||||
colorError: 'red',
|
||||
colorWarn: 'rgb(228,86,0)',
|
||||
colorWarnUrl: '#155BDA',
|
||||
colorFaded: '#7C8B9E', // For less important text
|
||||
colorBright: '#000000', // For important text
|
||||
dividerColor: '#dddddd',
|
||||
selectedColor: '#e5e5e5',
|
||||
urlColor: '#155BDA',
|
||||
|
||||
// Color scheme "2" is used for the sidebar. It's white text over
|
||||
// dark blue background.
|
||||
backgroundColor2: '#313640',
|
||||
color2: '#ffffff',
|
||||
selectedColor2: '#131313',
|
||||
colorError2: '#ff6c6c',
|
||||
colorWarn2: '#ffcb81',
|
||||
|
||||
// Color scheme "3" is used for the config screens for example/
|
||||
// It's dark text over gray background.
|
||||
backgroundColor3: '#F4F5F6',
|
||||
backgroundColorHover3: '#CBDAF1',
|
||||
color3: '#738598',
|
||||
|
||||
// Color scheme "4" is used for secondary-style buttons. It makes a white
|
||||
// button with blue text.
|
||||
backgroundColor4: '#ffffff',
|
||||
color4: '#2D6BDC',
|
||||
|
||||
raisedBackgroundColor: '#e5e5e5',
|
||||
raisedColor: '#222222',
|
||||
searchMarkerBackgroundColor: '#F7D26E',
|
||||
searchMarkerColor: 'black',
|
||||
|
||||
warningBackgroundColor: '#FFD08D',
|
||||
|
||||
tableBackgroundColor: 'rgb(247, 247, 247)',
|
||||
codeBackgroundColor: 'rgb(243, 243, 243)',
|
||||
codeBorderColor: 'rgb(220, 220, 220)',
|
||||
codeColor: 'rgb(0,0,0)',
|
||||
|
||||
blockQuoteOpacity: 0.7,
|
||||
|
||||
codeMirrorTheme: 'default',
|
||||
codeThemeCss: 'atom-one-light.css',
|
||||
};
|
||||
|
||||
const expected = `
|
||||
:root {
|
||||
--joplin-appearance: light;
|
||||
--joplin-background-color: #ffffff;
|
||||
--joplin-background-color-transparent: rgba(255,255,255,0.9);
|
||||
--joplin-odd-background-color: #eeeeee;
|
||||
--joplin-color: #32373F;
|
||||
--joplin-color-error: red;
|
||||
--joplin-color-warn: rgb(228,86,0);
|
||||
--joplin-color-warn-url: #155BDA;
|
||||
--joplin-color-faded: #7C8B9E;
|
||||
--joplin-color-bright: #000000;
|
||||
--joplin-divider-color: #dddddd;
|
||||
--joplin-selected-color: #e5e5e5;
|
||||
--joplin-url-color: #155BDA;
|
||||
--joplin-background-color2: #313640;
|
||||
--joplin-color2: #ffffff;
|
||||
--joplin-selected-color2: #131313;
|
||||
--joplin-color-error2: #ff6c6c;
|
||||
--joplin-color-warn2: #ffcb81;
|
||||
--joplin-background-color3: #F4F5F6;
|
||||
--joplin-background-color-hover3: #CBDAF1;
|
||||
--joplin-color3: #738598;
|
||||
--joplin-background-color4: #ffffff;
|
||||
--joplin-color4: #2D6BDC;
|
||||
--joplin-raised-background-color: #e5e5e5;
|
||||
--joplin-raised-color: #222222;
|
||||
--joplin-search-marker-background-color: #F7D26E;
|
||||
--joplin-search-marker-color: black;
|
||||
--joplin-warning-background-color: #FFD08D;
|
||||
--joplin-table-background-color: rgb(247, 247, 247);
|
||||
--joplin-code-background-color: rgb(243, 243, 243);
|
||||
--joplin-code-border-color: rgb(220, 220, 220);
|
||||
--joplin-code-color: rgb(0,0,0);
|
||||
--joplin-block-quote-opacity: 0.7;
|
||||
--joplin-code-mirror-theme: default;
|
||||
--joplin-code-theme-css: atom-one-light.css;
|
||||
}`;
|
||||
|
||||
describe('themeToCss', function() {
|
||||
|
||||
it('should a theme to a CSS string', async () => {
|
||||
const actual = themeToCss(input);
|
||||
expect(actual.trim()).toBe(expected.trim());
|
||||
});
|
||||
|
||||
});
|
||||
24
packages/lib/services/style/themeToCss.ts
Normal file
24
packages/lib/services/style/themeToCss.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Theme } from '../../themes/type';
|
||||
const { camelCaseToDash, formatCssSize } = require('../../string-utils');
|
||||
|
||||
// function quoteCssValue(name: string, value: string): string {
|
||||
// const needsQuote = ['appearance', 'codeMirrorTheme', 'codeThemeCss'].includes(name);
|
||||
// if (needsQuote) return `'${value}'`;
|
||||
// return value;
|
||||
// }
|
||||
|
||||
export default function(theme: Theme) {
|
||||
const lines = [];
|
||||
lines.push(':root {');
|
||||
|
||||
for (const name in theme) {
|
||||
const value = (theme as any)[name];
|
||||
const newName = `--joplin-${camelCaseToDash(name)}`;
|
||||
const formattedValue = typeof value === 'number' && newName.indexOf('opacity') < 0 ? formatCssSize(value) : value;
|
||||
lines.push(`\t${newName}: ${formattedValue};`);
|
||||
}
|
||||
|
||||
lines.push('}');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
@@ -23,7 +23,7 @@ const themes: any = {
|
||||
[Setting.THEME_OLED_DARK]: theme_oledDark,
|
||||
};
|
||||
|
||||
function themeById(themeId: string) {
|
||||
export function themeById(themeId: string) {
|
||||
if (!themes[themeId]) throw new Error(`Invalid theme ID: ${themeId}`);
|
||||
const output = Object.assign({}, themes[themeId]);
|
||||
|
||||
@@ -365,7 +365,7 @@ function addExtraStyles(style: any) {
|
||||
|
||||
const themeCache_: any = {};
|
||||
|
||||
function themeStyle(themeId: number) {
|
||||
export function themeStyle(themeId: number) {
|
||||
if (!themeId) throw new Error('Theme must be specified');
|
||||
|
||||
const zoomRatio = 1;
|
||||
@@ -405,7 +405,7 @@ const cachedStyles_: any = {
|
||||
// cacheKey must be a globally unique key, and must change whenever
|
||||
// the dependencies of the style change. If the style depends only
|
||||
// on the theme, a static string can be provided as a cache key.
|
||||
function buildStyle(cacheKey: any, themeId: number, callback: Function) {
|
||||
export function buildStyle(cacheKey: any, themeId: number, callback: Function) {
|
||||
cacheKey = Array.isArray(cacheKey) ? cacheKey.join('_') : cacheKey;
|
||||
|
||||
// We clear the cache whenever switching themes
|
||||
@@ -425,5 +425,3 @@ function buildStyle(cacheKey: any, themeId: number, callback: Function) {
|
||||
|
||||
return cachedStyles_.styles[cacheKey].style;
|
||||
}
|
||||
|
||||
export { themeStyle, buildStyle, themeById };
|
||||
|
||||
@@ -10,18 +10,8 @@ import updateReadme from './lib/updateReadme';
|
||||
import { NpmPackage } from './lib/types';
|
||||
import gitCompareUrl from './lib/gitCompareUrl';
|
||||
import commandUpdateRelease from './commands/updateRelease';
|
||||
|
||||
function stripOffPackageOrg(name: string): string {
|
||||
const n = name.split('/');
|
||||
if (n[0][0] === '@') n.splice(0, 1);
|
||||
return n.join('/');
|
||||
}
|
||||
|
||||
function isJoplinPluginPackage(pack: any): boolean {
|
||||
if (!pack.keywords || !pack.keywords.includes('joplin-plugin')) return false;
|
||||
if (stripOffPackageOrg(pack.name).indexOf('joplin-plugin') !== 0) return false;
|
||||
return true;
|
||||
}
|
||||
import { isJoplinPluginPackage, readJsonFile } from './lib/utils';
|
||||
import { applyManifestOverrides, getObsoleteManifests, readManifestOverrides } from './lib/overrideUtils';
|
||||
|
||||
function pluginInfoFromSearchResults(results: any[]): NpmPackage[] {
|
||||
const output: NpmPackage[] = [];
|
||||
@@ -44,19 +34,11 @@ async function checkPluginRepository(dirPath: string, dryRun: boolean) {
|
||||
if (!(await fs.pathExists(`${dirPath}/.git`))) throw new Error(`Directory is not a Git repository: ${dirPath}`);
|
||||
|
||||
const previousDir = chdir(dirPath);
|
||||
await gitRepoCleanTry();
|
||||
if (!dryRun) await gitPullTry();
|
||||
chdir(previousDir);
|
||||
}
|
||||
|
||||
async function readJsonFile(manifestPath: string, defaultValue: any = null): Promise<any> {
|
||||
if (!(await fs.pathExists(manifestPath))) {
|
||||
if (defaultValue === null) throw new Error(`No such file: ${manifestPath}`);
|
||||
return defaultValue;
|
||||
if (!dryRun) {
|
||||
await gitRepoCleanTry();
|
||||
await gitPullTry();
|
||||
}
|
||||
|
||||
const content = await fs.readFile(manifestPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
chdir(previousDir);
|
||||
}
|
||||
|
||||
async function extractPluginFilesFromPackage(existingManifests: any, workDir: string, packageName: string, destDir: string): Promise<any> {
|
||||
@@ -147,14 +129,14 @@ function chdir(path: string): string {
|
||||
return previous;
|
||||
}
|
||||
|
||||
async function processNpmPackage(npmPackage: NpmPackage, repoDir: string) {
|
||||
async function processNpmPackage(npmPackage: NpmPackage, repoDir: string, dryRun: boolean) {
|
||||
const tempDir = `${repoDir}/temp`;
|
||||
const obsoleteManifestsPath = path.resolve(repoDir, 'obsoletes.json');
|
||||
|
||||
await fs.mkdirp(tempDir);
|
||||
|
||||
const originalPluginManifests = await readManifests(repoDir);
|
||||
const obsoleteManifests = await readJsonFile(obsoleteManifestsPath, {});
|
||||
const manifestOverrides = await readManifestOverrides(repoDir);
|
||||
const obsoleteManifests = getObsoleteManifests(manifestOverrides);
|
||||
const existingManifests = {
|
||||
...originalPluginManifests,
|
||||
...obsoleteManifests,
|
||||
@@ -199,6 +181,8 @@ async function processNpmPackage(npmPackage: NpmPackage, repoDir: string) {
|
||||
...manifests,
|
||||
};
|
||||
|
||||
manifests = applyManifestOverrides(manifests, manifestOverrides);
|
||||
|
||||
await writeManifests(repoDir, manifests);
|
||||
await updateReadme(`${repoDir}/README.md`, manifests);
|
||||
}
|
||||
@@ -206,11 +190,13 @@ async function processNpmPackage(npmPackage: NpmPackage, repoDir: string) {
|
||||
chdir(repoDir);
|
||||
await fs.remove(tempDir);
|
||||
|
||||
if (!(await gitRepoClean())) {
|
||||
await execCommand2('git add -A', { showOutput: false });
|
||||
await execCommand2(['git', 'commit', '-m', commitMessage(actionType, manifest, previousManifest, npmPackage, error)], { showOutput: false });
|
||||
} else {
|
||||
console.info('Nothing to commit');
|
||||
if (!dryRun) {
|
||||
if (!(await gitRepoClean())) {
|
||||
await execCommand2('git add -A', { showOutput: false });
|
||||
await execCommand2(['git', 'commit', '-m', commitMessage(actionType, manifest, previousManifest, npmPackage, error)], { showOutput: false });
|
||||
} else {
|
||||
console.info('Nothing to commit');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,36 +209,40 @@ async function commandBuild(args: CommandBuildArgs) {
|
||||
await checkPluginRepository(repoDir, dryRun);
|
||||
|
||||
// When starting, always update and commit README, in case something has
|
||||
// been updated via a pull request (for example obsoletes.json being
|
||||
// modified). We do that separately so that the README update doesn't get
|
||||
// mixed up with plugin updates, as in this example:
|
||||
// been updated via a pull request. We do that separately so that the README
|
||||
// update doesn't get mixed up with plugin updates, as in this example:
|
||||
// https://github.com/joplin/plugins/commit/8a65bbbf64bf267674f854a172466ffd4f07c672
|
||||
const manifests = await readManifests(repoDir);
|
||||
await updateReadme(`${repoDir}/README.md`, manifests);
|
||||
const previousDir = chdir(repoDir);
|
||||
if (!(await gitRepoClean())) {
|
||||
console.info('Updating README...');
|
||||
await execCommand2('git add -A', { showOutput: true });
|
||||
await execCommand2('git commit -m "Update README"', { showOutput: true });
|
||||
|
||||
if (!dryRun) {
|
||||
if (!(await gitRepoClean())) {
|
||||
console.info('Updating README...');
|
||||
await execCommand2('git add -A', { showOutput: true });
|
||||
await execCommand2('git commit -m "Update README"', { showOutput: true });
|
||||
}
|
||||
}
|
||||
|
||||
chdir(previousDir);
|
||||
|
||||
const searchResults = (await execCommand2('npm search joplin-plugin --searchlimit 5000 --json', { showOutput: false })).trim();
|
||||
const npmPackages = pluginInfoFromSearchResults(JSON.parse(searchResults));
|
||||
|
||||
for (const npmPackage of npmPackages) {
|
||||
await processNpmPackage(npmPackage, repoDir);
|
||||
await processNpmPackage(npmPackage, repoDir, dryRun);
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
await commandUpdateRelease(args);
|
||||
|
||||
if (!(await gitRepoClean())) {
|
||||
await execCommand2('git add -A', { showOutput: true });
|
||||
await execCommand2('git commit -m "Update stats"', { showOutput: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (!dryRun) await execCommand2('git push');
|
||||
await execCommand2('git push');
|
||||
}
|
||||
}
|
||||
|
||||
async function commandVersion() {
|
||||
|
||||
95
packages/plugin-repo-cli/lib/overrideUtils.test.ts
Normal file
95
packages/plugin-repo-cli/lib/overrideUtils.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { applyManifestOverrides, getObsoleteManifests, ManifestOverrides } from './overrideUtils';
|
||||
|
||||
describe('overrideUtils', () => {
|
||||
|
||||
test('should get the obsolete manifests', () => {
|
||||
const manifestOverrides: any = {
|
||||
'ambrt.backlinksToNote': {
|
||||
'manifest_version': 1,
|
||||
'id': 'ambrt.backlinksToNote',
|
||||
'app_min_version': '1.5',
|
||||
'version': '1.0.2',
|
||||
'name': 'Backlinks to note',
|
||||
'description': 'Creates backlinks to opened note',
|
||||
'author': 'a',
|
||||
'homepage_url': 'https://discourse.joplinapp.org/t/insert-referencing-notes-backlinks-plugin/13632',
|
||||
'_publish_hash': 'sha256:5676da6b9ad71fc5a9779d3bde13f17de5352344711e135f0db8c62c6dbb5696',
|
||||
'_publish_commit': 'master:19e515bd67e51cc37bd270a32d2898ca009a0de2',
|
||||
'_npm_package_name': 'joplin-plugin-backlinks',
|
||||
'_obsolete': true,
|
||||
},
|
||||
'MyPlugin': {
|
||||
'manifest_version': 1,
|
||||
'id': 'MyPlugin',
|
||||
'app_min_version': '1.6',
|
||||
'version': '1.0.0',
|
||||
'name': 'Testing New Plugin',
|
||||
'description': 'bla',
|
||||
'author': 'bla',
|
||||
'homepage_url': 'bla',
|
||||
'repository_url': 'bla',
|
||||
'_publish_hash': 'sha256:065285d06ea3c084e7f8f8c23583de8d70c4d586274a242c4c750f6faad8c7cb',
|
||||
'_publish_commit': '',
|
||||
'_npm_package_name': 'joplin-plugin-testing-new-plugin',
|
||||
'_obsolete': true,
|
||||
},
|
||||
'io.github.jackgruber.copytags': {
|
||||
'_recommended': true,
|
||||
},
|
||||
};
|
||||
|
||||
const obsoletes = getObsoleteManifests(manifestOverrides);
|
||||
expect(Object.keys(obsoletes).sort()).toEqual(['MyPlugin', 'ambrt.backlinksToNote']);
|
||||
expect(obsoletes['ambrt.backlinksToNote'].description).toBe('Creates backlinks to opened note');
|
||||
});
|
||||
|
||||
test('should apply the overrides', () => {
|
||||
const manifests: any = {
|
||||
'io.github.jackgruber.copytags': {
|
||||
'manifest_version': 1,
|
||||
'id': 'io.github.jackgruber.copytags',
|
||||
'app_min_version': '1.6.2',
|
||||
'version': '1.0.1',
|
||||
'name': 'Tagging',
|
||||
'description': 'Plugin to extend the Joplin tagging menu with a coppy all tags and a tagging dialog with more control. (Formerly Copy Tags).',
|
||||
'author': 'JackGruber',
|
||||
'homepage_url': 'https://github.com/JackGruber/joplin-plugin-tagging/blob/master/README.md',
|
||||
'repository_url': 'https://github.com/JackGruber/joplin-plugin-tagging',
|
||||
'keywords': [
|
||||
'duplicate',
|
||||
'copy',
|
||||
'tags',
|
||||
'tagging',
|
||||
'tag',
|
||||
],
|
||||
'_publish_hash': 'sha256:88daaf234a9b47e5644a8de6f830a801d12edbe41ea5364d994773e89eeafeef',
|
||||
'_publish_commit': 'master:64c0510e3236df7788a8d10ec28dcfbb4c2bdbb7',
|
||||
'_npm_package_name': 'joplin-plugin-copytags',
|
||||
},
|
||||
'joplin.plugin.ambrt.backlinksToNote': {
|
||||
'manifest_version': 1,
|
||||
'id': 'joplin.plugin.ambrt.backlinksToNote',
|
||||
'app_min_version': '1.7',
|
||||
'version': '2.0.11',
|
||||
'name': 'Automatic Backlinks to note',
|
||||
'description': 'Creates backlinks to opened note, also in automatic way',
|
||||
'author': 'ambrt,cyingfan',
|
||||
'homepage_url': 'https://discourse.joplinapp.org/t/insert-referencing-notes-backlinks-plugin/13632',
|
||||
'_publish_hash': 'sha256:df57930d1ab62d4297dad0bb1764888935fcbf6ca8c04e3a843e86a260735c51',
|
||||
'_publish_commit': 'master:98102718a9c0fa9416d654451b18602798c4d3bb',
|
||||
'_npm_package_name': 'joplin-plugin-backlinks',
|
||||
},
|
||||
};
|
||||
|
||||
const overrides: ManifestOverrides = {
|
||||
'io.github.jackgruber.copytags': {
|
||||
_recommended: true,
|
||||
},
|
||||
};
|
||||
|
||||
const updatedManifests = applyManifestOverrides(manifests, overrides);
|
||||
expect(updatedManifests['io.github.jackgruber.copytags']._recommended).toBe(true);
|
||||
expect(updatedManifests['joplin.plugin.ambrt.backlinksToNote']._recommended).toBe(undefined);
|
||||
});
|
||||
|
||||
});
|
||||
51
packages/plugin-repo-cli/lib/overrideUtils.ts
Normal file
51
packages/plugin-repo-cli/lib/overrideUtils.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as path from 'path';
|
||||
import { readJsonFile } from './utils';
|
||||
|
||||
export interface ManifestOverride {
|
||||
_obsolete?: boolean;
|
||||
_recommended?: boolean;
|
||||
}
|
||||
|
||||
export type ManifestOverrides = Record<string, ManifestOverride>;
|
||||
|
||||
export function applyManifestOverrides(manifests: any, overrides: ManifestOverrides) {
|
||||
for (const [pluginId, override] of Object.entries(overrides)) {
|
||||
const manifest: any = manifests[pluginId];
|
||||
|
||||
if (!manifest) {
|
||||
// If the manifest does not exist in the destination, it means the
|
||||
// plugin has been taken out, so the obsolete property should be set
|
||||
// to `true`. If not, it's an error.
|
||||
if (!override._obsolete) {
|
||||
console.error(`Could not find manifest for plugin ${pluginId} when applying override`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [propName, propValue] of Object.entries(override)) {
|
||||
manifest[propName] = propValue;
|
||||
}
|
||||
}
|
||||
|
||||
return manifests;
|
||||
}
|
||||
|
||||
export function getObsoleteManifests(overrides: ManifestOverrides) {
|
||||
const output: any = {};
|
||||
|
||||
for (const [pluginId, override] of Object.entries(overrides)) {
|
||||
if (override._obsolete) {
|
||||
output[pluginId] = override;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function pluginManifestOverridesPath(repoDir: string): string {
|
||||
return path.resolve(repoDir, 'manifestOverrides.json');
|
||||
}
|
||||
|
||||
export async function readManifestOverrides(repoDir: string): Promise<ManifestOverrides> {
|
||||
return readJsonFile(pluginManifestOverridesPath(repoDir), {});
|
||||
}
|
||||
23
packages/plugin-repo-cli/lib/utils.ts
Normal file
23
packages/plugin-repo-cli/lib/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
export async function readJsonFile(manifestPath: string, defaultValue: any = null): Promise<any> {
|
||||
if (!(await fs.pathExists(manifestPath))) {
|
||||
if (defaultValue === null) throw new Error(`No such file: ${manifestPath}`);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const content = await fs.readFile(manifestPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
function stripOffPackageOrg(name: string): string {
|
||||
const n = name.split('/');
|
||||
if (n[0][0] === '@') n.splice(0, 1);
|
||||
return n.join('/');
|
||||
}
|
||||
|
||||
export function isJoplinPluginPackage(pack: any): boolean {
|
||||
if (!pack.keywords || !pack.keywords.includes('joplin-plugin')) return false;
|
||||
if (stripOffPackageOrg(pack.name).indexOf('joplin-plugin') !== 0) return false;
|
||||
return true;
|
||||
}
|
||||
2
packages/plugin-repo-cli/package-lock.json
generated
2
packages/plugin-repo-cli/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "2.3.1",
|
||||
"version": "2.4.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
@@ -13,13 +13,14 @@
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"watch": "tsc --watch --project tsconfig.json",
|
||||
"test": "jest",
|
||||
"test-ci": "npm run test"
|
||||
"test-ci": "npm run test",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.4",
|
||||
"@joplin/tools": "~2.4",
|
||||
"@joplin/lib": "^2.4.1",
|
||||
"@joplin/tools": "^2.4.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"gh-release-assets": "^2.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
||||
92
packages/renderer/headerAnchor.ts
Normal file
92
packages/renderer/headerAnchor.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
export default function(markdownIt: any) {
|
||||
markdownIt.core.ruler.push('anchorHeader', (state: any): boolean => {
|
||||
const tokens = state.tokens;
|
||||
const Token = state.Token;
|
||||
const doneNames = [];
|
||||
|
||||
const headingTextToAnchorName = (text: string, doneNames: string[]) => {
|
||||
const allowed = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let lastWasDash = true;
|
||||
let output = '';
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
if (allowed.indexOf(c) < 0) {
|
||||
if (lastWasDash) continue;
|
||||
lastWasDash = true;
|
||||
output += '-';
|
||||
} else {
|
||||
lastWasDash = false;
|
||||
output += c;
|
||||
}
|
||||
}
|
||||
|
||||
output = output.toLowerCase();
|
||||
|
||||
while (output.length && output[output.length - 1] === '-') {
|
||||
output = output.substr(0, output.length - 1);
|
||||
}
|
||||
|
||||
let temp = output;
|
||||
let index = 1;
|
||||
while (doneNames.indexOf(temp) >= 0) {
|
||||
temp = `${output}-${index}`;
|
||||
index++;
|
||||
}
|
||||
output = temp;
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const createAnchorTokens = (anchorName: string) => {
|
||||
const output = [];
|
||||
|
||||
{
|
||||
const token = new Token('heading_anchor_open', 'a', 1);
|
||||
token.attrs = [
|
||||
['name', anchorName],
|
||||
['href', `#${anchorName}`],
|
||||
['class', 'heading-anchor'],
|
||||
];
|
||||
output.push(token);
|
||||
}
|
||||
|
||||
{
|
||||
const token = new Token('text', '', 0);
|
||||
token.content = '🔗';
|
||||
output.push(token);
|
||||
}
|
||||
|
||||
{
|
||||
const token = new Token('heading_anchor_close', 'a', -1);
|
||||
output.push(token);
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
let insideHeading = false;
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type === 'heading_open') {
|
||||
insideHeading = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.type === 'heading_close') {
|
||||
insideHeading = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideHeading && token.type === 'inline') {
|
||||
const anchorName = headingTextToAnchorName(token.content, doneNames);
|
||||
doneNames.push(anchorName);
|
||||
const anchorTokens = createAnchorTokens(anchorName);
|
||||
// token.children = anchorTokens.concat(token.children);
|
||||
token.children = token.children.concat(anchorTokens);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import HtmlToHtml from './HtmlToHtml';
|
||||
import utils from './utils';
|
||||
import setupLinkify from './MdToHtml/setupLinkify';
|
||||
import validateLinks from './MdToHtml/validateLinks';
|
||||
import headerAnchor from './headerAnchor';
|
||||
const assetsToHeaders = require('./assetsToHeaders');
|
||||
|
||||
export {
|
||||
@@ -13,6 +14,7 @@ export {
|
||||
HtmlToHtml,
|
||||
setupLinkify,
|
||||
validateLinks,
|
||||
headerAnchor,
|
||||
assetsToHeaders,
|
||||
utils,
|
||||
};
|
||||
|
||||
4
packages/renderer/package-lock.json
generated
4
packages/renderer/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-awesome-filetypes": "^2.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "The Joplin note renderer, used the mobile and desktop application",
|
||||
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
|
||||
"main": "index.js",
|
||||
@@ -24,7 +24,7 @@
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.33",
|
||||
"@joplin/fork-htmlparser2": "^4.1.34",
|
||||
"font-awesome-filetypes": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"highlight.js": "^11.2.0",
|
||||
|
||||
4
packages/server/package-lock.json
generated
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/server",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@koa/cors": "^3.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
|
||||
|
||||
@@ -63,4 +63,24 @@ ul.pagination-list li {
|
||||
|
||||
.readable-block {
|
||||
max-width: 740px;
|
||||
}
|
||||
|
||||
a.heading-anchor {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
width: 1.3em;
|
||||
font-size: 0.7em;
|
||||
margin-left: 0.4em;
|
||||
line-height: 1em;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
a.heading-anchor:hover,
|
||||
h1:hover a.heading-anchor,
|
||||
h2:hover a.heading-anchor,
|
||||
h3:hover a.heading-anchor,
|
||||
h4:hover a.heading-anchor,
|
||||
h5:hover a.heading-anchor,
|
||||
h6:hover a.heading-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export async function initConfig(envType: Env, env: EnvVariables, overrides: any
|
||||
config_ = {
|
||||
appVersion: packageJson.version,
|
||||
appName,
|
||||
isJoplinCloud: apiBaseUrl.includes('.joplincloud.com'),
|
||||
isJoplinCloud: apiBaseUrl.includes('.joplincloud.com') || apiBaseUrl.includes('.joplincloud.local'),
|
||||
env: envType,
|
||||
rootDir: rootDir,
|
||||
viewDir: viewDir,
|
||||
|
||||
@@ -210,13 +210,13 @@ describe('UserModel', function() {
|
||||
await models().user().save({
|
||||
id: user1.id,
|
||||
account_type: AccountType.Basic,
|
||||
total_item_size: accountByType(AccountType.Basic).max_total_item_size * 0.85,
|
||||
total_item_size: Math.round(accountByType(AccountType.Basic).max_total_item_size * 0.85),
|
||||
});
|
||||
|
||||
await models().user().save({
|
||||
id: user2.id,
|
||||
account_type: AccountType.Pro,
|
||||
total_item_size: accountByType(AccountType.Pro).max_total_item_size * 0.2,
|
||||
total_item_size: Math.round(accountByType(AccountType.Pro).max_total_item_size * 0.2),
|
||||
});
|
||||
|
||||
const emailBeforeCount = (await models().email().all()).length;
|
||||
@@ -242,7 +242,7 @@ describe('UserModel', function() {
|
||||
|
||||
await models().user().save({
|
||||
id: user2.id,
|
||||
total_item_size: accountByType(AccountType.Pro).max_total_item_size * 1.1,
|
||||
total_item_size: Math.round(accountByType(AccountType.Pro).max_total_item_size * 1.1),
|
||||
});
|
||||
|
||||
// User upload should be enabled at this point
|
||||
|
||||
@@ -383,8 +383,8 @@ export default class UserModel extends BaseModel<User> {
|
||||
.db(this.tableName)
|
||||
.select(['id', 'total_item_size', 'max_total_item_size', 'account_type', 'email', 'full_name'])
|
||||
.where(function() {
|
||||
void this.whereRaw('total_item_size > ? AND account_type = ?', [alertLimit1 * basicAccount.max_total_item_size, AccountType.Basic])
|
||||
.orWhereRaw('total_item_size > ? AND account_type = ?', [alertLimit1 * proAccount.max_total_item_size, AccountType.Pro]);
|
||||
void this.whereRaw('total_item_size > ? AND account_type = ?', [Math.round(alertLimit1 * basicAccount.max_total_item_size), AccountType.Basic])
|
||||
.orWhereRaw('total_item_size > ? AND account_type = ?', [Math.round(alertLimit1 * proAccount.max_total_item_size), AccountType.Pro]);
|
||||
})
|
||||
// Users who are disabled or who cannot upload already received the
|
||||
// notification.
|
||||
|
||||
21
packages/server/src/routes/index/help.ts
Normal file
21
packages/server/src/routes/index/help.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SubPath } from '../../utils/routeUtils';
|
||||
import Router from '../../utils/Router';
|
||||
import { RouteType } from '../../utils/types';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { ErrorMethodNotAllowed } from '../../utils/errors';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
|
||||
const router: Router = new Router(RouteType.Web);
|
||||
|
||||
router.public = true;
|
||||
|
||||
router.get('help', async (_path: SubPath, ctx: AppContext) => {
|
||||
if (ctx.method === 'GET') {
|
||||
const view = defaultView('help', 'Help');
|
||||
return view;
|
||||
}
|
||||
|
||||
throw new ErrorMethodNotAllowed();
|
||||
});
|
||||
|
||||
export default router;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user