mirror of
https://github.com/laurent22/joplin.git
synced 2026-11-01 14:37:13 +00:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a77d500bfa | ||
|
|
8fea9ba193 | ||
|
|
3fdcd3aaa6 | ||
|
|
0629cb394c | ||
|
|
f58b7563e6 | ||
|
|
d434723244 | ||
|
|
fca8f71f4a | ||
|
|
023170548f | ||
|
|
d75adc3740 | ||
|
|
0bf3531f51 | ||
|
|
c9f40ea23f | ||
|
|
c8755839be | ||
|
|
c3e696db5a | ||
|
|
a0134005b7 | ||
|
|
b66d90bbcc | ||
|
|
41edf5b2da | ||
|
|
c484c88715 | ||
|
|
4a0fb124a7 | ||
|
|
ebbaa5177b | ||
|
|
5d1dc224ca | ||
|
|
3626714403 | ||
|
|
3884ada760 | ||
|
|
16788d1437 | ||
|
|
b1908756d4 | ||
|
|
ccf5271584 | ||
|
|
df3e6a6219 | ||
|
|
f43ec71d9a | ||
|
|
32dab88054 | ||
|
|
1b5868a7c5 | ||
|
|
72712e71eb | ||
|
|
ec29f791ba | ||
|
|
40c2e59394 | ||
|
|
98cbb237b5 | ||
|
|
826dda5a75 | ||
|
|
6dc5a816e5 | ||
|
|
1700b29f7d | ||
|
|
7a6966405c | ||
|
|
9cb576889f | ||
|
|
047846a552 | ||
|
|
99fb578db4 | ||
|
|
7114228fd6 | ||
|
|
8d4d438136 | ||
|
|
420ac8359c | ||
|
|
fe7691d786 | ||
|
|
db4f35b936 | ||
|
|
72aabf71f3 | ||
|
|
d145ce1876 | ||
|
|
ab6f02a949 | ||
|
|
1339414443 | ||
|
|
732ca53b70 | ||
|
|
fa749d6d2a | ||
|
|
6e69343bb6 | ||
|
|
520454affb | ||
|
|
734514b6d8 | ||
|
|
ceb252b9ad | ||
|
|
f121245e40 | ||
|
|
215a1e0240 | ||
|
|
9e076bf194 | ||
|
|
74a797b410 |
No files matched your search
+39
-6
@@ -110,6 +110,9 @@ packages/app-cli/tests/models_Note.js.map
|
||||
packages/app-cli/tests/models_Setting.d.ts
|
||||
packages/app-cli/tests/models_Setting.js
|
||||
packages/app-cli/tests/models_Setting.js.map
|
||||
packages/app-cli/tests/services/plugins/api/JoplinSettings.d.ts
|
||||
packages/app-cli/tests/services/plugins/api/JoplinSettings.js
|
||||
packages/app-cli/tests/services/plugins/api/JoplinSettings.js.map
|
||||
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.d.ts
|
||||
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js
|
||||
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js.map
|
||||
@@ -326,9 +329,18 @@ packages/app-desktop/gui/ConfigScreen/ConfigScreen.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.js.map
|
||||
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
||||
@@ -656,9 +668,6 @@ packages/app-desktop/gui/Root_UpgradeSyncTarget.js.map
|
||||
packages/app-desktop/gui/SearchBar/SearchBar.d.ts
|
||||
packages/app-desktop/gui/SearchBar/SearchBar.js
|
||||
packages/app-desktop/gui/SearchBar/SearchBar.js.map
|
||||
packages/app-desktop/gui/SearchBar/styles/index.d.ts
|
||||
packages/app-desktop/gui/SearchBar/styles/index.js
|
||||
packages/app-desktop/gui/SearchBar/styles/index.js.map
|
||||
packages/app-desktop/gui/ShareNoteDialog.d.ts
|
||||
packages/app-desktop/gui/ShareNoteDialog.js
|
||||
packages/app-desktop/gui/ShareNoteDialog.js.map
|
||||
@@ -707,6 +716,9 @@ packages/app-desktop/gui/hooks/usePrevious.js.map
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.d.ts
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js.map
|
||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.d.ts
|
||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.js
|
||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.js.map
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.d.ts
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js.map
|
||||
@@ -1121,6 +1133,9 @@ packages/lib/services/plugins/Plugin.js.map
|
||||
packages/lib/services/plugins/PluginService.d.ts
|
||||
packages/lib/services/plugins/PluginService.js
|
||||
packages/lib/services/plugins/PluginService.js.map
|
||||
packages/lib/services/plugins/RepositoryApi.d.ts
|
||||
packages/lib/services/plugins/RepositoryApi.js
|
||||
packages/lib/services/plugins/RepositoryApi.js.map
|
||||
packages/lib/services/plugins/ToolbarButtonController.d.ts
|
||||
packages/lib/services/plugins/ToolbarButtonController.js
|
||||
packages/lib/services/plugins/ToolbarButtonController.js.map
|
||||
@@ -1205,6 +1220,12 @@ packages/lib/services/plugins/utils/mapEventHandlersToIds.js.map
|
||||
packages/lib/services/plugins/utils/types.d.ts
|
||||
packages/lib/services/plugins/utils/types.js
|
||||
packages/lib/services/plugins/utils/types.js.map
|
||||
packages/lib/services/plugins/utils/validatePluginId.d.ts
|
||||
packages/lib/services/plugins/utils/validatePluginId.js
|
||||
packages/lib/services/plugins/utils/validatePluginId.js.map
|
||||
packages/lib/services/plugins/utils/validatePluginId.test.d.ts
|
||||
packages/lib/services/plugins/utils/validatePluginId.test.js
|
||||
packages/lib/services/plugins/utils/validatePluginId.test.js.map
|
||||
packages/lib/services/rest/Api.d.ts
|
||||
packages/lib/services/rest/Api.js
|
||||
packages/lib/services/rest/Api.js.map
|
||||
@@ -1334,6 +1355,15 @@ packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.js.map
|
||||
packages/plugins/ToggleSideBars/api/index.d.ts
|
||||
packages/plugins/ToggleSideBars/api/index.js
|
||||
packages/plugins/ToggleSideBars/api/index.js.map
|
||||
packages/plugins/ToggleSideBars/api/types.d.ts
|
||||
packages/plugins/ToggleSideBars/api/types.js
|
||||
packages/plugins/ToggleSideBars/api/types.js.map
|
||||
packages/plugins/ToggleSideBars/src/index.d.ts
|
||||
packages/plugins/ToggleSideBars/src/index.js
|
||||
packages/plugins/ToggleSideBars/src/index.js.map
|
||||
packages/renderer/HtmlToHtml.d.ts
|
||||
packages/renderer/HtmlToHtml.js
|
||||
packages/renderer/HtmlToHtml.js.map
|
||||
@@ -1646,6 +1676,9 @@ packages/server/src/utils/urlUtils.js.map
|
||||
packages/server/src/utils/uuidgen.d.ts
|
||||
packages/server/src/utils/uuidgen.js
|
||||
packages/server/src/utils/uuidgen.js.map
|
||||
packages/tools/build-plugin-repository.d.ts
|
||||
packages/tools/build-plugin-repository.js
|
||||
packages/tools/build-plugin-repository.js.map
|
||||
packages/tools/release-server.d.ts
|
||||
packages/tools/release-server.js
|
||||
packages/tools/release-server.js.map
|
||||
|
||||
+39
-6
@@ -99,6 +99,9 @@ packages/app-cli/tests/models_Note.js.map
|
||||
packages/app-cli/tests/models_Setting.d.ts
|
||||
packages/app-cli/tests/models_Setting.js
|
||||
packages/app-cli/tests/models_Setting.js.map
|
||||
packages/app-cli/tests/services/plugins/api/JoplinSettings.d.ts
|
||||
packages/app-cli/tests/services/plugins/api/JoplinSettings.js
|
||||
packages/app-cli/tests/services/plugins/api/JoplinSettings.js.map
|
||||
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.d.ts
|
||||
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js
|
||||
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js.map
|
||||
@@ -315,9 +318,18 @@ packages/app-desktop/gui/ConfigScreen/ConfigScreen.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.js.map
|
||||
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
||||
@@ -645,9 +657,6 @@ packages/app-desktop/gui/Root_UpgradeSyncTarget.js.map
|
||||
packages/app-desktop/gui/SearchBar/SearchBar.d.ts
|
||||
packages/app-desktop/gui/SearchBar/SearchBar.js
|
||||
packages/app-desktop/gui/SearchBar/SearchBar.js.map
|
||||
packages/app-desktop/gui/SearchBar/styles/index.d.ts
|
||||
packages/app-desktop/gui/SearchBar/styles/index.js
|
||||
packages/app-desktop/gui/SearchBar/styles/index.js.map
|
||||
packages/app-desktop/gui/ShareNoteDialog.d.ts
|
||||
packages/app-desktop/gui/ShareNoteDialog.js
|
||||
packages/app-desktop/gui/ShareNoteDialog.js.map
|
||||
@@ -696,6 +705,9 @@ packages/app-desktop/gui/hooks/usePrevious.js.map
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.d.ts
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js.map
|
||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.d.ts
|
||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.js
|
||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.js.map
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.d.ts
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js.map
|
||||
@@ -1110,6 +1122,9 @@ packages/lib/services/plugins/Plugin.js.map
|
||||
packages/lib/services/plugins/PluginService.d.ts
|
||||
packages/lib/services/plugins/PluginService.js
|
||||
packages/lib/services/plugins/PluginService.js.map
|
||||
packages/lib/services/plugins/RepositoryApi.d.ts
|
||||
packages/lib/services/plugins/RepositoryApi.js
|
||||
packages/lib/services/plugins/RepositoryApi.js.map
|
||||
packages/lib/services/plugins/ToolbarButtonController.d.ts
|
||||
packages/lib/services/plugins/ToolbarButtonController.js
|
||||
packages/lib/services/plugins/ToolbarButtonController.js.map
|
||||
@@ -1194,6 +1209,12 @@ packages/lib/services/plugins/utils/mapEventHandlersToIds.js.map
|
||||
packages/lib/services/plugins/utils/types.d.ts
|
||||
packages/lib/services/plugins/utils/types.js
|
||||
packages/lib/services/plugins/utils/types.js.map
|
||||
packages/lib/services/plugins/utils/validatePluginId.d.ts
|
||||
packages/lib/services/plugins/utils/validatePluginId.js
|
||||
packages/lib/services/plugins/utils/validatePluginId.js.map
|
||||
packages/lib/services/plugins/utils/validatePluginId.test.d.ts
|
||||
packages/lib/services/plugins/utils/validatePluginId.test.js
|
||||
packages/lib/services/plugins/utils/validatePluginId.test.js.map
|
||||
packages/lib/services/rest/Api.d.ts
|
||||
packages/lib/services/rest/Api.js
|
||||
packages/lib/services/rest/Api.js.map
|
||||
@@ -1323,6 +1344,15 @@ packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.js.map
|
||||
packages/plugins/ToggleSideBars/api/index.d.ts
|
||||
packages/plugins/ToggleSideBars/api/index.js
|
||||
packages/plugins/ToggleSideBars/api/index.js.map
|
||||
packages/plugins/ToggleSideBars/api/types.d.ts
|
||||
packages/plugins/ToggleSideBars/api/types.js
|
||||
packages/plugins/ToggleSideBars/api/types.js.map
|
||||
packages/plugins/ToggleSideBars/src/index.d.ts
|
||||
packages/plugins/ToggleSideBars/src/index.js
|
||||
packages/plugins/ToggleSideBars/src/index.js.map
|
||||
packages/renderer/HtmlToHtml.d.ts
|
||||
packages/renderer/HtmlToHtml.js
|
||||
packages/renderer/HtmlToHtml.js.map
|
||||
@@ -1635,6 +1665,9 @@ packages/server/src/utils/urlUtils.js.map
|
||||
packages/server/src/utils/uuidgen.d.ts
|
||||
packages/server/src/utils/uuidgen.js
|
||||
packages/server/src/utils/uuidgen.js.map
|
||||
packages/tools/build-plugin-repository.d.ts
|
||||
packages/tools/build-plugin-repository.js
|
||||
packages/tools/build-plugin-repository.js.map
|
||||
packages/tools/release-server.d.ts
|
||||
packages/tools/release-server.js
|
||||
packages/tools/release-server.js.map
|
||||
|
||||
@@ -235,6 +235,7 @@ WebDAV-compatible services that are known to work with Joplin:
|
||||
- [OwnCloud](https://owncloud.org/)
|
||||
- [Seafile](https://www.seafile.com/)
|
||||
- [Stack](https://www.transip.nl/stack/)
|
||||
- [Synology WebDAV Server](https://www.synology.com/en-us/dsm/packages/WebDAVServer)
|
||||
- [WebDAV Nav](https://www.schimera.com/products/webdav-nav-server/), a macOS server.
|
||||
- [Zimbra](https://www.zimbra.com/)
|
||||
|
||||
|
||||
+11
-1
@@ -1,5 +1,15 @@
|
||||
module.exports = {
|
||||
'**/*.ts?(x)': () => 'npm run tsc',
|
||||
// Don't compile when committing as it will process all TS files in the
|
||||
// monorepo, which is too slow. Errors should be checked during development
|
||||
// using `npm run watch`.
|
||||
//
|
||||
// Or if we add this back, we could do something like this:
|
||||
// https://stackoverflow.com/a/44748041/561309
|
||||
//
|
||||
// The script would check where the TS file is located, then use the right
|
||||
// tsconfig.json file along with the tsconfig override.
|
||||
//
|
||||
// '**/*.ts?(x)': () => 'npm run tsc',
|
||||
'*.{js,jsx,ts,tsx}': [
|
||||
'npm run linter-precommit',
|
||||
'git add',
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@
|
||||
"linter": "./node_modules/.bin/eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
|
||||
"postinstall": "npm run bootstrap --no-ci && npm run build",
|
||||
"publishAll": "git pull && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
|
||||
"releaseAndroid": "node packages/tools/release-android.js",
|
||||
"releaseAndroidClean": "node packages/tools/release-android.js",
|
||||
"releaseAndroid": "export PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" && node packages/tools/release-android.js",
|
||||
"releaseCli": "node packages/tools/release-cli.js",
|
||||
"releaseClipper": "node packages/tools/release-clipper.js",
|
||||
"releaseDesktop": "node packages/tools/release-electron.js",
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
This is visible
|
||||
This too<div style="display: none;">This is hidden
|
||||
and this sub-tag **too**
|
||||
</div>This is visible again
|
||||
</div>
|
||||
|
||||
This is visible again
|
||||
@@ -9,3 +9,9 @@
|
||||
<div><span style="font-weight: bold;">singleline bold text with span style font-weight: bold; and with trailing space </span>next to normal text.</div><div><br/></div>
|
||||
<div><span style="font-weight: bold;">singleline bold text with span style font-weight: bold;</span><span style="font-weight: bold;"> next to more bold text with span style font-weight: bold; and with leading space.</span></div><div><br/></div>
|
||||
<div><span style="font-weight: bold;">singleline bold text with span style font-weight: bold; and with trailing space </span><span style="font-weight: bold;">next to more bold text with span style font-weight: bold;.</span></div>
|
||||
|
||||
<div><span style="font-weight:bold;">bold no space</span></div>
|
||||
<div><span style="font-weight:700;">bold 700</span></div>
|
||||
<div><span style="font-weight:800;">bold 800</span></div>
|
||||
<div><span style="font-weight:900;">bold 900</span></div>
|
||||
<div><span style="font-weight:500;">not bold</span></div>
|
||||
@@ -12,4 +12,10 @@
|
||||
|
||||
**singleline bold text with span style font-weight: bold;**** next to more bold text with span style font-weight: bold; and with leading space.**
|
||||
|
||||
**singleline bold text with span style font-weight: bold; and with trailing space ****next to more bold text with span style font-weight: bold;.**
|
||||
**singleline bold text with span style font-weight: bold; and with trailing space ****next to more bold text with span style font-weight: bold;.**
|
||||
|
||||
**bold no space**
|
||||
**bold 700**
|
||||
**bold 800**
|
||||
**bold 900**
|
||||
not bold
|
||||
@@ -271,4 +271,48 @@ describe('models_Note', function() {
|
||||
expect(result).toBe(`[](:/${note1.id})`);
|
||||
}));
|
||||
|
||||
it('should perform natural sorting', (async () => {
|
||||
const folder1 = await Folder.save({});
|
||||
|
||||
const sortedNotes = await Note.previews(folder1.id, {
|
||||
fields: ['id', 'title'],
|
||||
order: [{ by: 'title', dir: 'ASC' }],
|
||||
});
|
||||
expect(sortedNotes.length).toBe(0);
|
||||
|
||||
const note0 = await Note.save({ title: 'A3', parent_id: folder1.id, is_todo: false });
|
||||
const note1 = await Note.save({ title: 'A20', parent_id: folder1.id, is_todo: false });
|
||||
const note2 = await Note.save({ title: 'A100', parent_id: folder1.id, is_todo: false });
|
||||
const note3 = await Note.save({ title: 'égalité', parent_id: folder1.id, is_todo: false });
|
||||
const note4 = await Note.save({ title: 'z', parent_id: folder1.id, is_todo: false });
|
||||
|
||||
const sortedNotes2 = await Note.previews(folder1.id, {
|
||||
fields: ['id', 'title'],
|
||||
order: [{ by: 'title', dir: 'ASC' }],
|
||||
});
|
||||
expect(sortedNotes2.length).toBe(5);
|
||||
expect(sortedNotes2[0].id).toBe(note0.id);
|
||||
expect(sortedNotes2[1].id).toBe(note1.id);
|
||||
expect(sortedNotes2[2].id).toBe(note2.id);
|
||||
expect(sortedNotes2[3].id).toBe(note3.id);
|
||||
expect(sortedNotes2[4].id).toBe(note4.id);
|
||||
|
||||
const todo3 = Note.changeNoteType(note3, 'todo');
|
||||
const todo4 = Note.changeNoteType(note4, 'todo');
|
||||
await Note.save(todo3);
|
||||
await Note.save(todo4);
|
||||
|
||||
const sortedNotes3 = await Note.previews(folder1.id, {
|
||||
fields: ['id', 'title'],
|
||||
order: [{ by: 'title', dir: 'ASC' }],
|
||||
uncompletedTodosOnTop: true,
|
||||
});
|
||||
expect(sortedNotes3.length).toBe(5);
|
||||
expect(sortedNotes3[0].id).toBe(note3.id);
|
||||
expect(sortedNotes3[1].id).toBe(note4.id);
|
||||
expect(sortedNotes3[2].id).toBe(note0.id);
|
||||
expect(sortedNotes3[3].id).toBe(note1.id);
|
||||
expect(sortedNotes3[4].id).toBe(note2.id);
|
||||
}));
|
||||
|
||||
});
|
||||
@@ -66,14 +66,14 @@ describe('pathUtils', function() {
|
||||
|
||||
it('should create correct fileURL syntax', (async () => {
|
||||
const testCases_win32 = [
|
||||
['C:\\handle\\space test', 'file:///C:/handle/space+test'],
|
||||
['C:\\handle\\space test', 'file:///C:/handle/space%20test'],
|
||||
['C:\\escapeplus\\+', 'file:///C:/escapeplus/%2B'],
|
||||
['C:\\handle\\single quote\'', 'file:///C:/handle/single+quote%27'],
|
||||
['C:\\handle\\single quote\'', 'file:///C:/handle/single%20quote%27'],
|
||||
];
|
||||
const testCases_unixlike = [
|
||||
['/handle/space test', 'file:///handle/space+test'],
|
||||
['/handle/space test', 'file:///handle/space%20test'],
|
||||
['/escapeplus/+', 'file:///escapeplus/%2B'],
|
||||
['/handle/single quote\'', 'file:///handle/single+quote%27'],
|
||||
['/handle/single quote\'', 'file:///handle/single%20quote%27'],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases_win32.length; i++) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
const { waitForFolderCount, newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('../../../test-utils');
|
||||
const Folder = require('@joplin/lib/models/Folder');
|
||||
|
||||
describe('JoplinSettings', () => {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await afterEachCleanUp();
|
||||
});
|
||||
|
||||
test('should listen to setting change event', async () => {
|
||||
const service = new newPluginService() as PluginService;
|
||||
|
||||
const pluginScript = newPluginScript(`
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.settings.registerSetting('myCustomSetting1', {
|
||||
value: 1,
|
||||
type: 1,
|
||||
public: true,
|
||||
label: 'My Custom Setting 1',
|
||||
});
|
||||
|
||||
await joplin.settings.registerSetting('myCustomSetting2', {
|
||||
value: 2,
|
||||
type: 1,
|
||||
public: true,
|
||||
label: 'My Custom Setting 2',
|
||||
});
|
||||
|
||||
joplin.settings.onChange((event) => {
|
||||
joplin.data.post(['folders'], null, { title: JSON.stringify(event.keys) });
|
||||
});
|
||||
},
|
||||
});
|
||||
`);
|
||||
|
||||
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
|
||||
await service.runPlugin(plugin);
|
||||
|
||||
Setting.setValue('plugin-org.joplinapp.plugins.PluginTest.myCustomSetting1', 111);
|
||||
Setting.setValue('plugin-org.joplinapp.plugins.PluginTest.myCustomSetting2', 222);
|
||||
|
||||
// Also change a global setting, to verify that the plugin doesn't get
|
||||
// notifications for non-plugin related events.
|
||||
Setting.setValue('locale', 'fr_FR');
|
||||
|
||||
Setting.emitScheduledChangeEvent();
|
||||
|
||||
await waitForFolderCount(1);
|
||||
|
||||
const folder = (await Folder.all())[0];
|
||||
|
||||
const settingNames: string[] = JSON.parse(folder.title);
|
||||
settingNames.sort();
|
||||
|
||||
expect(settingNames.join(',')).toBe('myCustomSetting1,myCustomSetting2');
|
||||
|
||||
await service.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,3 +1,7 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
|
||||
dist/*
|
||||
*.jpl
|
||||
@@ -6,3 +6,5 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+85
-10
@@ -701,14 +701,39 @@
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
@@ -3442,12 +3467,20 @@
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tapable": {
|
||||
@@ -3749,6 +3782,17 @@
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
@@ -3768,6 +3812,15 @@
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4395,6 +4448,28 @@
|
||||
"yargs": "^13.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
@@ -21,4 +24,4 @@
|
||||
"webpack-cli": "^3.3.11",
|
||||
"chalk": "^4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import joplin from 'api';
|
||||
import { ContentScriptType } from 'api/types';
|
||||
import { MenuItemLocation } from 'api/types';
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
@@ -9,5 +10,15 @@ joplin.plugins.register({
|
||||
'matchHighlighter',
|
||||
'./joplinMatchHighlighter.js'
|
||||
);
|
||||
|
||||
await joplin.commands.register({
|
||||
name: 'editor.printSomething',
|
||||
label: 'Print some random string',
|
||||
execute: async () => {
|
||||
alert('mathMode.printSomething not implemented by Editor yet');
|
||||
},
|
||||
});
|
||||
|
||||
await joplin.views.menuItems.create('printSomethingButton', 'editor.printSomething', MenuItemLocation.Tools, { accelerator: 'Ctrl+Alt+Shift+U' });
|
||||
},
|
||||
});
|
||||
@@ -6,5 +6,5 @@
|
||||
"version": "1.0.0",
|
||||
"author": "CalebJohn",
|
||||
"app_min_version": "1.4",
|
||||
"homepage_url": "inmoth.ca"
|
||||
"homepage_url": "joplinapp.org"
|
||||
}
|
||||
+63
-54
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extraScripts": [
|
||||
"markdownItTestPlugin.ts"
|
||||
]
|
||||
}
|
||||
@@ -6,8 +6,5 @@
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"author": "",
|
||||
"homepage_url": "",
|
||||
"content_scripts": [
|
||||
"markdownItTestPlugin"
|
||||
]
|
||||
"homepage_url": ""
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
@@ -9,10 +17,16 @@ const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
@@ -68,13 +82,7 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.warn(chalk.yellow('Plugin archive was not created because the "dist" directory is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -100,9 +108,13 @@ function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
try {
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
@@ -132,9 +144,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
});
|
||||
|
||||
const lastStepConfig = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -148,23 +157,15 @@ const lastStepConfig = {
|
||||
// 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 they 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackOnBuildPlugin(onBuildCompleted),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
@@ -173,52 +174,60 @@ const contentScriptConfig = Object.assign({}, baseConfig, {
|
||||
},
|
||||
});
|
||||
|
||||
function resolveContentScriptPaths(name) {
|
||||
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
|
||||
throw new Error(`Content script path must not include file extension: ${name}`);
|
||||
}
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const pathsToTry = [
|
||||
`./src/${name}.ts`,
|
||||
`${'./src/' + '/'}${name}.js`,
|
||||
];
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createContentScriptConfigs() {
|
||||
if (!manifest.content_scripts) return [];
|
||||
function addExtraScriptConfigs(baseConfig, userConfig) {
|
||||
if (!userConfig.extraScripts.length) return baseConfig;
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const contentScriptName of manifest.content_scripts) {
|
||||
const scriptPaths = resolveContentScriptPaths(contentScriptName);
|
||||
output.push(Object.assign({}, contentScriptConfig, {
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
return baseConfig.concat(output);
|
||||
}
|
||||
|
||||
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
|
||||
function addLastConfigStep(config) {
|
||||
const lastConfig = config[config.length - 1];
|
||||
if (!lastConfig.plugins) lastConfig.plugins = [];
|
||||
lastConfig.plugins.push(new WebpackOnBuildPlugin(onBuildCompleted));
|
||||
config[config.length - 1] = lastConfig;
|
||||
return config;
|
||||
}
|
||||
|
||||
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
|
||||
let exportedConfigs = [pluginConfig];
|
||||
|
||||
try {
|
||||
exportedConfigs = addExtraScriptConfigs(exportedConfigs, userConfig);
|
||||
exportedConfigs = addLastConfigStep(exportedConfigs);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
||||
@@ -1,3 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
@@ -29,6 +29,8 @@ 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.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## 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.
|
||||
@@ -49,44 +51,21 @@ In general all this is done automatically by the plugin generator, which will se
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `yo joplin --update`
|
||||
To update the plugin framework, run `npm run 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.
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
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.
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a 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
|
||||
## External script files
|
||||
|
||||
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/).
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
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.
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
For example, assuming these files:
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
```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.
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dist": "webpack",
|
||||
"prepare": "npm run dist"
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"keywords": ["joplin-plugin"],
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
||||
Loaded 100 of 194 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user