You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-14 23:26:58 +02:00
Compare commits
32 Commits
android-v1
...
content_sc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ff44f53da | ||
|
|
3c8f774e85 | ||
|
|
d49439689e | ||
|
|
ef3a102741 | ||
|
|
0e57baf5b9 | ||
|
|
05f65c326a | ||
|
|
491714cde6 | ||
|
|
34c1096307 | ||
|
|
ebe3ddcd21 | ||
|
|
ca5ac9696b | ||
|
|
fdfbb84d37 | ||
|
|
740aba90ea | ||
|
|
eab9ff175c | ||
|
|
5b295d5f6f | ||
|
|
0be8cdf760 | ||
|
|
545940f545 | ||
|
|
d58f39823a | ||
|
|
f9fb1b8a81 | ||
|
|
56ded0062a | ||
|
|
83b29d7c51 | ||
|
|
1ec0746263 | ||
|
|
568d11bddf | ||
|
|
97b25ac99d | ||
|
|
7f1d3d8a5d | ||
|
|
fde201fbe9 | ||
|
|
694a1b4ede | ||
|
|
20d126b39d | ||
|
|
8ca9c3092a | ||
|
|
d2771029a3 | ||
|
|
4128c53fcf | ||
|
|
a14410b28c | ||
|
|
d1f8520e6e |
@@ -155,6 +155,9 @@ packages/app-cli/tests/support/plugins/content_script/api/types.js.map
|
|||||||
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
|
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
|
||||||
packages/app-cli/tests/support/plugins/content_script/src/index.js
|
packages/app-cli/tests/support/plugins/content_script/src/index.js
|
||||||
packages/app-cli/tests/support/plugins/content_script/src/index.js.map
|
packages/app-cli/tests/support/plugins/content_script/src/index.js.map
|
||||||
|
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.d.ts
|
||||||
|
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js
|
||||||
|
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js.map
|
||||||
packages/app-cli/tests/support/plugins/dialog/api/index.d.ts
|
packages/app-cli/tests/support/plugins/dialog/api/index.d.ts
|
||||||
packages/app-cli/tests/support/plugins/dialog/api/index.js
|
packages/app-cli/tests/support/plugins/dialog/api/index.js
|
||||||
packages/app-cli/tests/support/plugins/dialog/api/index.js.map
|
packages/app-cli/tests/support/plugins/dialog/api/index.js.map
|
||||||
@@ -1388,6 +1391,12 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
|
|||||||
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
|
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
|
||||||
packages/renderer/MdToHtml/rules/sanitize_html.js
|
packages/renderer/MdToHtml/rules/sanitize_html.js
|
||||||
packages/renderer/MdToHtml/rules/sanitize_html.js.map
|
packages/renderer/MdToHtml/rules/sanitize_html.js.map
|
||||||
|
packages/renderer/MdToHtml/setupLinkify.d.ts
|
||||||
|
packages/renderer/MdToHtml/setupLinkify.js
|
||||||
|
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/htmlUtils.d.ts
|
packages/renderer/htmlUtils.d.ts
|
||||||
packages/renderer/htmlUtils.js
|
packages/renderer/htmlUtils.js
|
||||||
packages/renderer/htmlUtils.js.map
|
packages/renderer/htmlUtils.js.map
|
||||||
@@ -1460,6 +1469,9 @@ packages/server/src/controllers/index/HomeController.js.map
|
|||||||
packages/server/src/controllers/index/LoginController.d.ts
|
packages/server/src/controllers/index/LoginController.d.ts
|
||||||
packages/server/src/controllers/index/LoginController.js
|
packages/server/src/controllers/index/LoginController.js
|
||||||
packages/server/src/controllers/index/LoginController.js.map
|
packages/server/src/controllers/index/LoginController.js.map
|
||||||
|
packages/server/src/controllers/index/NotificationController.d.ts
|
||||||
|
packages/server/src/controllers/index/NotificationController.js
|
||||||
|
packages/server/src/controllers/index/NotificationController.js.map
|
||||||
packages/server/src/controllers/index/ProfileController.d.ts
|
packages/server/src/controllers/index/ProfileController.d.ts
|
||||||
packages/server/src/controllers/index/ProfileController.js
|
packages/server/src/controllers/index/ProfileController.js
|
||||||
packages/server/src/controllers/index/ProfileController.js.map
|
packages/server/src/controllers/index/ProfileController.js.map
|
||||||
@@ -1469,9 +1481,21 @@ packages/server/src/controllers/index/UserController.js.map
|
|||||||
packages/server/src/db.d.ts
|
packages/server/src/db.d.ts
|
||||||
packages/server/src/db.js
|
packages/server/src/db.js
|
||||||
packages/server/src/db.js.map
|
packages/server/src/db.js.map
|
||||||
|
packages/server/src/middleware/notificationHandler.d.ts
|
||||||
|
packages/server/src/middleware/notificationHandler.js
|
||||||
|
packages/server/src/middleware/notificationHandler.js.map
|
||||||
|
packages/server/src/middleware/ownerHandler.d.ts
|
||||||
|
packages/server/src/middleware/ownerHandler.js
|
||||||
|
packages/server/src/middleware/ownerHandler.js.map
|
||||||
|
packages/server/src/middleware/routeHandler.d.ts
|
||||||
|
packages/server/src/middleware/routeHandler.js
|
||||||
|
packages/server/src/middleware/routeHandler.js.map
|
||||||
packages/server/src/migrations/20190913171451_create.d.ts
|
packages/server/src/migrations/20190913171451_create.d.ts
|
||||||
packages/server/src/migrations/20190913171451_create.js
|
packages/server/src/migrations/20190913171451_create.js
|
||||||
packages/server/src/migrations/20190913171451_create.js.map
|
packages/server/src/migrations/20190913171451_create.js.map
|
||||||
|
packages/server/src/migrations/20203012152842_notifications.d.ts
|
||||||
|
packages/server/src/migrations/20203012152842_notifications.js
|
||||||
|
packages/server/src/migrations/20203012152842_notifications.js.map
|
||||||
packages/server/src/models/ApiClientModel.d.ts
|
packages/server/src/models/ApiClientModel.d.ts
|
||||||
packages/server/src/models/ApiClientModel.js
|
packages/server/src/models/ApiClientModel.js
|
||||||
packages/server/src/models/ApiClientModel.js.map
|
packages/server/src/models/ApiClientModel.js.map
|
||||||
@@ -1490,6 +1514,12 @@ packages/server/src/models/FileModel.js.map
|
|||||||
packages/server/src/models/FileModel.test.d.ts
|
packages/server/src/models/FileModel.test.d.ts
|
||||||
packages/server/src/models/FileModel.test.js
|
packages/server/src/models/FileModel.test.js
|
||||||
packages/server/src/models/FileModel.test.js.map
|
packages/server/src/models/FileModel.test.js.map
|
||||||
|
packages/server/src/models/NotificationModel.d.ts
|
||||||
|
packages/server/src/models/NotificationModel.js
|
||||||
|
packages/server/src/models/NotificationModel.js.map
|
||||||
|
packages/server/src/models/NotificationModel.test.d.ts
|
||||||
|
packages/server/src/models/NotificationModel.test.js
|
||||||
|
packages/server/src/models/NotificationModel.test.js.map
|
||||||
packages/server/src/models/PermissionModel.d.ts
|
packages/server/src/models/PermissionModel.d.ts
|
||||||
packages/server/src/models/PermissionModel.js
|
packages/server/src/models/PermissionModel.js
|
||||||
packages/server/src/models/PermissionModel.js.map
|
packages/server/src/models/PermissionModel.js.map
|
||||||
@@ -1535,6 +1565,9 @@ packages/server/src/routes/index/login.js.map
|
|||||||
packages/server/src/routes/index/logout.d.ts
|
packages/server/src/routes/index/logout.d.ts
|
||||||
packages/server/src/routes/index/logout.js
|
packages/server/src/routes/index/logout.js
|
||||||
packages/server/src/routes/index/logout.js.map
|
packages/server/src/routes/index/logout.js.map
|
||||||
|
packages/server/src/routes/index/notifications.d.ts
|
||||||
|
packages/server/src/routes/index/notifications.js
|
||||||
|
packages/server/src/routes/index/notifications.js.map
|
||||||
packages/server/src/routes/index/profile.d.ts
|
packages/server/src/routes/index/profile.d.ts
|
||||||
packages/server/src/routes/index/profile.js
|
packages/server/src/routes/index/profile.js
|
||||||
packages/server/src/routes/index/profile.js.map
|
packages/server/src/routes/index/profile.js.map
|
||||||
|
|||||||
@@ -65,7 +65,12 @@ module.exports = {
|
|||||||
'no-var': ['error'],
|
'no-var': ['error'],
|
||||||
'no-new-func': ['error'],
|
'no-new-func': ['error'],
|
||||||
'import/prefer-default-export': ['error'],
|
'import/prefer-default-export': ['error'],
|
||||||
'import/first': ['error'],
|
|
||||||
|
// This rule should not be enabled since it matters in what order
|
||||||
|
// imports are done, in particular in relation to the shim.setReact
|
||||||
|
// call, which should be done first, but this rule might move it down.
|
||||||
|
// 'import/first': ['error'],
|
||||||
|
|
||||||
'no-array-constructor': ['error'],
|
'no-array-constructor': ['error'],
|
||||||
'radix': ['error'],
|
'radix': ['error'],
|
||||||
|
|
||||||
|
|||||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -144,6 +144,9 @@ packages/app-cli/tests/support/plugins/content_script/api/types.js.map
|
|||||||
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
|
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
|
||||||
packages/app-cli/tests/support/plugins/content_script/src/index.js
|
packages/app-cli/tests/support/plugins/content_script/src/index.js
|
||||||
packages/app-cli/tests/support/plugins/content_script/src/index.js.map
|
packages/app-cli/tests/support/plugins/content_script/src/index.js.map
|
||||||
|
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.d.ts
|
||||||
|
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js
|
||||||
|
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js.map
|
||||||
packages/app-cli/tests/support/plugins/dialog/api/index.d.ts
|
packages/app-cli/tests/support/plugins/dialog/api/index.d.ts
|
||||||
packages/app-cli/tests/support/plugins/dialog/api/index.js
|
packages/app-cli/tests/support/plugins/dialog/api/index.js
|
||||||
packages/app-cli/tests/support/plugins/dialog/api/index.js.map
|
packages/app-cli/tests/support/plugins/dialog/api/index.js.map
|
||||||
@@ -1377,6 +1380,12 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
|
|||||||
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
|
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
|
||||||
packages/renderer/MdToHtml/rules/sanitize_html.js
|
packages/renderer/MdToHtml/rules/sanitize_html.js
|
||||||
packages/renderer/MdToHtml/rules/sanitize_html.js.map
|
packages/renderer/MdToHtml/rules/sanitize_html.js.map
|
||||||
|
packages/renderer/MdToHtml/setupLinkify.d.ts
|
||||||
|
packages/renderer/MdToHtml/setupLinkify.js
|
||||||
|
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/htmlUtils.d.ts
|
packages/renderer/htmlUtils.d.ts
|
||||||
packages/renderer/htmlUtils.js
|
packages/renderer/htmlUtils.js
|
||||||
packages/renderer/htmlUtils.js.map
|
packages/renderer/htmlUtils.js.map
|
||||||
@@ -1449,6 +1458,9 @@ packages/server/src/controllers/index/HomeController.js.map
|
|||||||
packages/server/src/controllers/index/LoginController.d.ts
|
packages/server/src/controllers/index/LoginController.d.ts
|
||||||
packages/server/src/controllers/index/LoginController.js
|
packages/server/src/controllers/index/LoginController.js
|
||||||
packages/server/src/controllers/index/LoginController.js.map
|
packages/server/src/controllers/index/LoginController.js.map
|
||||||
|
packages/server/src/controllers/index/NotificationController.d.ts
|
||||||
|
packages/server/src/controllers/index/NotificationController.js
|
||||||
|
packages/server/src/controllers/index/NotificationController.js.map
|
||||||
packages/server/src/controllers/index/ProfileController.d.ts
|
packages/server/src/controllers/index/ProfileController.d.ts
|
||||||
packages/server/src/controllers/index/ProfileController.js
|
packages/server/src/controllers/index/ProfileController.js
|
||||||
packages/server/src/controllers/index/ProfileController.js.map
|
packages/server/src/controllers/index/ProfileController.js.map
|
||||||
@@ -1458,9 +1470,21 @@ packages/server/src/controllers/index/UserController.js.map
|
|||||||
packages/server/src/db.d.ts
|
packages/server/src/db.d.ts
|
||||||
packages/server/src/db.js
|
packages/server/src/db.js
|
||||||
packages/server/src/db.js.map
|
packages/server/src/db.js.map
|
||||||
|
packages/server/src/middleware/notificationHandler.d.ts
|
||||||
|
packages/server/src/middleware/notificationHandler.js
|
||||||
|
packages/server/src/middleware/notificationHandler.js.map
|
||||||
|
packages/server/src/middleware/ownerHandler.d.ts
|
||||||
|
packages/server/src/middleware/ownerHandler.js
|
||||||
|
packages/server/src/middleware/ownerHandler.js.map
|
||||||
|
packages/server/src/middleware/routeHandler.d.ts
|
||||||
|
packages/server/src/middleware/routeHandler.js
|
||||||
|
packages/server/src/middleware/routeHandler.js.map
|
||||||
packages/server/src/migrations/20190913171451_create.d.ts
|
packages/server/src/migrations/20190913171451_create.d.ts
|
||||||
packages/server/src/migrations/20190913171451_create.js
|
packages/server/src/migrations/20190913171451_create.js
|
||||||
packages/server/src/migrations/20190913171451_create.js.map
|
packages/server/src/migrations/20190913171451_create.js.map
|
||||||
|
packages/server/src/migrations/20203012152842_notifications.d.ts
|
||||||
|
packages/server/src/migrations/20203012152842_notifications.js
|
||||||
|
packages/server/src/migrations/20203012152842_notifications.js.map
|
||||||
packages/server/src/models/ApiClientModel.d.ts
|
packages/server/src/models/ApiClientModel.d.ts
|
||||||
packages/server/src/models/ApiClientModel.js
|
packages/server/src/models/ApiClientModel.js
|
||||||
packages/server/src/models/ApiClientModel.js.map
|
packages/server/src/models/ApiClientModel.js.map
|
||||||
@@ -1479,6 +1503,12 @@ packages/server/src/models/FileModel.js.map
|
|||||||
packages/server/src/models/FileModel.test.d.ts
|
packages/server/src/models/FileModel.test.d.ts
|
||||||
packages/server/src/models/FileModel.test.js
|
packages/server/src/models/FileModel.test.js
|
||||||
packages/server/src/models/FileModel.test.js.map
|
packages/server/src/models/FileModel.test.js.map
|
||||||
|
packages/server/src/models/NotificationModel.d.ts
|
||||||
|
packages/server/src/models/NotificationModel.js
|
||||||
|
packages/server/src/models/NotificationModel.js.map
|
||||||
|
packages/server/src/models/NotificationModel.test.d.ts
|
||||||
|
packages/server/src/models/NotificationModel.test.js
|
||||||
|
packages/server/src/models/NotificationModel.test.js.map
|
||||||
packages/server/src/models/PermissionModel.d.ts
|
packages/server/src/models/PermissionModel.d.ts
|
||||||
packages/server/src/models/PermissionModel.js
|
packages/server/src/models/PermissionModel.js
|
||||||
packages/server/src/models/PermissionModel.js.map
|
packages/server/src/models/PermissionModel.js.map
|
||||||
@@ -1524,6 +1554,9 @@ packages/server/src/routes/index/login.js.map
|
|||||||
packages/server/src/routes/index/logout.d.ts
|
packages/server/src/routes/index/logout.d.ts
|
||||||
packages/server/src/routes/index/logout.js
|
packages/server/src/routes/index/logout.js
|
||||||
packages/server/src/routes/index/logout.js.map
|
packages/server/src/routes/index/logout.js.map
|
||||||
|
packages/server/src/routes/index/notifications.d.ts
|
||||||
|
packages/server/src/routes/index/notifications.js
|
||||||
|
packages/server/src/routes/index/notifications.js.map
|
||||||
packages/server/src/routes/index/profile.d.ts
|
packages/server/src/routes/index/profile.d.ts
|
||||||
packages/server/src/routes/index/profile.js
|
packages/server/src/routes/index/profile.js
|
||||||
packages/server/src/routes/index/profile.js.map
|
packages/server/src/routes/index/profile.js.map
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -109,6 +109,8 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
|
|||||||
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
|
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
|
||||||
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
|
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
|
||||||
- [Search Sorting spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/search_sorting.md)
|
- [Search Sorting spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/search_sorting.md)
|
||||||
|
- [Server: File URL Format](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_file_url_format.md)
|
||||||
|
- [Server: Delta Sync](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_delta_sync.md)
|
||||||
|
|
||||||
- Google Summer of Code 2020
|
- Google Summer of Code 2020
|
||||||
|
|
||||||
@@ -444,15 +446,15 @@ Current translations:
|
|||||||
 | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 26%
|
 | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 26%
|
||||||
 | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 93%
|
 | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 93%
|
||||||
 | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 77%
|
 | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 77%
|
||||||
 | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 96%
|
 | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 95%
|
||||||
 | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 61%
|
 | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 61%
|
||||||
 | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
 | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||||
 | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
 | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||||
 | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 93%
|
 | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 99%
|
||||||
 | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 35%
|
 | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 35%
|
||||||
 | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | | 94%
|
 | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | | 94%
|
||||||
 | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 93%
|
 | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
|
||||||
 | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 41%
|
 | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 40%
|
||||||
 | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 85%
|
 | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 85%
|
||||||
 | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 96%
|
 | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 96%
|
||||||
 | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 32%
|
 | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 32%
|
||||||
@@ -467,14 +469,14 @@ Current translations:
|
|||||||
 | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 66%
|
 | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 66%
|
||||||
 | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 49%
|
 | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 49%
|
||||||
 | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
|
 | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
|
||||||
 | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 92%
|
 | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 99%
|
||||||
 | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
|
 | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
|
||||||
 | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 96%
|
 | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 96%
|
||||||
 | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 67%
|
 | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 67%
|
||||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 97%
|
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 99%
|
||||||
 | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 89%
|
 | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
|
||||||
 | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
|
 | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
|
||||||
 | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 97%
|
 | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
|
||||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||||
|
|
||||||
# Contributors
|
# Contributors
|
||||||
|
|||||||
@@ -54,5 +54,5 @@ module.exports = {
|
|||||||
|
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
|
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
|
||||||
slowTestThreshold: 20,
|
slowTestThreshold: 40,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -147,27 +147,86 @@ describe('MdToHtml', function() {
|
|||||||
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
|
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// it('should render links correctly', (async () => {
|
it('should render links correctly', (async () => {
|
||||||
// const mdToHtml = newTestMdToHtml();
|
const testCases = [
|
||||||
|
// 0: input
|
||||||
|
// 1: output with linkify = off
|
||||||
|
// 2: output with linkify = on
|
||||||
|
[
|
||||||
|
'https://example.com',
|
||||||
|
'https://example.com',
|
||||||
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'file://C:\\AUTOEXEC.BAT',
|
||||||
|
'file://C:\\AUTOEXEC.BAT',
|
||||||
|
'<a data-from-md title=\'file://C:%5CAUTOEXEC.BAT\' href=\'file://C:%5CAUTOEXEC.BAT\'>file://C:\\AUTOEXEC.BAT</a>',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'example.com',
|
||||||
|
'example.com',
|
||||||
|
'example.com',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'oo.ps',
|
||||||
|
'oo.ps',
|
||||||
|
'oo.ps',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'test@example.com',
|
||||||
|
'test@example.com',
|
||||||
|
'test@example.com',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<https://example.com>',
|
||||||
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
|
||||||
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'[ok](https://example.com)',
|
||||||
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
|
||||||
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'[bla.pdf](file:///Users/tessus/Downloads/bla.pdf)',
|
||||||
|
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
|
||||||
|
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
// const testCases = [
|
const mdToHtmlLinkifyOn = newTestMdToHtml({
|
||||||
// // None of these should result in a link
|
pluginOptions: {
|
||||||
// ['https://example.com', 'https://example.com'],
|
linkify: { enabled: true },
|
||||||
// ['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
|
},
|
||||||
// ['example.com', 'example.com'],
|
});
|
||||||
// ['oo.ps', 'oo.ps'],
|
|
||||||
// ['test@example.com', 'test@example.com'],
|
|
||||||
|
|
||||||
// // Those should be converted to links
|
const mdToHtmlLinkifyOff = newTestMdToHtml({
|
||||||
// ['<https://example.com>', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>'],
|
pluginOptions: {
|
||||||
// ['[ok](https://example.com)', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>'],
|
linkify: { enabled: false },
|
||||||
// ];
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// for (const testCase of testCases) {
|
for (const testCase of testCases) {
|
||||||
// const [input, expected] = testCase;
|
const [input, expectedLinkifyOff, expectedLinkifyOn] = testCase;
|
||||||
// const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
|
|
||||||
// expect(actual.html).toBe(expected);
|
{
|
||||||
// }
|
const actual = await mdToHtmlLinkifyOn.render(input, null, {
|
||||||
// }));
|
bodyOnly: true,
|
||||||
|
plainResourceRendering: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(actual.html).toBe(expectedLinkifyOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const actual = await mdToHtmlLinkifyOff.render(input, null, {
|
||||||
|
bodyOnly: true,
|
||||||
|
plainResourceRendering: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(actual.html).toBe(expectedLinkifyOff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ describe('services_PluginService', function() {
|
|||||||
|
|
||||||
const contentScriptPath = `${tempDir}/markdownItTestPlugin.js`;
|
const contentScriptPath = `${tempDir}/markdownItTestPlugin.js`;
|
||||||
const contentScriptCssPath = `${tempDir}/markdownItTestPlugin.css`;
|
const contentScriptCssPath = `${tempDir}/markdownItTestPlugin.css`;
|
||||||
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.js`, contentScriptPath);
|
await shim.fsDriver().copy(`${testPluginDir}/markdownItTestPlugin.js`, contentScriptPath);
|
||||||
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.css`, contentScriptCssPath);
|
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.css`, contentScriptCssPath);
|
||||||
|
|
||||||
const service = newPluginService();
|
const service = newPluginService();
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,6 +492,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"bluebird": {
|
"bluebird": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
@@ -1436,6 +1446,13 @@
|
|||||||
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
|
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
@@ -2192,6 +2209,11 @@
|
|||||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"left-pad": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="
|
||||||
|
},
|
||||||
"loader-runner": {
|
"loader-runner": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
|
||||||
@@ -2464,6 +2486,13 @@
|
|||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"nan": {
|
||||||
|
"version": "2.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||||
|
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
@@ -4086,7 +4115,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"nan": "^2.12.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
|
|||||||
@@ -19,5 +19,8 @@
|
|||||||
"typescript": "^3.9.3",
|
"typescript": "^3.9.3",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.43.0",
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"left-pad": "^1.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,8 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"homepage_url": ""
|
"homepage_url": "",
|
||||||
|
"content_scripts": [
|
||||||
|
"markdownItTestPlugin"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const leftPad = require('left-pad');
|
||||||
|
|
||||||
function plugin(markdownIt, _options) {
|
function plugin(markdownIt, _options) {
|
||||||
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
|
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
|
||||||
return self.renderToken(tokens, idx, options, env, self);
|
return self.renderToken(tokens, idx, options, env, self);
|
||||||
@@ -8,7 +10,7 @@ function plugin(markdownIt, _options) {
|
|||||||
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
|
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
|
||||||
return `
|
return `
|
||||||
<div class="just-testing">
|
<div class="just-testing">
|
||||||
<p>JUST TESTING: ${token.content}</p>
|
<p>JUST TESTING: <pre>${leftPad(token.content.trim(), 10, 'x')}</pre></p>
|
||||||
<p><a href="#" onclick="webviewApi.executeCommand('testCommand', 'one', 'two'); return false;">Click to send "testCommand" to plugin</a></p>
|
<p><a href="#" onclick="webviewApi.executeCommand('testCommand', 'one', 'two'); return false;">Click to send "testCommand" to plugin</a></p>
|
||||||
<p><a href="#" onclick="webviewApi.executeCommand('testCommandNoArgs'); return false;">Click to send "testCommandNoArgs" to plugin</a></p>
|
<p><a href="#" onclick="webviewApi.executeCommand('testCommandNoArgs'); return false;">Click to send "testCommandNoArgs" to plugin</a></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,8 +18,7 @@ function plugin(markdownIt, _options) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default function(_context) {
|
||||||
default: function(_context) {
|
|
||||||
return {
|
return {
|
||||||
plugin: plugin,
|
plugin: plugin,
|
||||||
assets: function() {
|
assets: function() {
|
||||||
@@ -26,5 +27,4 @@ module.exports = {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
function plugin(markdownIt, _options) {
|
||||||
|
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
|
||||||
|
return self.renderToken(tokens, idx, options, env, self);
|
||||||
|
};
|
||||||
|
|
||||||
|
markdownIt.renderer.rules.fence = function(tokens, idx, options, env, self) {
|
||||||
|
const token = tokens[idx];
|
||||||
|
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
|
||||||
|
return `
|
||||||
|
<div class="just-testing">
|
||||||
|
<p>JUST TESTING: ${token.content}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
default: function(_context) {
|
||||||
|
return {
|
||||||
|
plugin: plugin,
|
||||||
|
assets: function() {
|
||||||
|
return [
|
||||||
|
{ name: 'markdownItTestPlugin.css' }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
83
packages/app-cli/tests/support/plugins/menu/GENERATOR_DOC.md
Normal file
83
packages/app-cli/tests/support/plugins/menu/GENERATOR_DOC.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
83
packages/app-cli/tests/support/plugins/toc/GENERATOR_DOC.md
Normal file
83
packages/app-cli/tests/support/plugins/toc/GENERATOR_DOC.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import joplin from 'api';
|
import joplin from 'api';
|
||||||
|
import { ToolbarButtonLocation } from 'api/types';
|
||||||
|
|
||||||
const uslug = require('uslug');
|
const uslug = require('uslug');
|
||||||
|
|
||||||
@@ -91,6 +92,18 @@ joplin.plugins.register({
|
|||||||
updateTocView();
|
updateTocView();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await joplin.commands.register({
|
||||||
|
name: 'toggleToc',
|
||||||
|
label: 'Toggle TOC',
|
||||||
|
iconName: 'fas fa-drum',
|
||||||
|
execute: async () => {
|
||||||
|
const isVisible = await (panels as any).visible(view);
|
||||||
|
(panels as any).show(view, !isVisible);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await joplin.views.toolbarButtons.create('toggleToc', 'toggleToc', ToolbarButtonLocation.NoteToolbar);
|
||||||
|
|
||||||
updateTocView();
|
updateTocView();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
|||||||
*/
|
*/
|
||||||
register(script: Script): Promise<void>;
|
register(script: Script): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Registers a new content script. Unlike regular plugin code, which
|
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||||
* runs in a separate process, content scripts run within the main
|
* a separate process, content scripts run within the main process code and
|
||||||
* process code and thus allow improved performances and more
|
* thus allow improved performances and more customisations in specific
|
||||||
* customisations in specific cases. It can be used for example to load
|
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
* a Markdown or editor plugin.
|
|
||||||
*
|
*
|
||||||
* Note that registering a content script in itself will do nothing -
|
* Note that registering a content script in itself will do nothing - it
|
||||||
* it will only be loaded in specific cases by the relevant app modules
|
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||||
* (eg. the Markdown renderer or the code editor). So it is not a way
|
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||||
* to inject and run arbitrary code in the app, which for safety and
|
* and run arbitrary code in the app, which for safety and performance
|
||||||
* performance reasons is not supported.
|
* reasons is not supported.
|
||||||
|
*
|
||||||
|
* The plugin generator provides a way to build any content script you might
|
||||||
|
* want to package as well as its dependencies. See the [Plugin Generator
|
||||||
|
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||||
|
* for more information.
|
||||||
*
|
*
|
||||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
|
|||||||
* Sets the dialog HTML content
|
* Sets the dialog HTML content
|
||||||
*/
|
*/
|
||||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Adds and loads a new JS or CSS files into the dialog.
|
||||||
|
*/
|
||||||
|
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Sets the dialog buttons.
|
* Sets the dialog buttons.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
|
|||||||
* Called when a message is sent from the webview (using postMessage).
|
* Called when a message is sent from the webview (using postMessage).
|
||||||
*/
|
*/
|
||||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Shows the panel
|
||||||
|
*/
|
||||||
|
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Hides the panel
|
||||||
|
*/
|
||||||
|
hide(handle: ViewHandle): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Tells whether the panel is visible or not
|
||||||
|
*/
|
||||||
|
visible(handle: ViewHandle): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
|
|||||||
console.info(`Plugin archive has been created in ${destPath}`);
|
console.info(`Plugin archive has been created in ${destPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'dist');
|
const rootDir = path.resolve(__dirname);
|
||||||
const srcDir = path.resolve(__dirname, 'src');
|
const distDir = path.resolve(rootDir, 'dist');
|
||||||
|
const srcDir = path.resolve(rootDir, 'src');
|
||||||
const manifestPath = `${srcDir}/manifest.json`;
|
const manifestPath = `${srcDir}/manifest.json`;
|
||||||
const manifest = readManifest(manifestPath);
|
const manifest = readManifest(manifestPath);
|
||||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||||
|
|
||||||
fs.removeSync(distDir);
|
fs.removeSync(distDir);
|
||||||
|
|
||||||
module.exports = {
|
const baseConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: './src/index.ts',
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -60,6 +60,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginConfig = Object.assign({}, baseConfig, {
|
||||||
|
entry: './src/index.ts',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
api: path.resolve(__dirname, 'api'),
|
api: path.resolve(__dirname, 'api'),
|
||||||
@@ -70,6 +74,9 @@ module.exports = {
|
|||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
path: distDir,
|
path: distDir,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastStepConfig = {
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -79,8 +86,17 @@ module.exports = {
|
|||||||
to: path.resolve(__dirname, 'dist'),
|
to: path.resolve(__dirname, 'dist'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
// All TypeScript files are compiled to JS and
|
||||||
|
// already copied into /dist so we don't copy them.
|
||||||
'**/*.ts',
|
'**/*.ts',
|
||||||
'**/*.tsx',
|
'**/*.tsx',
|
||||||
|
|
||||||
|
// Currently we don't support JS files for the main
|
||||||
|
// plugin script. We support it for content scripts,
|
||||||
|
// but theyr should be declared in manifest.json,
|
||||||
|
// and then they are also compiled and copied to
|
||||||
|
// /dist. So wse also don't need to copy JS files.
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,3 +107,62 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
api: path.resolve(__dirname, 'api'),
|
||||||
|
},
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveContentScriptPaths(name) {
|
||||||
|
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||||
|
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToTry = [
|
||||||
|
`./src/${name}.ts`,
|
||||||
|
`${'./src/' + '/'}${name}.js`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pathToTry of pathsToTry) {
|
||||||
|
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
|
||||||
|
return {
|
||||||
|
entry: pathToTry,
|
||||||
|
output: {
|
||||||
|
filename: `${name}.js`,
|
||||||
|
path: distDir,
|
||||||
|
library: 'default',
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
libraryExport: 'default',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContentScriptConfigs() {
|
||||||
|
if (!manifest.content_scripts) return [];
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const contentScriptName of manifest.content_scripts) {
|
||||||
|
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||||
|
output.push(Object.assign({}, contentScriptConfig, {
|
||||||
|
entry: scriptPaths.entry,
|
||||||
|
output: scriptPaths.output,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||||
|
|
||||||
|
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||||
|
|
||||||
|
module.exports = exportedConfigs;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Joplin Web Clipper [DEV]",
|
"name": "Joplin Web Clipper [DEV]",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||||
"homepage_url": "https://joplinapp.org",
|
"homepage_url": "https://joplinapp.org",
|
||||||
"content_security_policy": "script-src 'self'; object-src 'self'",
|
"content_security_policy": "script-src 'self'; object-src 'self'",
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
|||||||
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
|
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
|
||||||
import ResourceService from '@joplin/lib/services/ResourceService';
|
import ResourceService from '@joplin/lib/services/ResourceService';
|
||||||
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
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';
|
||||||
|
|
||||||
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||||
const MasterKey = require('@joplin/lib/models/MasterKey');
|
const MasterKey = require('@joplin/lib/models/MasterKey');
|
||||||
@@ -247,6 +250,29 @@ class Application extends BaseApplication {
|
|||||||
};
|
};
|
||||||
break;
|
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':
|
case 'NOTE_FILE_WATCHER_ADD':
|
||||||
|
|
||||||
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
|
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
|
||||||
|
|||||||
@@ -387,6 +387,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
|||||||
padding-bottom: 400px !important;
|
padding-bottom: 400px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Left padding is applied at the editor component level, so we should remove it from the lines */
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-sizer {
|
.CodeMirror-sizer {
|
||||||
/* Add a fixed right padding to account for the appearance (and disappearance) */
|
/* Add a fixed right padding to account for the appearance (and disappearance) */
|
||||||
/* of the sidebar */
|
/* of the sidebar */
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export default function useKeymap(CodeMirror: any) {
|
|||||||
};
|
};
|
||||||
if (shim.isMac()) {
|
if (shim.isMac()) {
|
||||||
CodeMirror.keyMap.default = {
|
CodeMirror.keyMap.default = {
|
||||||
// MacOS
|
// macOS
|
||||||
'Shift-Cmd-Z': 'redo',
|
'Shift-Cmd-Z': 'redo',
|
||||||
'Cmd-Y': 'redo',
|
'Cmd-Y': 'redo',
|
||||||
'Cmd-End': 'goDocEnd',
|
'Cmd-End': 'goDocEnd',
|
||||||
@@ -153,10 +153,12 @@ export default function useKeymap(CodeMirror: any) {
|
|||||||
'Cmd-Home': 'goDocStart',
|
'Cmd-Home': 'goDocStart',
|
||||||
'Cmd-Up': 'goDocStart',
|
'Cmd-Up': 'goDocStart',
|
||||||
'Ctrl-D': 'delCharAfter',
|
'Ctrl-D': 'delCharAfter',
|
||||||
'Cmd-Left': 'goGroupLeft',
|
'Alt-Left': 'goGroupLeft',
|
||||||
'Cmd-Right': 'goGroupRight',
|
'Alt-Right': 'goGroupRight',
|
||||||
'Ctrl-A': 'goLineStart',
|
'Ctrl-A': 'goLineStart',
|
||||||
'Ctrl-E': 'goLineEnd',
|
'Ctrl-E': 'goLineEnd',
|
||||||
|
'Cmd-Left': 'goLineLeftSmart',
|
||||||
|
'Cmd-Right': 'goLineRightSmart',
|
||||||
'Alt-Backspace': 'delGroupBefore',
|
'Alt-Backspace': 'delGroupBefore',
|
||||||
'Alt-Delete': 'delGroupAfter',
|
'Alt-Delete': 'delGroupAfter',
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const taboverride = require('taboverride');
|
|||||||
const { reg } = require('@joplin/lib/registry.js');
|
const { reg } = require('@joplin/lib/registry.js');
|
||||||
const BaseItem = require('@joplin/lib/models/BaseItem');
|
const BaseItem = require('@joplin/lib/models/BaseItem');
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
// const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
const supportedLocales = require('./supportedLocales');
|
const supportedLocales = require('./supportedLocales');
|
||||||
|
|
||||||
function markupRenderOptions(override: any = null) {
|
function markupRenderOptions(override: any = null) {
|
||||||
@@ -1015,19 +1015,22 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(_event: any) {
|
function onKeyDown(event: any) {
|
||||||
// It seems "paste as text" is now handled automatically by
|
// It seems "paste as text" is handled automatically by
|
||||||
// either Chrome, Electron and/or TinyMCE so the code below
|
// on Windows so the code below so we need to run the below
|
||||||
// should not longer be necessary. Also fixes
|
// code only on macOS (and maybe Linux). If we were to run
|
||||||
|
// this on Windows we would have this double-paste issue:
|
||||||
// https://github.com/laurent22/joplin/issues/4243
|
// https://github.com/laurent22/joplin/issues/4243
|
||||||
|
|
||||||
// Handle "paste as text". Note that when pressing CtrlOrCmd+Shift+V it's going
|
// Handle "paste as text". Note that when pressing CtrlOrCmd+Shift+V it's going
|
||||||
// to trigger the "keydown" event but not the "paste" event, so it's ok to process
|
// to trigger the "keydown" event but not the "paste" event, so it's ok to process
|
||||||
// it here and we don't need to do anything special in onPaste
|
// it here and we don't need to do anything special in onPaste
|
||||||
// if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
|
if (!shim.isWindows()) {
|
||||||
// const pastedText = clipboard.readText();
|
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
|
||||||
// if (pastedText) editor.insertContent(pastedText);
|
const pastedText = clipboard.readText();
|
||||||
// }
|
if (pastedText) editor.insertContent(pastedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.on('keyup', onKeyUp);
|
editor.on('keyup', onKeyUp);
|
||||||
|
|||||||
@@ -498,9 +498,10 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderSearchInfo() {
|
function renderSearchInfo() {
|
||||||
|
const theme = themeStyle(props.themeId);
|
||||||
if (formNoteFolder && ['Search', 'Tag', 'SmartFilter'].includes(props.notesParentType)) {
|
if (formNoteFolder && ['Search', 'Tag', 'SmartFilter'].includes(props.notesParentType)) {
|
||||||
return (
|
return (
|
||||||
<div style={{ paddingTop: 10, paddingBottom: 10 }}>
|
<div style={{ paddingTop: 10, paddingBottom: 10, paddingLeft: theme.editorPaddingLeft }}>
|
||||||
<Button
|
<Button
|
||||||
iconName="icon-notebooks"
|
iconName="icon-notebooks"
|
||||||
level={ButtonLevel.Primary}
|
level={ButtonLevel.Primary}
|
||||||
@@ -525,6 +526,8 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
return renderNoNotes(styles.root);
|
return renderNoNotes(styles.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const theme = themeStyle(props.themeId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.root} onDrop={onDrop}>
|
<div style={styles.root} onDrop={onDrop}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
@@ -539,13 +542,13 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
onTitleChange={onTitleChange}
|
onTitleChange={onTitleChange}
|
||||||
/>
|
/>
|
||||||
{renderSearchInfo()}
|
{renderSearchInfo()}
|
||||||
<div style={{ display: 'flex', flex: 1 }}>
|
<div style={{ display: 'flex', flex: 1, paddingLeft: theme.editorPaddingLeft }}>
|
||||||
{editor}
|
{editor}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||||
{renderSearchBar()}
|
{renderSearchBar()}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', height: 40 }}>
|
<div style={{ paddingLeft: theme.editorPaddingLeft, display: 'flex', flexDirection: 'row', alignItems: 'center', height: 40 }}>
|
||||||
{renderTagButton()}
|
{renderTagButton()}
|
||||||
{renderTagBar()}
|
{renderTagBar()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const StyledRoot = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-left: ${props => props.theme.editorPaddingLeft}px;
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -52,7 +53,6 @@ function styles_(props: Props) {
|
|||||||
paddingBottom: 5,
|
paddingBottom: 5,
|
||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
paddingRight: 8,
|
paddingRight: 8,
|
||||||
marginLeft: 5,
|
|
||||||
color: theme.textStyle.color,
|
color: theme.textStyle.color,
|
||||||
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
|
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
|
||||||
backgroundColor: theme.backgroundColor,
|
backgroundColor: theme.backgroundColor,
|
||||||
|
|||||||
@@ -16,8 +16,4 @@ export default function findItemByKey(layout: LayoutItem, key: string): LayoutIt
|
|||||||
}
|
}
|
||||||
|
|
||||||
return recurseFind(layout);
|
return recurseFind(layout);
|
||||||
|
|
||||||
// const output = recurseFind(layout);
|
|
||||||
// if (!output) throw new Error(`Could not find item "${key}"`);
|
|
||||||
// return output;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { LayoutItem } from './types';
|
|||||||
|
|
||||||
type ItemItemCallback = (itemIndex: number, item: LayoutItem, parent: LayoutItem)=> boolean;
|
type ItemItemCallback = (itemIndex: number, item: LayoutItem, parent: LayoutItem)=> boolean;
|
||||||
|
|
||||||
|
// Callback should return `true` if iteration should continue, or `false` if it
|
||||||
|
// should stop
|
||||||
export default function iterateItems(layout: LayoutItem, callback: ItemItemCallback) {
|
export default function iterateItems(layout: LayoutItem, callback: ItemItemCallback) {
|
||||||
const result = callback(0, layout, null);
|
const result = callback(0, layout, null);
|
||||||
if (result === false) return;
|
if (result === false) return;
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
|||||||
order: [{
|
order: [{
|
||||||
by: getSortingOrderColumn(sorting.order),
|
by: getSortingOrderColumn(sorting.order),
|
||||||
dir: sorting.type,
|
dir: sorting.type,
|
||||||
|
caseInsensitive: true,
|
||||||
}],
|
}],
|
||||||
limit: MAX_RESOURCES,
|
limit: MAX_RESOURCES,
|
||||||
fields: ['title', 'id', 'size', 'file_extension'],
|
fields: ['title', 'id', 'size', 'file_extension'],
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# It could be used to develop plugins too.
|
# It could be used to develop plugins too.
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/register_command"
|
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/toc"
|
||||||
npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH"
|
npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH"
|
||||||
@@ -138,8 +138,8 @@ android {
|
|||||||
applicationId "net.cozic.joplin"
|
applicationId "net.cozic.joplin"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 2097617
|
versionCode 2097618
|
||||||
versionName "1.6.3"
|
versionName "1.6.4"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,16 +19,19 @@
|
|||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Android 10 introduced new "scoped storage" mechanism.
|
android:requestLegacyExternalStorage: Android 10 introduced new "scoped storage" mechanism.
|
||||||
Apps targeting Android 10 (sdk 29) can no longer freely access arbitrary paths on the shared storage.
|
Apps targeting Android 10 (sdk 29) can no longer freely access arbitrary paths on the shared storage.
|
||||||
The attribute "requestLegacyExternalStorage" below allows to opt out of this restriction.
|
This attribute allows to opt out of this restriction.
|
||||||
|
|
||||||
|
android:allowBackup: used to enable Android Backup which some users need:
|
||||||
|
https://github.com/laurent22/joplin/issues/4020
|
||||||
-->
|
-->
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:allowBackup="false"
|
android:allowBackup="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
const React = require('react');
|
||||||
|
import shim from '@joplin/lib/shim';
|
||||||
|
shim.setReact(React);
|
||||||
|
|
||||||
import setUpQuickActions from './setUpQuickActions';
|
import setUpQuickActions from './setUpQuickActions';
|
||||||
import PluginAssetsLoader from './PluginAssetsLoader';
|
import PluginAssetsLoader from './PluginAssetsLoader';
|
||||||
|
|
||||||
import AlarmService from '@joplin/lib/services/AlarmService';
|
import AlarmService from '@joplin/lib/services/AlarmService';
|
||||||
import Alarm from '@joplin/lib/models/Alarm';
|
import Alarm from '@joplin/lib/models/Alarm';
|
||||||
import time from '@joplin/lib/time';
|
import time from '@joplin/lib/time';
|
||||||
@@ -23,10 +26,7 @@ import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainS
|
|||||||
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
|
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
|
||||||
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
|
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
|
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
|
||||||
const shim = require('@joplin/lib/shim').default;
|
|
||||||
shim.setReact(React);
|
|
||||||
|
|
||||||
const DropdownAlert = require('react-native-dropdownalert').default;
|
const DropdownAlert = require('react-native-dropdownalert').default;
|
||||||
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
|
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
|
||||||
|
|||||||
@@ -20,6 +20,63 @@ yo joplin
|
|||||||
## Development
|
## Development
|
||||||
|
|
||||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function mergePackageKey(parentKey, source, dest) {
|
|||||||
// If we are dealing with the dependencies, overwrite with the
|
// If we are dealing with the dependencies, overwrite with the
|
||||||
// version from source.
|
// version from source.
|
||||||
output[k] = source[k];
|
output[k] = source[k];
|
||||||
} else if (typeof source[k] === 'object' && !Array.isArray(k) && source[k] !== null) {
|
} else if (typeof source[k] === 'object' && !Array.isArray(source[k]) && source[k] !== null) {
|
||||||
// If it's an object, recursively process it
|
// If it's an object, recursively process it
|
||||||
output[k] = mergePackageKey(k, source[k], output[k]);
|
output[k] = mergePackageKey(k, source[k], output[k]);
|
||||||
} else {
|
} else {
|
||||||
@@ -116,6 +116,7 @@ module.exports = class extends Generator {
|
|||||||
'.gitignore_TEMPLATE',
|
'.gitignore_TEMPLATE',
|
||||||
'package_TEMPLATE.json',
|
'package_TEMPLATE.json',
|
||||||
'README.md',
|
'README.md',
|
||||||
|
'GENERATOR_DOC.md',
|
||||||
'tsconfig.json',
|
'tsconfig.json',
|
||||||
'webpack.config.js',
|
'webpack.config.js',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# generator-joplin
|
||||||
|
|
||||||
|
Scaffolds out a new Joplin plugin
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g yo
|
||||||
|
npm install -g generator-joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then generate your new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yo joplin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||||
|
This is a template to create a new Joplin plugin.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The main two files you will want to look at are:
|
||||||
|
|
||||||
|
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||||
|
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||||
|
|
||||||
|
## Building the plugin
|
||||||
|
|
||||||
|
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||||
|
|
||||||
|
To build the plugin, simply run `npm run dist`.
|
||||||
|
|
||||||
|
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||||
|
|
||||||
|
## Updating the plugin framework
|
||||||
|
|
||||||
|
To update the plugin framework, run `yo joplin --update`
|
||||||
|
|
||||||
|
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||||
|
|
||||||
|
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||||
|
|
||||||
|
## Content scripts
|
||||||
|
|
||||||
|
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||||
|
|
||||||
|
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||||
|
|
||||||
|
For example, assuming these files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/src
|
||||||
|
index.ts # Main plugin script
|
||||||
|
myContentScript.js # One content script (JS)
|
||||||
|
otherContentScript.ts # Another content script (TypeScript)
|
||||||
|
vendor/
|
||||||
|
test.ts # Sub-directories are also supported
|
||||||
|
```
|
||||||
|
|
||||||
|
The `manifest.json` file would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Testing Content Scripts",
|
||||||
|
content_scripts: [
|
||||||
|
"myContentScript",
|
||||||
|
"otherContentScript",
|
||||||
|
"vendor/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note in particular how the file path is relative to /src and the extensions removed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © Laurent Cozic
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user