You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-03-12 10:00:05 +02:00
Compare commits
37 Commits
android-v1
...
content_sc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ff44f53da | ||
|
|
3c8f774e85 | ||
|
|
d49439689e | ||
|
|
ef3a102741 | ||
|
|
0e57baf5b9 | ||
|
|
05f65c326a | ||
|
|
491714cde6 | ||
|
|
34c1096307 | ||
|
|
ebe3ddcd21 | ||
|
|
ca5ac9696b | ||
|
|
fdfbb84d37 | ||
|
|
740aba90ea | ||
|
|
eab9ff175c | ||
|
|
5b295d5f6f | ||
|
|
0be8cdf760 | ||
|
|
545940f545 | ||
|
|
d58f39823a | ||
|
|
f9fb1b8a81 | ||
|
|
56ded0062a | ||
|
|
83b29d7c51 | ||
|
|
1ec0746263 | ||
|
|
568d11bddf | ||
|
|
97b25ac99d | ||
|
|
7f1d3d8a5d | ||
|
|
fde201fbe9 | ||
|
|
694a1b4ede | ||
|
|
20d126b39d | ||
|
|
8ca9c3092a | ||
|
|
d2771029a3 | ||
|
|
4128c53fcf | ||
|
|
a14410b28c | ||
|
|
d1f8520e6e | ||
|
|
d76746b8e4 | ||
|
|
89d173b460 | ||
|
|
81aba8b8b0 | ||
|
|
f48697572d | ||
|
|
e61e8b7b94 |
@@ -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
|
||||
@@ -803,6 +806,9 @@ packages/app-mobile/components/screens/Note.js.map
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
||||
packages/app-mobile/root.d.ts
|
||||
packages/app-mobile/root.js
|
||||
packages/app-mobile/root.js.map
|
||||
packages/app-mobile/services/AlarmServiceDriver.android.d.ts
|
||||
packages/app-mobile/services/AlarmServiceDriver.android.js
|
||||
packages/app-mobile/services/AlarmServiceDriver.android.js.map
|
||||
@@ -1385,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
|
||||
@@ -1457,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
|
||||
@@ -1466,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
|
||||
@@ -1487,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
|
||||
@@ -1532,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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
|
||||
36
.gitignore
vendored
36
.gitignore
vendored
@@ -144,6 +144,9 @@ packages/app-cli/tests/support/plugins/content_script/api/types.js.map
|
||||
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/content_script/src/index.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
|
||||
@@ -792,6 +795,9 @@ packages/app-mobile/components/screens/Note.js.map
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
||||
packages/app-mobile/root.d.ts
|
||||
packages/app-mobile/root.js
|
||||
packages/app-mobile/root.js.map
|
||||
packages/app-mobile/services/AlarmServiceDriver.android.d.ts
|
||||
packages/app-mobile/services/AlarmServiceDriver.android.js
|
||||
packages/app-mobile/services/AlarmServiceDriver.android.js.map
|
||||
@@ -1374,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
|
||||
@@ -1446,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
|
||||
@@ -1455,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
|
||||
@@ -1476,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
|
||||
@@ -1521,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
|
||||
|
||||
20
README.md
20
README.md
@@ -109,6 +109,8 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
|
||||
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
|
||||
- [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:
|
||||
 | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 26%
|
||||
 | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 93%
|
||||
 | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 77%
|
||||
 | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 96%
|
||||
 | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 95%
|
||||
 | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 61%
|
||||
 | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||
 | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||
 | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 93%
|
||||
 | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 99%
|
||||
 | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 35%
|
||||
 | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | | 94%
|
||||
 | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 93%
|
||||
 | 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%
|
||||
 | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
|
||||
 | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 40%
|
||||
 | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 85%
|
||||
 | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 96%
|
||||
 | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 32%
|
||||
@@ -467,14 +469,14 @@ Current translations:
|
||||
 | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 66%
|
||||
 | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 49%
|
||||
 | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
|
||||
 | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 92%
|
||||
 | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 99%
|
||||
 | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
|
||||
 | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 96%
|
||||
 | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 67%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 97%
|
||||
 | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 89%
|
||||
 | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
|
||||
 | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 97%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 99%
|
||||
 | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
|
||||
 | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
|
||||
 | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Contributors
|
||||
|
||||
@@ -54,5 +54,5 @@ module.exports = {
|
||||
|
||||
testEnvironment: 'node',
|
||||
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
|
||||
slowTestThreshold: 20,
|
||||
slowTestThreshold: 40,
|
||||
};
|
||||
|
||||
@@ -147,27 +147,86 @@ describe('MdToHtml', function() {
|
||||
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
|
||||
}));
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -19,5 +19,8 @@
|
||||
"typescript": "^3.9.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"left-pad": "^1.3.0"
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,8 @@
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"author": "",
|
||||
"homepage_url": ""
|
||||
"homepage_url": "",
|
||||
"content_scripts": [
|
||||
"markdownItTestPlugin"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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' }
|
||||
];
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
function plugin(markdownIt, _options) {
|
||||
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
|
||||
return self.renderToken(tokens, idx, options, env, self);
|
||||
};
|
||||
|
||||
markdownIt.renderer.rules.fence = function(tokens, idx, options, env, self) {
|
||||
const token = tokens[idx];
|
||||
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
|
||||
return `
|
||||
<div class="just-testing">
|
||||
<p>JUST TESTING: ${token.content}</p>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
default: function(_context) {
|
||||
return {
|
||||
plugin: plugin,
|
||||
assets: function() {
|
||||
return [
|
||||
{ name: 'markdownItTestPlugin.css' }
|
||||
];
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
83
packages/app-cli/tests/support/plugins/menu/GENERATOR_DOC.md
Normal file
83
packages/app-cli/tests/support/plugins/menu/GENERATOR_DOC.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
83
packages/app-cli/tests/support/plugins/toc/GENERATOR_DOC.md
Normal file
83
packages/app-cli/tests/support/plugins/toc/GENERATOR_DOC.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
|
||||
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
|
||||
|
||||
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## Content scripts
|
||||
|
||||
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
|
||||
|
||||
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
|
||||
```bash
|
||||
/src
|
||||
index.ts # Main plugin script
|
||||
myContentScript.js # One content script (JS)
|
||||
otherContentScript.ts # Another content script (TypeScript)
|
||||
vendor/
|
||||
test.ts # Sub-directories are also supported
|
||||
```
|
||||
|
||||
The `manifest.json` file would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Testing Content Scripts",
|
||||
content_scripts: [
|
||||
"myContentScript",
|
||||
"otherContentScript",
|
||||
"vendor/test"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note in particular how the file path is relative to /src and the extensions removed.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
||||
@@ -20,17 +20,21 @@ export default class JoplinPlugins {
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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"
|
||||
@@ -138,8 +138,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097616
|
||||
versionName "1.6.2"
|
||||
versionCode 2097618
|
||||
versionName "1.6.4"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// So there's basically still a one way flux: React => SQLite => Redux => React
|
||||
|
||||
import { LogBox, AppRegistry } from 'react-native';
|
||||
const { Root } = require('./root.js');
|
||||
const Root = require('./root').default;
|
||||
|
||||
// Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed
|
||||
// or don't really matter. Because we want important warnings to actually be fixed, we disable
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
import setUpQuickActions from './setUpQuickActions';
|
||||
import PluginAssetsLoader from './PluginAssetsLoader';
|
||||
|
||||
const React = require('react');
|
||||
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
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';
|
||||
import Logger, { TargetType } from '@joplin/lib/Logger';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import BaseService from '@joplin/lib/services/BaseService';
|
||||
import ResourceService from '@joplin/lib/services/ResourceService';
|
||||
import KvStore from '@joplin/lib/services/KvStore';
|
||||
import NoteScreen from './components/screens/Note';
|
||||
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
|
||||
import reducer from '@joplin/lib/reducer';
|
||||
import ShareExtension from './utils/ShareExtension';
|
||||
import handleShared from './utils/shareHandler';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtils';
|
||||
import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile';
|
||||
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
|
||||
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
|
||||
|
||||
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
|
||||
|
||||
const DropdownAlert = require('react-native-dropdownalert').default;
|
||||
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
|
||||
const SafeAreaView = require('./components/SafeAreaView');
|
||||
const { connect, Provider } = require('react-redux');
|
||||
const { BackButtonService } = require('./services/back-button.js');
|
||||
const NavService = require('@joplin/lib/services/NavService.js');
|
||||
const AlarmService = require('@joplin/lib/services/AlarmService.js').default;
|
||||
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
|
||||
const Alarm = require('@joplin/lib/models/Alarm').default;
|
||||
const { createStore, applyMiddleware } = require('redux');
|
||||
const reduxSharedMiddleware = require('@joplin/lib/components/shared/reduxSharedMiddleware');
|
||||
const { shimInit } = require('./utils/shim-init-react.js');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { AppNav } = require('./components/app-nav.js');
|
||||
const Logger = require('@joplin/lib/Logger').default;
|
||||
const Note = require('@joplin/lib/models/Note.js');
|
||||
const Folder = require('@joplin/lib/models/Folder.js');
|
||||
const BaseSyncTarget = require('@joplin/lib/BaseSyncTarget.js');
|
||||
@@ -29,16 +48,11 @@ const NoteTag = require('@joplin/lib/models/NoteTag.js');
|
||||
const BaseItem = require('@joplin/lib/models/BaseItem.js');
|
||||
const MasterKey = require('@joplin/lib/models/MasterKey.js');
|
||||
const Revision = require('@joplin/lib/models/Revision.js');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const BaseService = require('@joplin/lib/services/BaseService').default;
|
||||
const ResourceService = require('@joplin/lib/services/ResourceService').default;
|
||||
const RevisionService = require('@joplin/lib/services/RevisionService');
|
||||
const KvStore = require('@joplin/lib/services/KvStore').default;
|
||||
const { JoplinDatabase } = require('@joplin/lib/joplin-database.js');
|
||||
const { Database } = require('@joplin/lib/database.js');
|
||||
const { NotesScreen } = require('./components/screens/notes.js');
|
||||
const { TagsScreen } = require('./components/screens/tags.js');
|
||||
const NoteScreen = require('./components/screens/Note').default;
|
||||
const { ConfigScreen } = require('./components/screens/config.js');
|
||||
const { FolderScreen } = require('./components/screens/folder.js');
|
||||
const { LogScreen } = require('./components/screens/log.js');
|
||||
@@ -47,31 +61,18 @@ const { SearchScreen } = require('./components/screens/search.js');
|
||||
const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js');
|
||||
const { EncryptionConfigScreen } = require('./components/screens/encryption-config.js');
|
||||
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
|
||||
const UpgradeSyncTargetScreen = require('./components/screens/UpgradeSyncTargetScreen').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { MenuContext } = require('react-native-popup-menu');
|
||||
const { SideMenu } = require('./components/side-menu.js');
|
||||
const { SideMenuContent } = require('./components/side-menu-content.js');
|
||||
const { SideMenuContentNote } = require('./components/side-menu-content-note.js');
|
||||
const { DatabaseDriverReactNative } = require('./utils/database-driver-react-native');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const { setLocale, closestSupportedLocale, defaultLocale } = require('@joplin/lib/locale');
|
||||
const RNFetchBlob = require('rn-fetch-blob').default;
|
||||
const PoorManIntervals = require('@joplin/lib/PoorManIntervals').default;
|
||||
const reducer = require('@joplin/lib/reducer').default;
|
||||
const { defaultState } = require('@joplin/lib/reducer');
|
||||
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local.js');
|
||||
const DropdownAlert = require('react-native-dropdownalert').default;
|
||||
const ShareExtension = require('./utils/ShareExtension.js').default;
|
||||
const handleShared = require('./utils/shareHandler').default;
|
||||
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher');
|
||||
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine');
|
||||
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
||||
const { themeStyle } = require('./components/global-style.js');
|
||||
const uuid = require('@joplin/lib/uuid').default;
|
||||
|
||||
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
|
||||
const KeychainServiceDriverMobile = require('@joplin/lib/services/keychain/KeychainServiceDriver.mobile').default;
|
||||
|
||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js');
|
||||
const SyncTargetOneDrive = require('@joplin/lib/SyncTargetOneDrive.js');
|
||||
@@ -87,15 +88,16 @@ SyncTargetRegistry.addClass(SyncTargetWebDAV);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
|
||||
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
|
||||
|
||||
const FsDriverRN = require('./utils/fs-driver-rn.js').FsDriverRN;
|
||||
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker');
|
||||
const EncryptionService = require('@joplin/lib/services/EncryptionService');
|
||||
const MigrationService = require('@joplin/lib/services/MigrationService');
|
||||
|
||||
let storeDispatch = function() {};
|
||||
let storeDispatch = function(_action: any) {};
|
||||
|
||||
const logReducerAction = function(action) {
|
||||
const logReducerAction = function(action: any) {
|
||||
if (['SIDE_MENU_OPEN_PERCENT', 'SYNC_REPORT_UPDATE'].indexOf(action.type) >= 0) return;
|
||||
|
||||
const msg = [action.type];
|
||||
@@ -104,7 +106,7 @@ const logReducerAction = function(action) {
|
||||
// reg.logger().debug('Reducer action', msg.join(', '));
|
||||
};
|
||||
|
||||
const generalMiddleware = store => next => async (action) => {
|
||||
const generalMiddleware = (store: any) => (next: any) => async (action: any) => {
|
||||
logReducerAction(action);
|
||||
PoorManIntervals.update(); // This function needs to be called regularly so put it here
|
||||
|
||||
@@ -167,9 +169,9 @@ const generalMiddleware = store => next => async (action) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const navHistory = [];
|
||||
const navHistory: any[] = [];
|
||||
|
||||
function historyCanGoBackTo(route) {
|
||||
function historyCanGoBackTo(route: any) {
|
||||
if (route.routeName === 'Note') return false;
|
||||
if (route.routeName === 'Folder') return false;
|
||||
|
||||
@@ -194,13 +196,14 @@ const appDefaultState = Object.assign({}, defaultState, {
|
||||
noteSideMenuOptions: null,
|
||||
});
|
||||
|
||||
const appReducer = (state = appDefaultState, action) => {
|
||||
const appReducer = (state = appDefaultState, action: any) => {
|
||||
let newState = state;
|
||||
let historyGoingBack = false;
|
||||
|
||||
try {
|
||||
switch (action.type) {
|
||||
|
||||
// @ts-ignore
|
||||
case 'NAV_BACK':
|
||||
|
||||
{
|
||||
@@ -224,7 +227,7 @@ const appReducer = (state = appDefaultState, action) => {
|
||||
{
|
||||
const currentRoute = state.route;
|
||||
|
||||
if (!historyGoingBack && historyCanGoBackTo(currentRoute, action)) {
|
||||
if (!historyGoingBack && historyCanGoBackTo(currentRoute)) {
|
||||
// If the route *name* is the same (even if the other parameters are different), we
|
||||
// overwrite the last route in the history with the current one. If the route name
|
||||
// is different, we push a new history entry.
|
||||
@@ -368,7 +371,7 @@ const appReducer = (state = appDefaultState, action) => {
|
||||
const store = createStore(appReducer, applyMiddleware(generalMiddleware));
|
||||
storeDispatch = store.dispatch;
|
||||
|
||||
function resourceFetcher_downloadComplete(event) {
|
||||
function resourceFetcher_downloadComplete(event: any) {
|
||||
if (event.encrypted) {
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
}
|
||||
@@ -378,9 +381,10 @@ function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
|
||||
ResourceFetcher.instance().scheduleAutoAddResources();
|
||||
}
|
||||
|
||||
async function initialize(dispatch) {
|
||||
async function initialize(dispatch: Function) {
|
||||
shimInit();
|
||||
|
||||
// @ts-ignore
|
||||
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
|
||||
Setting.setConstant('appType', 'mobile');
|
||||
@@ -391,18 +395,18 @@ async function initialize(dispatch) {
|
||||
await logDatabase.exec(Logger.databaseCreateTableSql());
|
||||
|
||||
const mainLogger = new Logger();
|
||||
mainLogger.addTarget('database', { database: logDatabase, source: 'm' });
|
||||
mainLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
|
||||
mainLogger.setLevel(Logger.LEVEL_INFO);
|
||||
|
||||
if (Setting.value('env') == 'dev') {
|
||||
mainLogger.addTarget('console');
|
||||
mainLogger.addTarget(TargetType.Console);
|
||||
mainLogger.setLevel(Logger.LEVEL_DEBUG);
|
||||
}
|
||||
|
||||
Logger.initializeGlobalLogger(mainLogger);
|
||||
|
||||
reg.setLogger(mainLogger);
|
||||
reg.setShowErrorMessageBoxHandler((message) => { alert(message); });
|
||||
reg.setShowErrorMessageBoxHandler((message: string) => { alert(message); });
|
||||
|
||||
BaseService.logger_ = mainLogger;
|
||||
// require('@joplin/lib/ntpDate').setLogger(reg.logger());
|
||||
@@ -411,9 +415,9 @@ async function initialize(dispatch) {
|
||||
reg.logger().info(`Starting application ${Setting.value('appId')} (${Setting.value('env')})`);
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget('database', { database: logDatabase, source: 'm' });
|
||||
dbLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
|
||||
if (Setting.value('env') == 'dev') {
|
||||
dbLogger.addTarget('console');
|
||||
dbLogger.addTarget(TargetType.Console);
|
||||
dbLogger.setLevel(Logger.LEVEL_INFO); // Set to LEVEL_DEBUG for full SQL queries
|
||||
} else {
|
||||
dbLogger.setLevel(Logger.LEVEL_INFO);
|
||||
@@ -452,7 +456,7 @@ async function initialize(dispatch) {
|
||||
if (Setting.value('env') == 'prod') {
|
||||
await db.open({ name: 'joplin.sqlite' });
|
||||
} else {
|
||||
await db.open({ name: 'joplin-76.sqlite' });
|
||||
await db.open({ name: 'joplin-100.sqlite' });
|
||||
|
||||
// await db.clearForTesting();
|
||||
}
|
||||
@@ -562,7 +566,7 @@ async function initialize(dispatch) {
|
||||
reg.setupRecurrentSync();
|
||||
|
||||
PoorManIntervals.setTimeout(() => {
|
||||
AlarmService.garbageCollect();
|
||||
void AlarmService.garbageCollect();
|
||||
}, 1000 * 60 * 60);
|
||||
|
||||
ResourceService.runInBackground();
|
||||
@@ -584,7 +588,7 @@ async function initialize(dispatch) {
|
||||
reg.scheduleSync(1000).then(() => {
|
||||
// Wait for the first sync before updating the notifications, since synchronisation
|
||||
// might change the notifications.
|
||||
AlarmService.updateAllNotifications();
|
||||
void AlarmService.updateAllNotifications();
|
||||
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
});
|
||||
@@ -654,7 +658,7 @@ class AppComponent extends React.Component {
|
||||
|
||||
BackButtonService.initialize(this.backButtonHandler_);
|
||||
|
||||
AlarmService.setInAppNotificationHandler(async (alarmId) => {
|
||||
AlarmService.setInAppNotificationHandler(async (alarmId: string) => {
|
||||
const alarm = await Alarm.load(alarmId);
|
||||
const notification = await Alarm.makeNotification(alarm);
|
||||
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
|
||||
@@ -666,7 +670,7 @@ class AppComponent extends React.Component {
|
||||
if (sharedData) {
|
||||
reg.logger().info('Received shared data');
|
||||
if (this.props.selectedFolderId) {
|
||||
handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
|
||||
await handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
|
||||
} else {
|
||||
reg.logger.info('Cannot handle share - default folder id is not set');
|
||||
}
|
||||
@@ -677,7 +681,7 @@ class AppComponent extends React.Component {
|
||||
AppState.removeEventListener('change', this.onAppStateChange_);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: any) {
|
||||
if (this.props.showSideMenu !== prevProps.showSideMenu) {
|
||||
Animated.timing(this.state.sideMenuContentOpacity, {
|
||||
toValue: this.props.showSideMenu ? 0.5 : 0,
|
||||
@@ -707,14 +711,14 @@ class AppComponent extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
UNSAFE_componentWillReceiveProps(newProps: any) {
|
||||
if (newProps.syncStarted != this.lastSyncStarted_) {
|
||||
if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders();
|
||||
this.lastSyncStarted_ = newProps.syncStarted;
|
||||
}
|
||||
}
|
||||
|
||||
sideMenu_change(isOpen) {
|
||||
sideMenu_change(isOpen: boolean) {
|
||||
// Make sure showSideMenu property of state is updated
|
||||
// when the menu is open/closed.
|
||||
this.props.dispatch({
|
||||
@@ -759,8 +763,8 @@ class AppComponent extends React.Component {
|
||||
menu={sideMenuContent}
|
||||
edgeHitWidth={5}
|
||||
menuPosition={menuPosition}
|
||||
onChange={(isOpen) => this.sideMenu_change(isOpen)}
|
||||
onSliding={(percent) => {
|
||||
onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)}
|
||||
onSliding={(percent: number) => {
|
||||
this.props.dispatch({
|
||||
type: 'SIDE_MENU_OPEN_PERCENT',
|
||||
value: percent,
|
||||
@@ -773,7 +777,7 @@ class AppComponent extends React.Component {
|
||||
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||
<AppNav screens={appNavInit} />
|
||||
</View>
|
||||
<DropdownAlert ref={ref => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
|
||||
<DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
|
||||
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
|
||||
</SafeAreaView>
|
||||
</MenuContext>
|
||||
@@ -783,7 +787,7 @@ class AppComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
historyCanGoBack: state.historyCanGoBack,
|
||||
showSideMenu: state.showSideMenu,
|
||||
@@ -799,7 +803,7 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const App = connect(mapStateToProps)(AppComponent);
|
||||
|
||||
class Root extends React.Component {
|
||||
export default class Root extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
@@ -808,5 +812,3 @@ class Root extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Root };
|
||||
@@ -24,9 +24,9 @@ export default async (sharedData: SharedData, folderId: string, dispatch: Functi
|
||||
// below will do nothing (because routeName wouldn't change)
|
||||
// Then we wait a bit for the state to be set correctly, and
|
||||
// finally we go to the new note.
|
||||
await dispatch({ type: 'NAV_BACK' });
|
||||
dispatch({ type: 'NAV_BACK' });
|
||||
|
||||
await dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
const newNote = await Note.save({
|
||||
parent_id: folderId,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user