1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-05 00:12:33 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Laurent Cozic
c3854ec350 ios-v10.6.1 2021-01-02 14:54:31 +00:00
Laurent Cozic
c2c737541d ios release script 2021-01-02 14:53:30 +00:00
100 changed files with 373 additions and 3130 deletions

View File

@@ -155,9 +155,6 @@ packages/app-cli/tests/support/plugins/content_script/api/types.js.map
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
packages/app-cli/tests/support/plugins/content_script/src/index.js
packages/app-cli/tests/support/plugins/content_script/src/index.js.map
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.d.ts
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js.map
packages/app-cli/tests/support/plugins/dialog/api/index.d.ts
packages/app-cli/tests/support/plugins/dialog/api/index.js
packages/app-cli/tests/support/plugins/dialog/api/index.js.map
@@ -1391,12 +1388,6 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
packages/renderer/MdToHtml/rules/sanitize_html.js
packages/renderer/MdToHtml/rules/sanitize_html.js.map
packages/renderer/MdToHtml/setupLinkify.d.ts
packages/renderer/MdToHtml/setupLinkify.js
packages/renderer/MdToHtml/setupLinkify.js.map
packages/renderer/MdToHtml/validateLinks.d.ts
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map

9
.gitignore vendored
View File

@@ -144,9 +144,6 @@ packages/app-cli/tests/support/plugins/content_script/api/types.js.map
packages/app-cli/tests/support/plugins/content_script/src/index.d.ts
packages/app-cli/tests/support/plugins/content_script/src/index.js
packages/app-cli/tests/support/plugins/content_script/src/index.js.map
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.d.ts
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js
packages/app-cli/tests/support/plugins/content_script/src/markdownItTestPlugin.js.map
packages/app-cli/tests/support/plugins/dialog/api/index.d.ts
packages/app-cli/tests/support/plugins/dialog/api/index.js
packages/app-cli/tests/support/plugins/dialog/api/index.js.map
@@ -1380,12 +1377,6 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
packages/renderer/MdToHtml/rules/sanitize_html.js
packages/renderer/MdToHtml/rules/sanitize_html.js.map
packages/renderer/MdToHtml/setupLinkify.d.ts
packages/renderer/MdToHtml/setupLinkify.js
packages/renderer/MdToHtml/setupLinkify.js.map
packages/renderer/MdToHtml/validateLinks.d.ts
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map

View File

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

View File

@@ -147,86 +147,27 @@ describe('MdToHtml', function() {
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
}));
it('should render links correctly', (async () => {
const testCases = [
// 0: input
// 1: output with linkify = off
// 2: output with linkify = on
[
'https://example.com',
'https://example.com',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
],
[
'file://C:\\AUTOEXEC.BAT',
'file://C:\\AUTOEXEC.BAT',
'<a data-from-md title=\'file://C:%5CAUTOEXEC.BAT\' href=\'file://C:%5CAUTOEXEC.BAT\'>file://C:\\AUTOEXEC.BAT</a>',
],
[
'example.com',
'example.com',
'example.com',
],
[
'oo.ps',
'oo.ps',
'oo.ps',
],
[
'test@example.com',
'test@example.com',
'test@example.com',
],
[
'<https://example.com>',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
],
[
'[ok](https://example.com)',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
],
[
'[bla.pdf](file:///Users/tessus/Downloads/bla.pdf)',
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
],
];
// it('should render links correctly', (async () => {
// const mdToHtml = newTestMdToHtml();
const mdToHtmlLinkifyOn = newTestMdToHtml({
pluginOptions: {
linkify: { enabled: true },
},
});
// const testCases = [
// // None of these should result in a link
// ['https://example.com', 'https://example.com'],
// ['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
// ['example.com', 'example.com'],
// ['oo.ps', 'oo.ps'],
// ['test@example.com', 'test@example.com'],
const mdToHtmlLinkifyOff = newTestMdToHtml({
pluginOptions: {
linkify: { enabled: false },
},
});
// // Those should be converted to links
// ['<https://example.com>', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>'],
// ['[ok](https://example.com)', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>'],
// ];
for (const testCase of testCases) {
const [input, expectedLinkifyOff, expectedLinkifyOn] = testCase;
{
const actual = await mdToHtmlLinkifyOn.render(input, null, {
bodyOnly: true,
plainResourceRendering: true,
});
expect(actual.html).toBe(expectedLinkifyOn);
}
{
const actual = await mdToHtmlLinkifyOff.render(input, null, {
bodyOnly: true,
plainResourceRendering: true,
});
expect(actual.html).toBe(expectedLinkifyOff);
}
}
}));
// for (const testCase of testCases) {
// const [input, expected] = testCase;
// const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
// expect(actual.html).toBe(expected);
// }
// }));
});

