1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-14 23:26:58 +02:00

Compare commits

..

32 Commits

Author SHA1 Message Date
Laurent Cozic
6ff44f53da minor fixes 2021-01-03 13:18:36 +00:00
Laurent Cozic
3c8f774e85 Plugins: Updated types 2021-01-03 12:59:16 +00:00
Laurent Cozic
d49439689e update generator 2021-01-03 12:58:00 +00:00
Laurent Cozic
ef3a102741 content script support 2021-01-03 00:51:36 +00:00
Laurent Cozic
0e57baf5b9 Desktop: Made editor padding more consistent and ensure it is present even when sidebars are gone 2021-01-02 17:27:37 +00:00
Laurent Cozic
05f65c326a Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-01-02 16:55:20 +00:00
Laurent Cozic
491714cde6 Desktop, Mobile: Fixed issue where file:// URLs would not be rendered correctly 2021-01-02 16:53:59 +00:00
Laurent Cozic
34c1096307 Plugins: Allow loading external JS or CSS from dialog 2021-01-02 16:21:32 +00:00
Roman Musin
ebe3ddcd21 Android: Resolves #4020: Enable Android Backup support again (#4277) 2021-01-02 15:31:54 +00:00
Daeraxa
ca5ac9696b Desktop: Sort attachments in a case insensitive way in Attachments screen (#4273)
Update to add case insensitive sorting - https://discourse.joplinapp.org/t/a-feature-request/13486/5?u=daeraxa
2021-01-02 15:27:53 +00:00
Caleb John
fdfbb84d37 Desktop: Resolves #4257: Add extra macOS keys (#4259) 2021-01-02 15:12:29 +00:00
Jonathan Heard
740aba90ea All: Resolves #3528: Upload Big Notes to Onedrive (#4120) 2021-01-02 15:09:33 +00:00
Laurent Cozic
eab9ff175c Tools: Added script to simplify iOS release 2021-01-02 14:56:47 +00:00
Laurent Cozic
5b295d5f6f Plugins: Add support for hiding and showing panels 2021-01-02 13:32:15 +00:00
Helmut K. C. Tessarek
0be8cdf760 Update translations 2021-01-01 21:00:35 -05:00
Po-chiang Chao
545940f545 All: Translation: Update zh_TW.po (#4278)
Include new strings and fuzzy translated strings
2021-01-01 19:18:52 -05:00
Laurent Cozic
d58f39823a Doc: Added link to new specs, and added more details to delta sync spec 2021-01-01 13:25:05 +00:00
Laurent Cozic
f9fb1b8a81 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-01-01 13:04:30 +00:00
Laurent Cozic
56ded0062a All: Add more log info when a revision cannot be deleted due to still-encrypted itel 2021-01-01 13:04:15 +00:00
Laurent Cozic
83b29d7c51 macOS: Fixed paste as text in Rich Text editor 2021-01-01 12:38:17 +00:00
Arda Kılıçdağı
1ec0746263 All: Translation: Update tr_TR.po (#4274)
Hello,

You can find the updated Turkish translations in this pull request.

Also, happy new year! 🎉
2020-12-31 12:08:09 -05:00
Laurent Cozic
568d11bddf Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-12-31 16:04:06 +00:00
Laurent Cozic
97b25ac99d Doc: Add spec for Joplin Server delta sync 2020-12-31 15:58:38 +00:00
Laurent Cozic
7f1d3d8a5d Doc: Added spec for server URL formats 2020-12-31 12:38:22 +00:00
Elaborendum
fde201fbe9 All: Translation: Update es_ES.po (#4260)
* Updated es_ES.po

* Reverting some changes

* Redid translation using current .po file
2020-12-31 00:59:25 -05:00
Laurent Cozic
694a1b4ede Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-12-30 23:53:20 +00:00
Laurent Cozic
20d126b39d Server: Fixed access control on user list and clean up 2020-12-30 23:50:44 +00:00
Helmut K. C. Tessarek
8ca9c3092a Clipper release v1.6.1 2020-12-30 14:50:47 -05:00
Laurent Cozic
d2771029a3 Server: Support for notifications and clean up 2020-12-30 18:35:18 +00:00
Laurent Cozic
4128c53fcf Update French translation 2020-12-30 13:07:41 +00:00
Laurent Cozic
a14410b28c Android release v1.6.4 2020-12-30 12:04:25 +00:00
Laurent Cozic
d1f8520e6e Mobile: Fixed package import issue 2020-12-30 11:51:43 +00:00
236 changed files with 6356 additions and 2260 deletions

View File

@@ -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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
packages/server/src/routes/index/profile.js.map

View File

@@ -65,7 +65,12 @@ module.exports = {
'no-var': ['error'],
'no-new-func': ['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'],
'radix': ['error'],

33
.gitignore vendored
View File

@@ -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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
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.js
packages/server/src/routes/index/profile.js.map

View File

@@ -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)
- [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)
- [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
@@ -444,15 +446,15 @@ Current translations:
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 26%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 61%
![](https://joplinapp.org/images/flags/country-4x3/gb.png) | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/us.png) | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | 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%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 35%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | | 94%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 93%
![](https://joplinapp.org/images/flags/es/galicia.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
![](https://joplinapp.org/images/flags/es/galicia.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | 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:
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 66%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 49%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | 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%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 67%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 89%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 97%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 99%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [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 -->
# Contributors

View File

@@ -54,5 +54,5 @@ module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
slowTestThreshold: 20,
slowTestThreshold: 40,
};

View File

@@ -147,27 +147,86 @@ describe('MdToHtml', function() {
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
}));
// it('should render links correctly', (async () => {
// const mdToHtml = newTestMdToHtml();
it('should render links correctly', (async () => {
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 = [
// // None of these should result in a link
// ['https://example.com', 'https://example.com'],
// ['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
// ['example.com', 'example.com'],
// ['oo.ps', 'oo.ps'],
// ['test@example.com', 'test@example.com'],
const mdToHtmlLinkifyOn = newTestMdToHtml({
pluginOptions: {
linkify: { enabled: true },
},
});
// // Those should be converted to links
// ['<https://example.com>', '<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>'],
// ];
const mdToHtmlLinkifyOff = newTestMdToHtml({
pluginOptions: {
linkify: { enabled: false },
},
});
// for (const testCase of testCases) {
// const [input, expected] = testCase;
// const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
// expect(actual.html).toBe(expected);
// }
// }));
for (const testCase of testCases) {
const [input, expectedLinkifyOff, expectedLinkifyOn] = testCase;
{
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);
}
}
}));
});

View File

@@ -171,7 +171,7 @@ describe('services_PluginService', function() {
const contentScriptPath = `${tempDir}/markdownItTestPlugin.js`;
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);
const service = newPluginService();

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -492,6 +492,16 @@
"dev": 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": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -1436,6 +1446,13 @@
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"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": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2192,6 +2209,11 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"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": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
@@ -2464,6 +2486,13 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"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": {
"version": "1.2.13",
"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",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"optional": true
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"glob-parent": {
"version": "3.1.0",

View File

@@ -19,5 +19,8 @@
"typescript": "^3.9.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"left-pad": "^1.3.0"
}
}

View File

@@ -6,5 +6,8 @@
"description": "",
"version": "1.0.0",
"author": "",
"homepage_url": ""
"homepage_url": "",
"content_scripts": [
"markdownItTestPlugin"
]
}

View File

@@ -1,3 +1,5 @@
const leftPad = require('left-pad');
function plugin(markdownIt, _options) {
const defaultRender = markdownIt.renderer.rules.fence || function(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);
return `
<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('testCommandNoArgs'); return false;">Click to send "testCommandNoArgs" to plugin</a></p>
</div>
@@ -16,15 +18,13 @@ function plugin(markdownIt, _options) {
};
}
module.exports = {
default: function(_context) {
return {
plugin: plugin,
assets: function() {
return [
{ name: 'markdownItTestPlugin.css' }
];
},
}
},
export default function(_context) {
return {
plugin: plugin,
assets: function() {
return [
{ name: 'markdownItTestPlugin.css' }
];
},
}
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View File

@@ -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' }
];
},
}
},
}

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -1,4 +1,5 @@
import joplin from 'api';
import { ToolbarButtonLocation } from 'api/types';
const uslug = require('uslug');
@@ -91,6 +92,18 @@ joplin.plugins.register({
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();
},
});

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View 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

View File

@@ -20,17 +20,21 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* 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 editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,6 +47,10 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
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.
*/

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
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>;
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
module.exports = {
const baseConfig = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,6 +60,10 @@ module.exports = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -70,6 +74,9 @@ module.exports = {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -79,8 +86,17 @@ module.exports = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.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;

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"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.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",

View File

@@ -22,6 +22,9 @@ import { LayoutItem } from './gui/ResizableLayout/utils/types';
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
import ResourceService from '@joplin/lib/services/ResourceService';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import produce from 'immer';
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
const MasterKey = require('@joplin/lib/models/MasterKey');
@@ -247,6 +250,29 @@ class Application extends BaseApplication {
};
break;
case 'MAIN_LAYOUT_SET_ITEM_PROP':
{
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
if (item.key === action.itemKey) {
(item as any)[action.propName] = action.propValue;
return false;
}
return true;
});
});
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
newState = {
...state,
mainLayout: newLayout,
};
}
break;
case 'NOTE_FILE_WATCHER_ADD':
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {

View File

@@ -387,6 +387,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
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 {
/* Add a fixed right padding to account for the appearance (and disappearance) */
/* of the sidebar */

View File

@@ -145,7 +145,7 @@ export default function useKeymap(CodeMirror: any) {
};
if (shim.isMac()) {
CodeMirror.keyMap.default = {
// MacOS
// macOS
'Shift-Cmd-Z': 'redo',
'Cmd-Y': 'redo',
'Cmd-End': 'goDocEnd',
@@ -153,10 +153,12 @@ export default function useKeymap(CodeMirror: any) {
'Cmd-Home': 'goDocStart',
'Cmd-Up': 'goDocStart',
'Ctrl-D': 'delCharAfter',
'Cmd-Left': 'goGroupLeft',
'Cmd-Right': 'goGroupRight',
'Alt-Left': 'goGroupLeft',
'Alt-Right': 'goGroupRight',
'Ctrl-A': 'goLineStart',
'Ctrl-E': 'goLineEnd',
'Cmd-Left': 'goLineLeftSmart',
'Cmd-Right': 'goLineRightSmart',
'Alt-Backspace': 'delGroupBefore',
'Alt-Delete': 'delGroupAfter',

View File

@@ -19,7 +19,7 @@ const taboverride = require('taboverride');
const { reg } = require('@joplin/lib/registry.js');
const BaseItem = require('@joplin/lib/models/BaseItem');
const { themeStyle } = require('@joplin/lib/theme');
// const { clipboard } = require('electron');
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
function markupRenderOptions(override: any = null) {
@@ -1015,19 +1015,22 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
}
function onKeyDown(_event: any) {
// It seems "paste as text" is now handled automatically by
// either Chrome, Electron and/or TinyMCE so the code below
// should not longer be necessary. Also fixes
function onKeyDown(event: any) {
// It seems "paste as text" is handled automatically by
// on Windows so the code below so we need to run the below
// 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
// 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
// it here and we don't need to do anything special in onPaste
// if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
// const pastedText = clipboard.readText();
// if (pastedText) editor.insertContent(pastedText);
// }
if (!shim.isWindows()) {
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
const pastedText = clipboard.readText();
if (pastedText) editor.insertContent(pastedText);
}
}
}
editor.on('keyup', onKeyUp);

View File

@@ -498,9 +498,10 @@ function NoteEditor(props: NoteEditorProps) {
}
function renderSearchInfo() {
const theme = themeStyle(props.themeId);
if (formNoteFolder && ['Search', 'Tag', 'SmartFilter'].includes(props.notesParentType)) {
return (
<div style={{ paddingTop: 10, paddingBottom: 10 }}>
<div style={{ paddingTop: 10, paddingBottom: 10, paddingLeft: theme.editorPaddingLeft }}>
<Button
iconName="icon-notebooks"
level={ButtonLevel.Primary}
@@ -525,6 +526,8 @@ function NoteEditor(props: NoteEditorProps) {
return renderNoNotes(styles.root);
}
const theme = themeStyle(props.themeId);
return (
<div style={styles.root} onDrop={onDrop}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
@@ -539,13 +542,13 @@ function NoteEditor(props: NoteEditorProps) {
onTitleChange={onTitleChange}
/>
{renderSearchInfo()}
<div style={{ display: 'flex', flex: 1 }}>
<div style={{ display: 'flex', flex: 1, paddingLeft: theme.editorPaddingLeft }}>
{editor}
</div>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
{renderSearchBar()}
</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()}
{renderTagBar()}
</div>

View File

@@ -11,6 +11,7 @@ const StyledRoot = styled.div`
display: flex;
flex-direction: row;
align-items: center;
padding-left: ${props => props.theme.editorPaddingLeft}px;
@media (max-width: 800px) {
flex-direction: column;
@@ -52,7 +53,6 @@ function styles_(props: Props) {
paddingBottom: 5,
paddingLeft: 0,
paddingRight: 8,
marginLeft: 5,
color: theme.textStyle.color,
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
backgroundColor: theme.backgroundColor,

View File

@@ -16,8 +16,4 @@ export default function findItemByKey(layout: LayoutItem, key: string): LayoutIt
}
return recurseFind(layout);
// const output = recurseFind(layout);
// if (!output) throw new Error(`Could not find item "${key}"`);
// return output;
}

View File

@@ -2,6 +2,8 @@ import { LayoutItem } from './types';
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) {
const result = callback(0, layout, null);
if (result === false) return;

View File

@@ -153,6 +153,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
order: [{
by: getSortingOrderColumn(sorting.order),
dir: sorting.type,
caseInsensitive: true,
}],
limit: MAX_RESOURCES,
fields: ['title', 'id', 'size', 'file_extension'],

View File

@@ -4,5 +4,5 @@
# It could be used to develop plugins too.
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"

View File

@@ -138,8 +138,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097617
versionName "1.6.3"
versionCode 2097618
versionName "1.6.4"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -19,16 +19,19 @@
<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.
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
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:allowBackup="true"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme">

View File

@@ -1,6 +1,9 @@
const React = require('react');
import shim from '@joplin/lib/shim';
shim.setReact(React);
import setUpQuickActions from './setUpQuickActions';
import PluginAssetsLoader from './PluginAssetsLoader';
import AlarmService from '@joplin/lib/services/AlarmService';
import Alarm from '@joplin/lib/models/Alarm';
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 SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
const React = require('react');
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 AlarmServiceDriver = require('./services/AlarmServiceDriver').default;

View File

@@ -20,6 +20,63 @@ 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

View File

@@ -15,7 +15,7 @@ function mergePackageKey(parentKey, source, dest) {
// If we are dealing with the dependencies, overwrite with the
// version from source.
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
output[k] = mergePackageKey(k, source[k], output[k]);
} else {
@@ -116,6 +116,7 @@ module.exports = class extends Generator {
'.gitignore_TEMPLATE',
'package_TEMPLATE.json',
'README.md',
'GENERATOR_DOC.md',
'tsconfig.json',
'webpack.config.js',
];

View 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

Some files were not shown because too many files have changed in this diff Show More