View File

@@ -171,7 +171,7 @@ describe('services_PluginService', function() {
const contentScriptPath = `${tempDir}/markdownItTestPlugin.js`;
const contentScriptCssPath = `${tempDir}/markdownItTestPlugin.css`;
await shim.fsDriver().copy(`${testPluginDir}/markdownItTestPlugin.js`, contentScriptPath);
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.js`, contentScriptPath);
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.css`, contentScriptCssPath);
const service = newPluginService();

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -492,16 +492,6 @@
"dev": true,
"optional": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -1446,13 +1436,6 @@
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"dev": true
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2209,11 +2192,6 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"left-pad": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
"integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="
},
"loader-runner": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
@@ -2486,13 +2464,6 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"dev": true,
"optional": true
},
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -4115,11 +4086,7 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
"optional": true
},
"glob-parent": {
"version": "3.1.0",

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
const leftPad = require('left-pad');
function plugin(markdownIt, _options) {
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options, env, self);
@@ -10,7 +8,7 @@ function plugin(markdownIt, _options) {
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
return `
<div class="just-testing">
<p>JUST TESTING: <pre>${leftPad(token.content.trim(), 10, 'x')}</pre></p>
<p>JUST TESTING: ${token.content}</p>
<p><a href="#" onclick="webviewApi.executeCommand('testCommand', 'one', 'two'); return false;">Click to send "testCommand" to plugin</a></p>
<p><a href="#" onclick="webviewApi.executeCommand('testCommandNoArgs'); return false;">Click to send "testCommandNoArgs" to plugin</a></p>
</div>
@@ -18,13 +16,15 @@ function plugin(markdownIt, _options) {
};
}
export default function(_context) {
return {
plugin: plugin,
assets: function() {
return [
{ name: 'markdownItTestPlugin.css' }
];
},
}
module.exports = {
default: function(_context) {
return {
plugin: plugin,
assets: function() {
return [
{ name: 'markdownItTestPlugin.css' }
];
},
}
},
}

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,28 +0,0 @@
function plugin(markdownIt, _options) {
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options, env, self);
};
markdownIt.renderer.rules.fence = function(tokens, idx, options, env, self) {
const token = tokens[idx];
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
return `
<div class="just-testing">
<p>JUST TESTING: ${token.content}</p>
</div>
`;
};
}
module.exports = {
default: function(_context) {
return {
plugin: plugin,
assets: function() {
return [
{ name: 'markdownItTestPlugin.css' }
];
},
}
},
}

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -387,12 +387,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
padding-bottom: 400px !important;
}
/* Left padding is applied at the editor component level, so we should remove it from the lines */
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding-left: 0;
}
.CodeMirror-sizer {
/* Add a fixed right padding to account for the appearance (and disappearance) */
/* of the sidebar */

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,19 +19,16 @@
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<!--
android:requestLegacyExternalStorage: Android 10 introduced new "scoped storage" mechanism.
Android 10 introduced new "scoped storage" mechanism.
Apps targeting Android 10 (sdk 29) can no longer freely access arbitrary paths on the shared storage.
This attribute allows to opt out of this restriction.
android:allowBackup: used to enable Android Backup which some users need:
https://github.com/laurent22/joplin/issues/4020
The attribute "requestLegacyExternalStorage" below allows to opt out of this restriction.
-->
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme">

View File

@@ -82,7 +82,9 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
resources: noteResources,
codeTheme: theme.codeThemeCss,
postMessageSyntax: 'window.joplinPostMessage_',
enableLongPress: shim.mobilePlatform() === 'android', // On iOS, there's already a built-on open/share menu
// Disabled for now as it causes issues when zooming in or out
// https://github.com/laurent22/joplin/pull/3939#issuecomment-734260166
enableLongPress: false, // shim.mobilePlatform() === 'android', // On iOS, there's already a built-on open/share menu
};
// Whenever a resource state changes, for example when it goes from "not downloaded" to "downloaded", the "noteResources"

View File

@@ -338,13 +338,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 58;
CURRENT_PROJECT_VERSION = 59;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.6.0;
MARKETING_VERSION = 10.6.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -365,12 +365,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 58;
CURRENT_PROJECT_VERSION = 59;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.6.0;
MARKETING_VERSION = 10.6.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",

View File

@@ -20,63 +20,6 @@ yo joplin
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License

View File

@@ -15,7 +15,7 @@ function mergePackageKey(parentKey, source, dest) {
// If we are dealing with the dependencies, overwrite with the
// version from source.
output[k] = source[k];
} else if (typeof source[k] === 'object' && !Array.isArray(source[k]) && source[k] !== null) {
} else if (typeof source[k] === 'object' && !Array.isArray(k) && source[k] !== null) {
// If it's an object, recursively process it
output[k] = mergePackageKey(k, source[k], output[k]);
} else {
@@ -116,7 +116,6 @@ module.exports = class extends Generator {
'.gitignore_TEMPLATE',
'package_TEMPLATE.json',
'README.md',
'GENERATOR_DOC.md',
'tsconfig.json',
'webpack.config.js',
];

View File

@@ -1,83 +0,0 @@
# generator-joplin
Scaffolds out a new Joplin plugin
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo joplin
```
## Development
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
This is a template to create a new Joplin plugin.
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts
A plugin that uses [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinplugins.html#registercontentscript) must declare them under the `content_scripts` key of [manifest.json](https://joplinapp.org/api/references/plugin_manifest/).
Each entry must be a path **relative to /src**, and **without extension**. The extension should not be included because it might change once the script is compiled. Each of these scripts will then be compiled to JavaScript and packaged into the plugin file. The content script files can be TypeScript (.ts or .tsx) or JavaScript.
For example, assuming these files:
```bash
/src
index.ts # Main plugin script
myContentScript.js # One content script (JS)
otherContentScript.ts # Another content script (TypeScript)
vendor/
test.ts # Sub-directories are also supported
```
The `manifest.json` file would be:
```json
{
"manifest_version": 1,
"name": "Testing Content Scripts",
content_scripts: [
"myContentScript",
"otherContentScript",
"vendor/test"
]
}
```
Note in particular how the file path is relative to /src and the extensions removed.
## License
MIT © Laurent Cozic

View File

@@ -20,21 +20,17 @@ export default class JoplinPlugins {
*/
register(script: Script): Promise<void>;
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -47,10 +47,6 @@ export default class JoplinViewsDialogs {
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/

View File

@@ -1,10 +1,12 @@
{
"name": "joplin_plugin",
"version": "1.0.0",
"description": "",
"scripts": {
"dist": "webpack",
"postinstall": "npm run dist"
},
"keywords": [],
"license": "MIT",
"devDependencies": {
"@types/node": "^14.0.14",

View File

@@ -39,17 +39,17 @@ function createPluginArchive(sourceDir, destPath) {
console.info(`Plugin archive has been created in ${destPath}`);
}
const rootDir = path.resolve(__dirname);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(__dirname, 'dist');
const srcDir = path.resolve(__dirname, 'src');
const manifestPath = `${srcDir}/manifest.json`;
const manifest = readManifest(manifestPath);
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
fs.removeSync(distDir);
const baseConfig = {
module.exports = {
mode: 'production',
entry: './src/index.ts',
target: 'node',
module: {
rules: [
@@ -60,10 +60,6 @@ const baseConfig = {
},
],
},
};
const pluginConfig = Object.assign({}, baseConfig, {
entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
@@ -74,9 +70,6 @@ const pluginConfig = Object.assign({}, baseConfig, {
filename: 'index.js',
path: distDir,
},
});
const lastStepConfig = {
plugins: [
new CopyPlugin({
patterns: [
@@ -86,17 +79,8 @@ const lastStepConfig = {
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
// but theyr should be declared in manifest.json,
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js',
],
},
},
@@ -107,62 +91,3 @@ const lastStepConfig = {
}),
],
};
const contentScriptConfig = Object.assign({}, baseConfig, {
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
extensions: ['.tsx', '.ts', '.js'],
},
});
function resolveContentScriptPaths(name) {
if (['.js', '.ts', '.tsx'].includes(path.extname(name).toLowerCase())) {
throw new Error(`Content script path must not include file extension: ${name}`);
}
const pathsToTry = [
`./src/${name}.ts`,
`${'./src/' + '/'}${name}.js`,
];
for (const pathToTry of pathsToTry) {
if (fs.pathExistsSync(`${rootDir}/${pathToTry}`)) {
return {
entry: pathToTry,
output: {
filename: `${name}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
}
throw new Error(`Could not find content script "${name}" at locations ${JSON.stringify(pathsToTry)}`);
}
function createContentScriptConfigs() {
if (!manifest.content_scripts) return [];
const output = [];
for (const contentScriptName of manifest.content_scripts) {
const scriptPaths = resolveContentScriptPaths(contentScriptName);
output.push(Object.assign({}, contentScriptConfig, {
entry: scriptPaths.entry,
output: scriptPaths.output,
}));
}
return output;
}
const exportedConfigs = [pluginConfig].concat(createContentScriptConfigs());
exportedConfigs[exportedConfigs.length - 1] = Object.assign({}, exportedConfigs[exportedConfigs.length - 1], lastStepConfig);
module.exports = exportedConfigs;

View File

@@ -1,6 +1,6 @@
{
"name": "generator-joplin",
"version": "1.6.2",
"version": "1.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,11 +1,11 @@
{
"name": "generator-joplin",
"version": "1.6.2",
"version": "1.6.0",
"description": "Scaffolds out a new Joplin plugin",
"homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin",
"homepage": "https://joplinapp.org",
"author": {
"name": "Laurent Cozic",
"url": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin"
"url": "https://joplinapp.org"
},
"files": [
"generators"
@@ -26,4 +26,4 @@
"repository": "https://github.com/laurent22/generator-joplin",
"license": "MIT",
"private": true
}
}

View File

@@ -1,7 +1,6 @@
const moment = require('moment');
const { dirname, basename } = require('./path-utils');
const shim = require('./shim').default;
const Buffer = require('buffer').Buffer;
class FileApiDriverOneDrive {
constructor(api) {
@@ -134,19 +133,17 @@ class FileApiDriverOneDrive {
if (!options) options = {};
let response = null;
// We need to check the file size as files > 4 MBs are uploaded in a different way than files < 4 MB (see https://docs.microsoft.com/de-de/onedrive/developer/rest-api/concepts/upload?view=odsp-graph-online)
let byteSize = null;
if (options.source == 'file') {
byteSize = (await shim.fsDriver().stat(options.path)).size;
// We need to check the file size as files > 4 MBs are uploaded in a different way than files < 4 MB (see https://docs.microsoft.com/de-de/onedrive/developer/rest-api/concepts/upload?view=odsp-graph-online)
const fileSize = (await shim.fsDriver().stat(options.path)).size;
path = fileSize < 4 * 1024 * 1024 ? `${this.makePath_(path)}:/content` : `${this.makePath_(path)}:/createUploadSession`;
response = await this.api_.exec('PUT', path, null, null, options);
} else {
options.headers = { 'Content-Type': 'text/plain' };
byteSize = Buffer.byteLength(content);
response = await this.api_.exec('PUT', `${this.makePath_(path)}:/content`, null, content, options);
}
path = byteSize < 4 * 1024 * 1024 ? `${this.makePath_(path)}:/content` : `${this.makePath_(path)}:/createUploadSession`;
response = await this.api_.exec('PUT', path, null, content, options);
return response;
}

View File

@@ -1,7 +1,7 @@
import { validateLinks } from '@joplin/renderer';
const stringPadding = require('string-padding');
const urlUtils = require('./urlUtils');
const MarkdownIt = require('markdown-it');
const { setupLinkify } = require('@joplin/renderer');
// Taken from codemirror/addon/edit/continuelist.js
const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
@@ -47,7 +47,7 @@ const markdownUtils = {
// Returns the **encoded** URLs, so to be useful they should be decoded again before use.
extractImageUrls(md: string) {
const markdownIt = new MarkdownIt();
markdownIt.validateLink = validateLinks; // Necessary to support file:/// links
setupLinkify(markdownIt); // Necessary to support file:/// links
const env = {};
const tokens = markdownIt.parse(md, env);

View File

@@ -4,7 +4,6 @@ const time = require('./time').default;
const Logger = require('./Logger').default;
const { _ } = require('./locale');
const urlUtils = require('./urlUtils.js');
const Buffer = require('buffer').Buffer;
class OneDriveApi {
// `isPublic` is to tell OneDrive whether the application is a "public" one (Mobile and desktop
@@ -137,25 +136,20 @@ class OneDriveApi {
}
}
async uploadChunk(url, handle, buffer, options) {
async uploadChunk(url, handle, options) {
options = Object.assign({}, options);
if (!options.method) { options.method = 'POST'; }
if (!options.headers) { options.headers = {}; }
if (!options.contentLength) throw new Error('uploadChunk: contentLength is missing');
if (!options.headers) throw new Error('uploadChunk: header is missing');
if (buffer) {
options.body = buffer.slice(options.startByte, options.startByte + options.contentLength);
} else {
const chunk = await shim.fsDriver().readFileChunk(handle, options.contentLength);
const buffer = Buffer.from(chunk, 'base64');
options.body = buffer;
}
if (!options.contentLength) throw new Error(' uploadChunk: contentLength is missing');
const chunk = await shim.fsDriver().readFileChunk(handle, options.contentLength);
const Buffer = require('buffer').Buffer;
const buffer = Buffer.from(chunk, 'base64');
delete options.contentLength;
delete options.startByte;
options.body = buffer;
const response = await shim.fetch(url,options);
const response = await shim.fetch(url, options);
return response;
}
@@ -171,20 +165,12 @@ class OneDriveApi {
return response;
} else {
const uploadUrl = (await response.json()).uploadUrl;
// uploading file in 7.5 MiB-Fragments (except the last one) because this is the mean of 5 and 10 Mib which are the recommended lower and upper limits.
// https://docs.microsoft.com/de-de/onedrive/developer/rest-api/api/driveitem_createuploadsession?view=odsp-graph-online#best-practices
const chunkSize = 7.5 * 1024 * 1024;
let byteSize = null;
let handle = null;
let buffer = null;
if (options.body) {
byteSize = Buffer.byteLength(options.body);
buffer = Buffer.from(options.body);
} else {
byteSize = (await shim.fsDriver().stat(options.path)).size;
handle = await shim.fsDriver().open(options.path, 'r');
}
const numberOfChunks = Math.ceil(byteSize / chunkSize);
const fileSize = (await shim.fsDriver().stat(options.path)).size;
const numberOfChunks = Math.ceil(fileSize / chunkSize);
const handle = await shim.fsDriver().open(options.path, 'r');
try {
for (let i = 0; i < numberOfChunks; i++) {
@@ -193,34 +179,32 @@ class OneDriveApi {
let contentLength = null;
if (i === numberOfChunks - 1) {
// Last fragment. It is not ensured that the last fragment is a multiple of 327,680 bytes as recommanded in the api doc. The reasons is that the docs are out of day for this purpose: https://github.com/OneDrive/onedrive-api-docs/issues/1200#issuecomment-597281253
endByte = byteSize - 1;
contentLength = byteSize - ((numberOfChunks - 1) * chunkSize);
endByte = fileSize - 1;
contentLength = fileSize - ((numberOfChunks - 1) * chunkSize);
} else {
endByte = (i + 1) * chunkSize - 1;
contentLength = chunkSize;
}
this.logger().debug(`Uploading File Fragment ${(startByte / 1048576).toFixed(2)} - ${(endByte / 1048576).toFixed(2)} from ${(byteSize / 1048576).toFixed(2)} Mbit ...`);
this.logger().debug(`${options.path}: Uploading File Fragment ${(startByte / 1048576).toFixed(2)} - ${(endByte / 1048576).toFixed(2)} from ${(fileSize / 1048576).toFixed(2)} Mbit ...`);
const headers = {
'Content-Length': contentLength,
'Content-Range': `bytes ${startByte}-${endByte}/${byteSize}`,
'Content-Range': `bytes ${startByte}-${endByte}/${fileSize}`,
'Content-Type': 'application/octet-stream; charset=utf-8',
};
const response = await this.uploadChunk(uploadUrl, handle, buffer, { startByte: startByte, contentLength: contentLength, method: 'PUT', headers: headers });
const response = await this.uploadChunk(uploadUrl, handle, { contentLength: contentLength, method: 'PUT', headers: headers });
if (!response.ok) {
return response;
}
}
return { ok: true };
} catch (error) {
const type = (handle) ? 'Resource' : 'Note Content';
this.logger().error(`Couldn't upload ${type} > 4 Mb. Got unhandled error:`, error ? error.code : '', error ? error.message : '', error);
this.logger().error('Got unhandled error:', error ? error.code : '', error ? error.message : '', error);
throw error;
} finally {
if (handle) await shim.fsDriver().close(handle);
await shim.fsDriver().close(handle);
}
}
}
@@ -266,10 +250,8 @@ class OneDriveApi {
let response = null;
try {
if (path.includes('/createUploadSession')) {
response = await this.uploadBigFile(url, options);
} else if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
response = await shim.uploadBlob(url, options);
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
response = path.includes('/createUploadSession') ? await this.uploadBigFile(url, options) : await shim.uploadBlob(url, options);
} else if (options.target == 'string') {
response = await shim.fetch(url, options);
} else {

View File

@@ -51,21 +51,17 @@ export default class JoplinPlugins {
}
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
* Registers a new content script. Unlike regular plugin code, which
* runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
* Note that registering a content script in itself will do nothing -
* it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)

View File

@@ -77,13 +77,6 @@ export default class JoplinViewsDialogs {
return this.controller(handle).html = html;
}
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
public async addScript(handle: ViewHandle, scriptPath: string) {
return this.controller(handle).addScript(scriptPath);
}
/**
* Sets the dialog buttons.
*/

View File

@@ -2,9 +2,6 @@ import { PluginStates } from '../reducer';
import { ContentScriptType } from '../api/types';
import { dirname } from '@joplin/renderer/pathUtils';
import shim from '../../../shim';
import Logger from '../../../Logger';
const logger = Logger.create('loadContentScripts');
export interface ExtraContentScript {
id: string;
@@ -31,24 +28,17 @@ function loadContentScripts(plugins: PluginStates, scriptType: ContentScriptType
if (!contentScripts) continue;
for (const contentScript of contentScripts) {
try {
const module = shim.requireDynamic(contentScript.path);
if (!module.default || typeof module.default !== 'function') throw new Error(`Content script must export a function under the "default" key: Plugin: ${pluginId}: Script: ${contentScript.id}`);
const module = shim.requireDynamic(contentScript.path);
if (!module.default || typeof module.default !== 'function') throw new Error(`Content script must export a function under the "default" key: Plugin: ${pluginId}: Script: ${contentScript.id}`);
const loadedModule = module.default({});
if (!loadedModule.plugin && !loadedModule.codeMirrorResources && !loadedModule.codeMirrorOptions) throw new Error(`Content script must export a "plugin" key or a list of CodeMirror assets or define a CodeMirror option: Plugin: ${pluginId}: Script: ${contentScript.id}`);
const loadedModule = module.default({});
if (!loadedModule.plugin && !loadedModule.codeMirrorResources && !loadedModule.codeMirrorOptions) throw new Error(`Content script must export a "plugin" key or a list of CodeMirror assets or define a CodeMirror option: Plugin: ${pluginId}: Script: ${contentScript.id}`);
output.push({
id: contentScript.id,
module: loadedModule,
assetPath: dirname(contentScript.path),
});
} catch (error) {
// This function must not throw as doing so would crash the
// application, which we want to avoid for plugins. Instead log
// the error, and continue loading the other content scripts.
logger.error(error.message);
}
output.push({
id: contentScript.id,
module: loadedModule,
assetPath: dirname(contentScript.path),
});
}
}

View File

@@ -61,7 +61,6 @@ const globalStyle: any = {
appearance: 'light',
mainPadding: 12,
topRowHeight: 50,
editorPaddingLeft: 8,
};
globalStyle.marginRight = globalStyle.margin;

View File

@@ -1,8 +1,6 @@
import InMemoryCache from './InMemoryCache';
import noteStyle from './noteStyle';
import { fileExtension } from './pathUtils';
import setupLinkify from './MdToHtml/setupLinkify';
import validateLinks from './MdToHtml/validateLinks';
const MarkdownIt = require('markdown-it');
const md5 = require('md5');
@@ -43,6 +41,7 @@ const rules: RendererRules = {
mermaid: require('./MdToHtml/rules/mermaid').default,
};
const setupLinkify = require('./MdToHtml/setupLinkify');
const hljs = require('highlight.js');
const uslug = require('uslug');
const markdownItAnchor = require('markdown-it-anchor');
@@ -518,8 +517,6 @@ export default class MdToHtml {
}
}
markdownIt.validateLink = validateLinks;
if (this.pluginEnabled('linkify')) setupLinkify(markdownIt);
const renderedBody = markdownIt.render(body, context);

View File

@@ -91,9 +91,7 @@ export default function(href: string, options: Options = null): LinkReplacementR
if (options.enableLongPress && !!resourceId) {
const onClick = `${options.postMessageSyntax}(${JSON.stringify(href)})`;
const onLongClick = `${options.postMessageSyntax}("longclick:${resourceId}")`;
// if t is set when ontouchstart is called it means the user has already touched the screen once and this is the 2nd touch
// in this case we assume the user is trying to zoom and we don't want to show the menu
const touchStart = `if (typeof(t) !== "undefined" && !!t) { clearTimeout(t); t = null; } else { t = setTimeout(() => { t = null; ${onLongClick}; }, ${utils.longPressDelay}); }`;
const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${utils.longPressDelay});`;
const cancel = 'if (!!t) {clearTimeout(t); t=null;';
const touchEnd = `${cancel} ${onClick};}`;
js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}' ontouchcancel='${cancel} ontouchmove="${cancel}'`;

View File

@@ -22,9 +22,7 @@ function plugin(markdownIt: any, ruleOptions: RuleOptions) {
const id = r['data-resource-id'];
const longPressHandler = `${ruleOptions.postMessageSyntax}('longclick:${id}')`;
// if t is set when ontouchstart is called it means the user has already touched the screen once and this is the 2nd touch
// in this case we assume the user is trying to zoom and we don't want to show the menu
const touchStart = `if (typeof(t) !== 'undefined' && !!t) { clearTimeout(t); t = null; } else { t = setTimeout(() => { t = null; ${longPressHandler}; }, ${utils.longPressDelay}); }`;
const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${utils.longPressDelay});`;
const cancel = 'if (!!t) clearTimeout(t); t=null';
js = ` ontouchstart="${touchStart}" ontouchend="${cancel}" ontouchcancel="${cancel}" ontouchmove="${cancel}"`;

View File

@@ -0,0 +1,39 @@
module.exports = function(markdownIt) {
// Add `file:` protocol in linkify to allow text in the format of "file://..." to translate into
// file-URL links in html view
markdownIt.linkify.add('file:', {
validate: function(text, pos, self) {
const tail = text.slice(pos);
if (!self.re.file) {
// matches all local file URI on Win/Unix/MacOS systems including reserved characters in some OS (i.e. no OS specific sanity check)
self.re.file = new RegExp('^[\\/]{2,3}[\\S]+');
}
if (self.re.file.test(tail)) {
return tail.match(self.re.file)[0].length;
}
return 0;
},
});
// enable file link URLs in MarkdownIt. Keeps other URL restrictions of MarkdownIt untouched.
// Format [link name](file://...)
markdownIt.validateLink = function(url) {
const BAD_PROTO_RE = /^(vbscript|javascript|data):/;
const GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/;
// url should be normalized at this point, and existing entities are decoded
const str = url.trim().toLowerCase();
if (str.indexOf('data:image/svg+xml,') === 0) {
return true;
}
return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
};
markdownIt.linkify.set({
'fuzzyLink': false,
'fuzzyIP': false,
'fuzzyEmail': false,
});
};

View File

@@ -1,23 +0,0 @@
export default function(markdownIt: any) {
// Add `file:` protocol in linkify to allow text in the format of "file://..." to translate into
// file-URL links in html view
markdownIt.linkify.add('file:', {
validate: function(text: string, pos: number, self: any) {
const tail = text.slice(pos);
if (!self.re.file) {
// matches all local file URI on Win/Unix/MacOS systems including reserved characters in some OS (i.e. no OS specific sanity check)
self.re.file = new RegExp('^[\\/]{2,3}[\\S]+');
}
if (self.re.file.test(tail)) {
return tail.match(self.re.file)[0].length;
}
return 0;
},
});
markdownIt.linkify.set({
'fuzzyLink': false,
'fuzzyIP': false,
'fuzzyEmail': false,
});
}

View File

@@ -1,15 +0,0 @@
// enable file link URLs in MarkdownIt. Keeps other URL restrictions of MarkdownIt untouched.
// Format [link name](file://...)
export default function(url: string) {
const BAD_PROTO_RE = /^(vbscript|javascript|data):/;
const GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/;
// url should be normalized at this point, and existing entities are decoded
const str = url.trim().toLowerCase();
if (str.indexOf('data:image/svg+xml,') === 0) {
return true;
}
return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
}

View File

@@ -2,8 +2,7 @@ import MarkupToHtml, { MarkupLanguage } from './MarkupToHtml';
import MdToHtml from './MdToHtml';
import HtmlToHtml from './HtmlToHtml';
import utils from './utils';
import setupLinkify from './MdToHtml/setupLinkify';
import validateLinks from './MdToHtml/validateLinks';
const setupLinkify = require('./MdToHtml/setupLinkify');
const assetsToHeaders = require('./assetsToHeaders');
export {
@@ -12,7 +11,6 @@ export {
MdToHtml,
HtmlToHtml,
setupLinkify,
validateLinks,
assetsToHeaders,
utils,
};

View File

@@ -307,7 +307,7 @@ export default function(theme: any) {
.mce-content-body {
/* Note: we give a bit more padding at the bottom, to allow scrolling past the end of the document */
padding: 5px 10px 10em 0;
padding: 5px 10px 10em 10px;
}
/*

View File

@@ -45,8 +45,6 @@ async function main() {
await execCommandVerbose('git', ['tag', tagName]);
await execCommandVerbose('git', ['push']);
await execCommandVerbose('git', ['push', '--tags']);
console.info(`To create changelog: node packages/tools/git-changelog.js ${tagName}`);
}
main().catch((error) => {

View File

@@ -2,18 +2,17 @@
The manifest file is a JSON file that describes various properties of the plugin. If you use the Yeoman generator, it should be automatically generated based on the answers you've provided. The supported properties are:
Name | Type | Required? | Description
--- | --- | --- | ---
`manifest_version` | number | **Yes** | For now should always be "1".
`name` | string | **Yes** | Name of the plugin. Should be a user-friendly string, as it will be displayed in the UI.
`version` | string | **Yes** | Version number such as "1.0.0".
`app_min_version` | string | **Yes** | Minimum version of Joplin that the plugin is compatible with. In general it should be whatever version you are using to develop the plugin.
`description` | string | No | Detailed description of the plugin.
`author` | string | No | Plugin author name.
`homepage_url` | string | No | Homepage URL of the plugin. It can also be, for example, a link to a GitHub repository.
`content_scripts` | string[] | No | List of [content scripts](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md#content-scripts) used by the plugin.
Name | Required? | Description
--- | --- | ---
`manifest_version` | **Yes** | For now should always be "1".
`name` | **Yes** | Name of the plugin. Should be a user-friendly string, as it will be displayed in the UI.
`version` | **Yes** | Version number such as "1.0.0".
`app_min_version` | **Yes** | Minimum version of Joplin that the plugin is compatible with. In general it should be whatever version you are using to develop the plugin.
`description` | No | Detailed description of the plugin.
`author` | No | Plugin author name.
`homepage_url` | No | Homepage URL of the plugin. It can also be, for example, a link to a GitHub repository.
## Manifest example
Here's a complete example:
```json
{