1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-05 20:56:22 +02:00

Compare commits

...

88 Commits

Author SHA1 Message Date
Laurent Cozic
e1cd8d9b85 Server v2.10.8 2023-02-09 18:24:30 +00:00
Laurent Cozic
293f621e46 Server: Fixed sharing issue when a user no longer has a user item associated with their account 2023-02-09 18:24:09 +00:00
Joplin Bot
c5b551bbcb Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-09 18:17:12 +00:00
Laurent Cozic
ac776eabc1 Server v2.10.7 2023-02-09 15:57:38 +00:00
Laurent Cozic
05c17fbfac Server: Fixed sharing issue for changes that are associated with deleted items 2023-02-09 15:56:44 +00:00
Laurent Cozic
6b9de394ca Tools: Update post release commands 2023-02-09 11:48:22 +00:00
Laurent Cozic
bef9a29581 Renovate exclude 2023-02-09 01:34:02 +00:00
renovate[bot]
1546aad7e9 Update dependency punycode to v2.3.0 (#7749) 2023-02-09 01:33:32 +00:00
renovate[bot]
fe47fef261 Update dependency redux to v4.2.1 (#7743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 00:24:35 +00:00
renovate[bot]
288f4ab43b Update dependency react-native-image-picker to v5.0.1 (#7747)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-08 20:36:31 +00:00
renovate[bot]
294cbaea4d Update dependency react-native-image-picker to v5 (#7746) 2023-02-08 17:01:40 +00:00
Carlos
f5fc1f2f22 Mobile: Resolves #1044: Add create sub-notebook feature (#7728) 2023-02-08 14:34:29 +00:00
Henry Heino
c90865c4d2 Mobile: Fixes #7700: Fix double-scroll issue in long notes (#7701) 2023-02-08 14:24:59 +00:00
Helmut K. C. Tessarek
322641ccd6 Desktop: Fixes #7694: Markdown editor not surrounding highlighted text with backticks (#7697) 2023-02-08 14:24:20 +00:00
Henry Heino
2656666ed8 Mobile: Fixes #7687: Fix startup error (#7688) 2023-02-08 14:21:59 +00:00
andy1631
2d673902a4 Desktop: Fix highlighting in GotoAnything dialogue (#7592) 2023-02-08 14:20:54 +00:00
Julien
631c41a1ff Desktop: Resolves #6143: Show installed plugins in Help - About Joplin (#7711) 2023-02-08 14:16:09 +00:00
renovate[bot]
7841c99c02 Update dependency @rmp135/sql-ts to v1.16.0 (#7736) 2023-02-08 14:13:42 +00:00
renovate[bot]
ac75d8f6ac Update dependency react-native-safe-area-context to v4.5.0 (#7744) 2023-02-08 14:13:28 +00:00
github-actions[bot]
200ff617dc @rakeshhotker has signed the CLA from Pull Request #7745 2023-02-08 06:07:30 +00:00
Joplin Bot
bbdc18f371 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-07 18:18:46 +00:00
Joplin Bot
0d35b64f9a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-07 12:21:36 +00:00
renovate[bot]
765c4482d6 Update dependency react-native-webview to v11.26.1 (#7735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-07 06:18:23 +00:00
renovate[bot]
0a0e31a37c Update dependency madge to v5.0.2 (#7734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-07 02:34:11 +00:00
Laurent Cozic
0c1f4031b4 Server v2.10.6 2023-02-06 19:01:57 +00:00
Laurent Cozic
9ed022458b Chore: Server: Clean up 2023-02-06 18:59:36 +00:00
Laurent Cozic
ba5f0bc6e3 Server: Fixed issue when an item is associated with a share that no longer exists 2023-02-06 18:59:36 +00:00
renovate[bot]
793e8f6c0f Update dependency jsdom to v21 (#7732) 2023-02-06 17:29:47 +00:00
renovate[bot]
6182ce521d Update dependency nodemailer to v6.9.1 (#7726) 2023-02-06 16:17:15 +00:00
Joplin Bot
544c50663a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-06 10:17:57 +00:00
Laurent Cozic
53aa9e2b42 Chore: Restore stats 2023-02-06 10:07:25 +00:00
renovate[bot]
884260189c Update dependency punycode to v2.2.2 (#7724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-05 20:28:43 +00:00
Laurent Cozic
2f9464f21f Tools: Revert CI issue 2023-02-05 19:10:31 +00:00
github-actions[bot]
c6e993b04a @TaoK has signed the CLA from Pull Request #7729 2023-02-05 17:06:32 +00:00
Laurent Cozic
89eb012b25 Tools: Add repeat mechanism when electron-builder randomly fails to build 2023-02-05 16:51:47 +00:00
Laurent Cozic
1e2aa4e2b5 Tools: Fixed Renovate patterns 2023-02-05 12:27:09 +00:00
Laurent Cozic
0019bb8d6b Tools: Add eslint rule "@typescript-eslint/no-inferrable-types" 2023-02-05 12:27:09 +00:00
renovate[bot]
e629a4d325 Update dependency nodemailer to v6.9.0 (#7722) 2023-02-05 11:43:37 +00:00
renovate[bot]
049c769d37 Update dependency eslint-plugin-react to v7.32.0 (#7698) 2023-02-05 11:41:13 +00:00
renovate[bot]
47aed8742a Update dependency punycode to v2.2.0 (#7695) 2023-02-05 11:40:58 +00:00
Adarsh Singh
8aad67ccfe Desktop: Fixes #7521: Mermaid images are incorrectly sized when exported as PNG (#7546) 2023-02-05 11:39:26 +00:00
renovate[bot]
af7cbcbca7 Update dependency eslint-plugin-import to v2.27.4 (#7717) 2023-02-05 11:06:20 +00:00
Laurent Cozic
88a91314af Tools: Reduce noise with Renovate updates 2023-02-05 11:06:10 +00:00
renovate[bot]
9873c2d756 Update dependency glob to v8.1.0 (#7718) 2023-02-04 16:12:16 +00:00
renovate[bot]
46b68cf461 Update typescript-eslint monorepo to v5.48.2 (#7705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 07:58:54 +00:00
renovate[bot]
6b96b1f355 Update dependency react-native-paper to v5.1.4 (#7714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 05:37:03 +00:00
renovate[bot]
dc819700bb Update dependency knex to v2.4.2 (#7713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 01:11:49 +00:00
renovate[bot]
1d1d5fea06 Update dependency @react-native-community/datetimepicker to v6.7.3 (#7712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 21:02:58 +00:00
renovate[bot]
c3afc0ede7 Update dependency prettier to v2.8.3 (#7704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 17:08:36 +00:00
github-actions[bot]
2092110a6e @julien-me has signed the CLA from Pull Request #7711 2023-02-03 12:51:14 +00:00
github-actions[bot]
fc940e9a7c @abhiippili has signed the CLA from Pull Request #7709 2023-02-01 17:12:16 +00:00
github-actions[bot]
8718310dd0 @deepampriyadarshi has signed the CLA from Pull Request #7708 2023-02-01 15:00:52 +00:00
renovate[bot]
1b527f2bbe Update dependency @react-native-community/slider to v4.4.1 (#7702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-01 00:38:41 +00:00
Joplin Bot
4da217bc2f Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-31 00:43:35 +00:00
github-actions[bot]
d3abd4ebf2 @tessus has signed the CLA from Pull Request #7697 2023-01-31 00:13:17 +00:00
Mr-Kanister
38851edf86 All: Translation: Update de_DE.po (#7696) 2023-01-30 18:15:05 -05:00
Joplin Bot
05efb765d6 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-30 18:16:44 +00:00
majsterkovic
28dc4a6abd All: Translation: Update pl_PL.po (#7690) 2023-01-29 22:54:17 -05:00
renovate[bot]
2f7b56f96f Update dependency @react-native-community/datetimepicker to v6.7.2 (#7689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-30 02:26:11 +00:00
renovate[bot]
fdaa3735fb Update dependency @types/yargs to v17.0.20 (#7685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 17:03:51 +00:00
Laurent Cozic
7dfaea12f7 Chore: Fixed build following conversion from JSX to TSX 2023-01-29 13:11:53 +00:00
renovate[bot]
18199b27d9 Update dependency @types/react to v17.0.53 (#7684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 06:18:39 +00:00
renovate[bot]
a7c52082bb Update dependency @types/react to v16.14.35 (#7683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 02:18:29 +00:00
renovate[bot]
3b5357e0c1 Update dependency @types/jest to v29.2.6 (#7682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 22:41:15 +00:00
Self Not Found
10dd4e45ed Desktop: Fixes #7678: Fix open files with non-ASCII characters in path (#7679) 2023-01-28 12:28:01 +00:00
github-actions[bot]
1963835309 @carlosngo has signed the CLA from Pull Request #7681 2023-01-28 10:07:12 +00:00
renovate[bot]
46ec0c1381 Update dependency knex to v2.4.1 (#7676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 03:43:16 +00:00
Laurent Cozic
bb84ae4d68 Tools: Disable tests that randomly fail onn CI 2023-01-27 18:55:39 +00:00
Laurent Cozic
b3ff53c0da Tools: Added hack to try to fix issue with broken GitHub API 2023-01-27 17:16:23 +00:00
Self Not Found
acd7bfd9f5 Desktop: Remove auto-matching for greater than character (#7669) 2023-01-27 16:50:07 +00:00
renovate[bot]
1dff50d080 Update dependency knex to v2.4.0 (#7674) 2023-01-27 16:47:20 +00:00
Laurent Cozic
af40970d09 Ignore package 2023-01-27 16:46:37 +00:00
Joplin Bot
07535a494e Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-27 06:17:16 +00:00
Joplin Bot
907422cefa Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-27 00:45:49 +00:00
renovate[bot]
f643baea25 Update dependency tap to v16.3.4 (#7671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 08:50:49 +00:00
github-actions[bot]
55a4f33982 @majsterkovic has signed the CLA from Pull Request #7668 2023-01-25 21:26:20 +00:00
Dmitriy Q
df6700959a All: Translation: Update ru_RU.po (#7665) 2023-01-25 16:10:50 -05:00
github-actions[bot]
fde8235f3e @krote5k has signed the CLA from Pull Request #7665 2023-01-25 12:59:46 +00:00
Laurent Cozic
f6ba56d966 Doc: Added GSoC idea 2023-01-24 15:56:22 +00:00
Laurent Cozic
4a5312823b Doc: Add info for GSoC 2023 2023-01-24 15:18:51 +00:00
Light
31a27b0e1c Desktop: Fixes #7565: Fix text editor text highlighting when used with special IME methods (#7630) 2023-01-24 14:46:40 +00:00
github-actions[bot]
984ad868e8 @trevor-james-nangosha has signed the CLA from Pull Request #7663 2023-01-24 12:56:39 +00:00
Betty Alagwu
9b657eeda2 Desktop: Resolves #7602: Fix copy text with no selection (#7641) 2023-01-23 18:50:24 +00:00
Laurent Cozic
6f3ad4b3b0 Doc: Allow setting period from parameter 2023-01-23 17:49:16 +00:00
renovate[bot]
56f06fae3c Update dependency ts-jest to v29.0.5 (#7659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 15:52:54 +00:00
ThetaDev
c22d884357 Doc: Update rscss link (#7653) 2023-01-23 10:11:34 +00:00
github-actions[bot]
35dc22197d @Theta-Dev has signed the CLA from Pull Request #7653 2023-01-22 22:43:08 +00:00
renovate[bot]
70d56ca0be Update dependency ts-jest to v29.0.4 (#7651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-22 02:05:55 +00:00
78 changed files with 2055 additions and 4802 deletions

View File

@@ -756,6 +756,7 @@ packages/lib/utils/credentialFiles.js
packages/lib/utils/joplinCloud.js
packages/lib/uuid.js
packages/lib/versionInfo.js
packages/lib/versionInfo.test.js
packages/pdf-viewer/FullViewer.js
packages/pdf-viewer/Page.js
packages/pdf-viewer/PdfDocument.js

View File

@@ -159,6 +159,7 @@ module.exports = {
// make everything public which is not great. New code however should specify member accessibility.
'@typescript-eslint/explicit-member-accessibility': ['warn'],
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
'@typescript-eslint/comma-dangle': ['error', {
'arrays': 'always-multiline',
'objects': 'always-multiline',

1
.gitignore vendored
View File

@@ -744,6 +744,7 @@ packages/lib/utils/credentialFiles.js
packages/lib/utils/joplinCloud.js
packages/lib/uuid.js
packages/lib/versionInfo.js
packages/lib/versionInfo.test.js
packages/pdf-viewer/FullViewer.js
packages/pdf-viewer/Page.js
packages/pdf-viewer/PdfDocument.js

View File

@@ -130,7 +130,11 @@
});
setupBetaHandling(urlQuery);
applyPeriod('yearly');
if (urlQuery.get('period') === 'monthly') {
// Nothing - this is the default
} else {
applyPeriod('yearly');
}
});
</script>
</div>

View File

@@ -65,21 +65,21 @@
},
"devDependencies": {
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.48.0",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"cspell": "5.21.2",
"eslint": "8.31.0",
"eslint-interactive": "10.3.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.27.4",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react": "7.32.0",
"fs-extra": "11.1.0",
"glob": "8.0.3",
"glob": "8.1.0",
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "13.1.0",
"madge": "5.0.1",
"madge": "5.0.2",
"npm-package-json-lint": "6.4.0",
"typedoc": "0.17.8",
"typescript": "4.9.4"

View File

@@ -12,7 +12,7 @@ class Command extends BaseCommand {
}
async action() {
this.stdout(versionInfo(require('./package.json')).message);
this.stdout(versionInfo(require('./package.json'), {}).message);
}
}

View File

@@ -70,7 +70,7 @@
"devDependencies": {
"@joplin/tools": "~2.10",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"gulp": "4.0.2",
"jest": "29.3.1",

View File

@@ -20,7 +20,7 @@ const { space } = require('styled-system');
const logger = Logger.create('PluginState');
const maxWidth: number = 320;
const maxWidth = 320;
const Root = styled.div`
display: flex;

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import versionInfo from '@joplin/lib/versionInfo';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';
import restart from '../services/restart';
const packageInfo = require('../packageInfo.js');
@@ -21,6 +21,7 @@ interface State {
error: Error;
errorInfo: ErrorInfo;
pluginInfos: PluginInfo[];
plugins: Plugins;
}
interface Props {
@@ -29,14 +30,16 @@ interface Props {
export default class ErrorBoundary extends React.Component<Props, State> {
public state: State = { error: null, errorInfo: null, pluginInfos: [] };
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
componentDidCatch(error: any, errorInfo: ErrorInfo) {
if (typeof error === 'string') error = { message: error };
const pluginInfos: PluginInfo[] = [];
let plugins: Plugins = {};
try {
const service = PluginService.instance();
plugins = service.plugins;
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
for (const pluginId in pluginSettings) {
const plugin = PluginService.instance().pluginById(pluginId);
@@ -52,7 +55,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
console.error('Could not get plugin info:', error);
}
this.setState({ error, errorInfo, pluginInfos });
this.setState({ error, errorInfo, pluginInfos, plugins });
}
componentDidMount() {
@@ -91,7 +94,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
output.push(
<section key="versionInfo">
<h2>Version info</h2>
<pre>{versionInfo(packageInfo).message}</pre>
<pre>{versionInfo(packageInfo, this.state.plugins).message}</pre>
</section>
);

View File

@@ -4,10 +4,10 @@ interface Props {
style: any;
itemHeight: number;
items: any[];
disabled: boolean;
onKeyDown: Function;
disabled?: boolean;
onKeyDown?: Function;
itemRenderer: Function;
className: string;
className?: string;
}
interface State {

View File

@@ -21,6 +21,7 @@ import checkForUpdates from '../checkForUpdates';
const { connect } = require('react-redux');
import { reg } from '@joplin/lib/registry';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const packageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;
@@ -485,7 +486,8 @@ function useMenu(props: Props) {
}
function _showAbout() {
const v = versionInfo(packageInfo);
const v = versionInfo(packageInfo, PluginService.instance().plugins);
const copyToClipboard = bridge().showMessageBox(v.message, {
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,

View File

@@ -31,6 +31,7 @@ import dialogs from '../../../dialogs';
import convertToScreenCoordinates from '../../../utils/convertToScreenCoordinates';
import { MarkupToHtml } from '@joplin/renderer';
const { clipboard } = require('electron');
const debounce = require('debounce');
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -287,8 +288,17 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const editorCopyText = useCallback(() => {
if (editorRef.current) {
const selections = editorRef.current.getSelections();
if (selections.length > 0) {
// Handle the case when there is a selection - copy the selection to the clipboard
// When there is no selection, the selection array contains an empty string.
if (selections.length > 0 && selections[0]) {
clipboard.writeText(selections[0]);
} else {
// This is the case when there is no selection - copy the current line to the clipboard
const cursor = editorRef.current.getCursor();
const line = editorRef.current.getLine(cursor.line);
clipboard.writeText(line);
}
}
}, []);
@@ -404,7 +414,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const maxWidthCss = props.contentMaxWidth ? `
margin-right: auto !important;
margin-left: auto !important;
max-width: ${props.contentMaxWidth}px !important;
max-width: ${props.contentMaxWidth}px !important;
` : '';
const element = document.createElement('style');
@@ -673,7 +683,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}, [renderedBody, webviewReady]);
useEffect(() => {
if (!props.searchMarkers) return;
if (!props.searchMarkers) return () => {};
// If there is a currently active search, it's important to re-search the text as the user
// types. However this is slow for performance so we ONLY want it to happen when there is
@@ -688,11 +698,19 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
webviewRef.current.send('setMarkers', props.searchMarkers.keywords, props.searchMarkers.options);
if (editorRef.current) {
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
// Fixes https://github.com/laurent22/joplin/issues/7565
const debouncedMarkers = debounce(() => {
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
props.setLocalSearchResultCount(matches);
props.setLocalSearchResultCount(matches);
}, 50);
debouncedMarkers();
return () => {
debouncedMarkers.clear();
};
}
}
return () => {};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [props.searchMarkers, previousSearchMarkers, props.setLocalSearchResultCount, props.content, previousContent, renderedBody, previousRenderedBody, renderedBody]);
@@ -851,7 +869,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
function renderEditor() {
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '<>()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '``()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
return (
<div style={cellEditorStyle}>
@@ -909,4 +927,3 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
export default forwardRef(CodeMirror);

View File

@@ -102,7 +102,7 @@ interface LastOnChangeEventInfo {
let loadedCssFiles_: string[] = [];
let loadedJsFiles_: string[] = [];
let dispatchDidUpdateIID_: any = null;
let changeId_: number = 1;
let changeId_ = 1;
const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const [editor, setEditor] = useState(null);

View File

@@ -2,7 +2,7 @@ import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng, svgDimensions } from './contextMenuUtils';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import Resource from '@joplin/lib/models/Resource';
@@ -106,8 +106,10 @@ export function menuItems(dispatch: Function): ContextMenuItems {
if (!options.filename) {
throw new Error('Filename is needed to save as png');
}
// double dimensions to make sure it's always big enough even on hdpi screens
const [width, height] = svgDimensions(document, options.textToCopy).map((x: number) => x * 2 || undefined);
const dataUri = textToDataUri(options.textToCopy, options.mime);
const png = await svgUriToPng(document, dataUri);
const png = await svgUriToPng(document, dataUri, width, height);
const filename = options.filename.replace('.svg', '.png');
await saveFileData(png, filename);
},

View File

@@ -1,5 +1,6 @@
import Resource from '@joplin/lib/models/Resource';
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('contextMenuUtils');
export enum ContextMenuItemType {
None = '',
Image = 'image',
@@ -40,8 +41,23 @@ export async function resourceInfo(options: ContextMenuOptions) {
export function textToDataUri(text: string, mime: string): string {
return `data:${mime};base64,${Buffer.from(text).toString('base64')}`;
}
export const svgUriToPng = (document: Document, svg: string) => {
export const svgDimensions = (document: Document, svg: string) => {
let width: number;
let height: number;
try {
const parser = new DOMParser();
const id = parser.parseFromString(svg, 'text/html').querySelector('svg').id;
({ width, height } = document.querySelector<HTMLIFrameElement>('.noteTextViewer').contentWindow.document.querySelector(`#${id}`).getBoundingClientRect());
} catch (error) {
logger.warn('Could not get SVG dimensions.');
logger.warn('Error was: ', error);
}
if (!width || !height) {
return [undefined, undefined];
}
return [width, height];
};
export const svgUriToPng = (document: Document, svg: string, width: number, height: number) => {
return new Promise<Uint8Array>((resolve, reject) => {
let canvas: HTMLCanvasElement;
let img: HTMLImageElement;
@@ -63,11 +79,21 @@ export const svgUriToPng = (document: Document, svg: string) => {
try {
canvas = document.createElement('canvas');
if (!canvas) throw new Error('Failed to create canvas element');
canvas.width = img.width;
canvas.height = img.height;
if (!width || !height) {
const maxDimension = 1024;
if (img.width > img.height) {
width = maxDimension;
height = width * (img.height / img.width);
} else {
height = maxDimension;
width = height * (img.width / img.height);
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get context');
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
const pngUri = canvas.toDataURL('image/png');
if (!pngUri) throw new Error('Failed to generate png uri');
const pngBase64 = pngUri.split(',')[1];

View File

@@ -21,6 +21,9 @@ const tasks = {
electronRebuild: {
fn: require('./tools/electronRebuild.js'),
},
electronBuilder: {
fn: require('./tools/electronBuilder.js'),
},
tsc: require('@joplin/tools/gulp/tasks/tsc'),
updateIgnoredTypeScriptBuild: require('@joplin/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
buildCommandIndex: require('@joplin/tools/gulp/tasks/buildCommandIndex'),

View File

@@ -8,6 +8,7 @@
"dist": "yarn run electronRebuild && npx electron-builder",
"build": "gulp build",
"postinstall": "yarn run build",
"electronBuilder": "gulp electronBuilder",
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
@@ -108,16 +109,16 @@
"devDependencies": {
"@joplin/tools": "~2.10",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"@types/react": "16.14.34",
"@types/react": "16.14.35",
"@types/react-redux": "7.1.25",
"@types/styled-components": "5.1.26",
"electron": "19.1.4",
"electron-builder": "23.6.0",
"electron-notarize": "1.2.2",
"electron-rebuild": "3.2.9",
"glob": "8.0.3",
"glob": "8.1.0",
"gulp": "4.0.2",
"jest": "29.3.1",
"jest-environment-jsdom": "29.3.1",
@@ -165,7 +166,7 @@
"react-select": "5.7.0",
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",
"redux": "4.2.0",
"redux": "4.2.1",
"reselect": "4.1.7",
"roboto-fontface": "0.10.0",
"smalltalk": "2.5.1",

View File

@@ -3,17 +3,16 @@ import { AppState } from '../app.reducer';
import CommandService, { SearchResult as CommandSearchResult } from '@joplin/lib/services/CommandService';
import KeymapService from '@joplin/lib/services/KeymapService';
import shim from '@joplin/lib/shim';
const { connect } = require('react-redux');
const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme');
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import gotoAnythingStyleQuery from '@joplin/lib/services/searchengine/gotoAnythingStyleQuery';
import BaseModel from '@joplin/lib/BaseModel';
import Tag from '@joplin/lib/models/Tag';
import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note';
const { ItemList } = require('../gui/ItemList.min');
import ItemList from '../gui/ItemList';
import HelpButton from '../gui/HelpButton';
const { surroundKeywords, nextWhitespaceIndex, removeDiacritics } = require('@joplin/lib/string-utils.js');
import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils';

View File

@@ -22,6 +22,8 @@
# ./runForTesting.sh 1 createUsers,createData,reset,sync && ./runForTesting.sh 2 reset,sync && ./runForTesting.sh 1
# ./runForTesting.sh 1 createUsers,createData,reset,sync && ./runForTesting.sh 2 reset,sync && ./runForTesting.sh 3 reset,sync && ./runForTesting.sh 1
# ----------------------------------------------------------------------------------
# To create two client profiles, in sync, both used by the same user:
# ----------------------------------------------------------------------------------

View File

@@ -0,0 +1,31 @@
// Note: this is not working because electron-builder needs access to env
// variables which are not defined in this context. To make it work, we'll need
// to somehow pass this to the execCommand call.
const execCommand = require('./execCommand');
async function main() {
process.chdir(`${__dirname}/..`);
const maxTries = 3;
for (let i = 0; i < maxTries; i++) {
try {
console.info(await execCommand(['yarn', 'run', 'electron-builder'].join(' ')));
console.info('electronBuilder: electron-builder completed successfully');
break;
} catch (error) {
console.info(error.stdout);
console.error(error);
if (error.stdout.includes('cannot resolve') && i !== maxTries - 1) {
console.info(`electronBuilder: electron-builder could not download an asset - trying again (${i + 1})`);
continue;
} else {
throw error;
}
}
}
}
module.exports = main;

View File

@@ -1,22 +1,4 @@
const execCommand = function(command) {
const exec = require('child_process').exec;
console.info(`Running: ${command}`);
return new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
if (error.signal === 'SIGTERM') {
resolve('Process was killed');
} else {
reject(error);
}
} else {
resolve(stdout.trim());
}
});
});
};
const execCommand = require('./execCommand');
const isWindows = () => {
return process && process.platform === 'win32';

View File

@@ -0,0 +1,22 @@
const execCommand = (command) => {
const exec = require('child_process').exec;
console.info(`Running: ${command}`);
return new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
if (error.signal === 'SIGTERM') {
resolve('Process was killed');
} else {
error.stdout = stdout;
reject(error);
}
} else {
resolve(stdout.trim());
}
});
});
};
module.exports = execCommand;

View File

@@ -1,24 +1,7 @@
const fs = require('fs');
const path = require('path');
const electron_notarize = require('electron-notarize');
function execCommand(command) {
const exec = require('child_process').exec;
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
if (error.signal === 'SIGTERM') {
resolve('Process was killed');
} else {
reject(new Error([stdout.trim(), stderr.trim()].join('\n')));
}
} else {
resolve([stdout.trim(), stderr.trim()].join('\n'));
}
});
});
}
const execCommand = require('./execCommand');
function isDesktopAppTag(tagName) {
if (!tagName) return false;

View File

@@ -25,9 +25,11 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
const [resourceLoadedTime, setResourceLoadedTime] = useState(0);
const [isFirstRender, setIsFirstRender] = useState(true);
const paddingTop = '.8em';
const rendererTheme = useMemo(() => {
return {
bodyPaddingTop: '.8em', // Extra top padding on the rendered MD so it doesn't touch the border
bodyPaddingTop: paddingTop, // Extra top padding on the rendered MD so it doesn't touch the border
bodyPaddingBottom: paddingBottom, // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text)
...themeStyle(themeId),
};
@@ -152,8 +154,14 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
*/
body > #rendered-md {
width: 100vw;
height: 100vh;
overflow: auto;
height: calc(100vh - ${paddingBottom}px - ${paddingTop});
padding-bottom: ${paddingBottom}px;
padding-top: ${paddingTop};
}
:root > body {
padding: 0;
}
`;

View File

@@ -65,41 +65,41 @@ describe('markdownCommands.toggleList', () => {
});
it('should correctly replace an unordered list with a checklist', async () => {
const editor = await createEditor(
unorderedListText,
EditorSelection.cursor(unorderedListText.length),
['BulletList']
);
// it('should correctly replace an unordered list with a checklist', async () => {
// const editor = await createEditor(
// unorderedListText,
// EditorSelection.cursor(unorderedListText.length),
// ['BulletList']
// );
toggleList(ListType.CheckList)(editor);
expect(editor.state.doc.toString()).toBe(
'- [ ] 1\n- [ ] 2\n- [ ] 3\n- [ ] 4\n- [ ] 5\n- [ ] 6\n- [ ] 7'
);
});
// toggleList(ListType.CheckList)(editor);
// expect(editor.state.doc.toString()).toBe(
// '- [ ] 1\n- [ ] 2\n- [ ] 3\n- [ ] 4\n- [ ] 5\n- [ ] 6\n- [ ] 7'
// );
// });
it('should properly toggle a sublist of a bulleted list', async () => {
const preSubListText = '# List test\n * This\n * is\n';
const initialDocText = `${preSubListText}\t* a\n\t* test\n * of list toggling`;
// it('should properly toggle a sublist of a bulleted list', async () => {
// const preSubListText = '# List test\n * This\n * is\n';
// const initialDocText = `${preSubListText}\t* a\n\t* test\n * of list toggling`;
const editor = await createEditor(
initialDocText,
EditorSelection.cursor(preSubListText.length + '\t* a'.length),
['BulletList', 'ATXHeading1']
);
// const editor = await createEditor(
// initialDocText,
// EditorSelection.cursor(preSubListText.length + '\t* a'.length),
// ['BulletList', 'ATXHeading1']
// );
// Indentation should be preserved when changing list types
toggleList(ListType.OrderedList)(editor);
expect(editor.state.doc.toString()).toBe(
'# List test\n * This\n * is\n\t1. a\n\t2. test\n * of list toggling'
);
// // Indentation should be preserved when changing list types
// toggleList(ListType.OrderedList)(editor);
// expect(editor.state.doc.toString()).toBe(
// '# List test\n * This\n * is\n\t1. a\n\t2. test\n * of list toggling'
// );
// The changed region should be selected
expect(editor.state.selection.main.from).toBe(preSubListText.length);
expect(editor.state.selection.main.to).toBe(
`${preSubListText}\t1. a\n\t2. test`.length
);
});
// // The changed region should be selected
// expect(editor.state.selection.main.from).toBe(preSubListText.length);
// expect(editor.state.selection.main.to).toBe(
// `${preSubListText}\t1. a\n\t2. test`.length
// );
// });
it('should not preserve indentation when removing sublists', async () => {
const preSubListText = '# List test\n * This\n * is\n';

View File

@@ -577,7 +577,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
<Text key="label" style={this.styles().settingText}>
{md.label()}
</Text>
<TextInput autoCorrect={false} autoCompleteType="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={(value: any) => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
<TextInput autoCorrect={false} autoComplete="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={(value: any) => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
</View>
);
} else {

View File

@@ -63,6 +63,10 @@ class FolderScreenComponent extends BaseScreenComponent {
async saveFolderButton_press() {
let folder = Object.assign({}, this.state.folder);
if (this.props.navigation.state.parentFolderId) {
folder.parent_id = this.props.navigation.state.parentFolderId;
}
try {
folder = await Folder.save(folder, { userSideValidation: true });
} catch (error) {

View File

@@ -138,6 +138,19 @@ const SideMenuContentComponent = (props: Props) => {
'',
_('Notebook: %s', folder.title),
[
{
text: _('New sub-notebook'),
onPress: () => {
props.dispatch({ type: 'SIDE_MENU_CLOSE' });
props.dispatch({
type: 'NAV_GO',
routeName: 'Folder',
folderId: null,
parentFolderId: folder.id,
});
},
},
{
text: _('Rename'),
onPress: () => {

View File

@@ -23,11 +23,11 @@
"@joplin/react-native-saf-x": "~2.10",
"@joplin/renderer": "~2.10",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/datetimepicker": "6.7.1",
"@react-native-community/datetimepicker": "6.7.3",
"@react-native-community/geolocation": "2.1.0",
"@react-native-community/netinfo": "9.3.7",
"@react-native-community/push-notification-ios": "1.10.1",
"@react-native-community/slider": "4.4.0",
"@react-native-community/slider": "4.4.1",
"assert-browserify": "2.0.0",
"buffer": "6.0.3",
"constants-browserify": "1.0.0",
@@ -37,7 +37,7 @@
"lodash": "4.17.21",
"md5": "2.3.0",
"prop-types": "15.8.1",
"punycode": "2.1.1",
"punycode": "2.3.0",
"react": "18.2.0",
"react-native": "0.70.6",
"react-native-action-button": "2.8.5",
@@ -50,14 +50,14 @@
"react-native-fingerprint-scanner": "6.0.0",
"react-native-fs": "2.20.0",
"react-native-get-random-values": "1.8.0",
"react-native-image-picker": "4.10.3",
"react-native-image-picker": "5.0.1",
"react-native-image-resizer": "1.4.5",
"react-native-modal-datetime-picker": "14.0.1",
"react-native-paper": "5.1.3",
"react-native-paper": "5.1.4",
"react-native-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13",
"react-native-rsa-native": "2.0.5",
"react-native-safe-area-context": "4.4.1",
"react-native-safe-area-context": "4.5.0",
"react-native-securerandom": "1.0.1",
"react-native-share": "8.1.0",
"react-native-side-menu-updated": "1.3.2",
@@ -65,9 +65,9 @@
"react-native-url-polyfill": "1.3.0",
"react-native-vector-icons": "9.2.0",
"react-native-version-info": "1.1.1",
"react-native-webview": "11.26.0",
"react-native-webview": "11.26.1",
"react-redux": "7.2.9",
"redux": "4.2.0",
"redux": "4.2.1",
"rn-fetch-blob": "0.12.0",
"stream": "0.0.2",
"stream-browserify": "3.0.0",
@@ -94,8 +94,8 @@
"@joplin/tools": "~2.10",
"@lezer/highlight": "1.1.3",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/react-native": "0.64.19",
"@types/jest": "29.2.6",
"@types/react-native": "0.70.6",
"@types/react-redux": "7.1.25",
"babel-plugin-module-resolver": "4.1.0",
"execa": "4.1.0",
@@ -104,11 +104,11 @@
"jest": "29.3.1",
"jest-environment-jsdom": "29.3.1",
"jetifier": "2.0.0",
"jsdom": "20.0.0",
"jsdom": "21.0.0",
"md5-file": "5.0.0",
"metro-react-native-babel-preset": "0.72.3",
"nodemon": "2.0.20",
"ts-jest": "29.0.3",
"ts-jest": "29.0.5",
"ts-loader": "9.4.2",
"ts-node": "10.9.1",
"typescript": "4.9.4",

View File

@@ -28,8 +28,8 @@ import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
import initProfile from '@joplin/lib/services/profileConfig/initProfile';
const VersionInfo = require('react-native-version-info').default;
const { Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform, Dimensions } = require('react-native');
const RNAppState = require('react-native').AppState;
const { Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Platform, Dimensions } = require('react-native');
import { AppState as RNAppState, EmitterSubscription, Linking, NativeEventSubscription } from 'react-native';
import getResponsiveValue from './components/getResponsiveValue';
import NetInfo from '@react-native-community/netinfo';
const DropdownAlert = require('react-native-dropdownalert').default;
@@ -704,6 +704,9 @@ async function initialize(dispatch: Function) {
class AppComponent extends React.Component {
private urlOpenListener_: EmitterSubscription|null = null;
private appStateChangeListener_: NativeEventSubscription|null = null;
public constructor() {
super();
@@ -796,7 +799,7 @@ class AppComponent extends React.Component {
// }, 1000);
}
Linking.addEventListener('url', this.handleOpenURL_);
this.urlOpenListener_ = Linking.addEventListener('url', this.handleOpenURL_);
BackButtonService.initialize(this.backButtonHandler_);
@@ -806,7 +809,7 @@ class AppComponent extends React.Component {
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
});
RNAppState.addEventListener('change', this.onAppStateChange_);
this.appStateChangeListener_ = RNAppState.addEventListener('change', this.onAppStateChange_);
this.unsubscribeScreenWidthChangeHandler_ = Dimensions.addEventListener('change', this.handleScreenWidthChange_);
await this.handleShareData();
@@ -820,8 +823,15 @@ class AppComponent extends React.Component {
}
public componentWillUnmount() {
RNAppState.removeEventListener('change', this.onAppStateChange_);
Linking.removeEventListener('url', this.handleOpenURL_);
if (this.appStateChangeListener_) {
this.appStateChangeListener_.remove();
this.appStateChangeListener_ = null;
}
if (this.urlOpenListener_) {
this.urlOpenListener_.remove();
this.urlOpenListener_ = null;
}
if (this.unsubscribeScreenWidthChangeHandler_) {
this.unsubscribeScreenWidthChangeHandler_.remove();

View File

@@ -45,16 +45,16 @@
"entities": "2.2.0"
},
"devDependencies": {
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.48.0",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"coveralls": "3.1.1",
"eslint": "8.31.0",
"eslint-config-prettier": "8.6.0",
"jest": "29.3.1",
"prettier": "2.8.1",
"ts-jest": "29.0.3",
"prettier": "2.8.3",
"ts-jest": "29.0.5",
"typescript": "4.9.4"
},
"jest": {

View File

@@ -16,7 +16,7 @@
],
"devDependencies": {
"standard": "17.0.0",
"tap": "16.3.3"
"tap": "16.3.4"
},
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -9,13 +9,18 @@ describe('StringUtils', function() {
it('should surround keywords with strings', (async () => {
const testCases = [
[[], 'test', 'a', 'b', 'test'],
[['test'], 'test', 'a', 'b', 'atestb'],
[['test'], 'Test', 'a', 'b', 'aTestb'],
[['te[]st'], 'Te[]st', 'a', 'b', 'aTe[]stb'],
[[], 'test', 'a', 'b', null, 'test'],
[['test'], 'test', 'a', 'b', null, 'atestb'],
[['test'], 'Test', 'a', 'b', null, 'aTestb'],
[['te[]st'], 'Te[]st', 'a', 'b', null, 'aTe[]stb'],
// [['test1', 'test2'], 'bla test1 blabla test1 bla test2 not this one - test22', 'a', 'b', 'bla atest1b blabla atest1b bla atest2b not this one - test22'],
[['test1', 'test2'], 'bla test1 test1 bla test2', '<span class="highlighted-keyword">', '</span>', 'bla <span class="highlighted-keyword">test1</span> <span class="highlighted-keyword">test1</span> bla <span class="highlighted-keyword">test2</span>'],
[['test1', 'test2'], 'bla test1 test1 bla test2', '<span class="highlighted-keyword">', '</span>', null, 'bla <span class="highlighted-keyword">test1</span> <span class="highlighted-keyword">test1</span> bla <span class="highlighted-keyword">test2</span>'],
// [[{ type:'regex', value:'test.*?'}], 'bla test1 test1 bla test2 test tttest', 'a', 'b', 'bla atest1b atest1b bla atest2b atestb tttest'],
[['test'], 'testTest', 'a', 'b', { escapeHtml: true }, 'atestbaTestb'],
[['test'], 'test test Test', 'a', 'b', { escapeHtml: true }, 'atestb atestb aTestb'],
[['d'], 'dfasdf', '[', ']', { escapeHtml: true }, '[d]fas[d]f'],
[[{ scriptType: 'en', type: 'regex', value: 'd*', valueRegex: 'd[^ \t\n\r,\\.,\\+\\-\\*\\?\\!\\=\\{\\}\\<\\>\\|\\:"\'\\(\\)\\[\\]]*?' }], 'dfasdf', '[', ']', { escapeHtml: true }, '[d]fas[d]f'],
[['zzz'], 'zzz<img src=q onerror=eval("require(\'child_process\').exec(\'mate-calc\');");>', 'a', 'b', { escapeHtml: true }, 'azzzb&lt;img src=q onerror=eval(&quot;require(&apos;child_process&apos;).exec(&apos;mate-calc&apos;);&quot;);&gt;'],
];
for (let i = 0; i < testCases.length; i++) {
@@ -25,9 +30,10 @@ describe('StringUtils', function() {
const input = t[1];
const prefix = t[2];
const suffix = t[3];
const expected = t[4];
const options = t[4];
const expected = t[5];
const actual = StringUtils.surroundKeywords(keywords, input, prefix, suffix);
const actual = StringUtils.surroundKeywords(keywords, input, prefix, suffix, options);
expect(actual).toBe(expected, `Test case ${i}`);
}

View File

@@ -17,11 +17,11 @@
},
"devDependencies": {
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/js-yaml": "4.0.5",
"@types/node": "18.11.18",
"@types/node-rsa": "1.1.1",
"@types/react": "17.0.52",
"@types/react": "17.0.53",
"@types/uuid": "^9.0.0",
"clean-html": "1.5.0",
"jest": "29.3.1",
@@ -77,7 +77,7 @@
"query-string": "7.1.3",
"re-reselect": "4.0.1",
"read-chunk": "2.1.0",
"redux": "4.2.0",
"redux": "4.2.1",
"relative": "3.0.2",
"reselect": "4.1.7",
"server-destroy": "1.0.1",

View File

@@ -178,7 +178,7 @@ export default class ReportService {
for (let i = 0; i < disabledItems.length; i++) {
const row = disabledItems[i];
let msg: string = '';
let msg = '';
if (row.location === BaseItem.SYNC_ITEM_LOCATION_LOCAL) {
msg = _('%s (%s) could not be uploaded: %s', row.item.title, row.item.id, row.syncInfo.sync_disabled_reason);
} else {

View File

@@ -15,7 +15,7 @@ interface AdvancedExpression {
function parseAdvancedExpression(advancedExpression: string): AdvancedExpression {
let subExpressionIndex = -1;
let subExpressions: string = '';
let subExpressions = '';
let currentSubExpressionKey = '';
const subContext: any = {};

View File

@@ -100,7 +100,7 @@ export default class MenuUtils {
}
public commandsToMenuItems(commandNames: string[], onClick: Function, locale: string): MenuItems {
const key: string = `${this.keymapService.lastSaveTime}_${commandNames.join('_')}_${locale}`;
const key = `${this.keymapService.lastSaveTime}_${commandNames.join('_')}_${locale}`;
if (this.menuItemCache_[key]) return this.menuItemCache_[key];
const output: MenuItems = {};

View File

@@ -61,7 +61,7 @@ export default async function populateDatabase(db: any, options: Options = null)
const createdTagIds: string[] = [];
const createdFolderDepths: Record<string, number> = {};
const folderDepthToId: Record<number, string[]> = {};
let rootFolderCount: number = 0;
let rootFolderCount = 0;
for (let i = 0; i < options.folderCount; i++) {
const folder: any = {
@@ -72,7 +72,7 @@ export default async function populateDatabase(db: any, options: Options = null)
if (options.rootFolderCount && rootFolderCount >= options.rootFolderCount) isRoot = false;
let depth: number = 0;
let depth = 0;
if (!isRoot) {
let possibleFolderIds: string[] = [];

View File

@@ -110,7 +110,7 @@ export default class InteropService_Importer_Md extends InteropService_Importer_
if (stat && !isDir) {
const supportedFileExtension = this.metadata().fileExtensions;
const resolvedPath = shim.fsDriver().resolve(pathWithExtension);
let id: string = '';
let id = '';
// If the link looks like a note, then import it
if (supportedFileExtension.indexOf(fileExtension(trimmedLink).toLowerCase()) >= 0) {
// If the note hasn't been imported yet, do so now

View File

@@ -188,7 +188,7 @@ describe('ShareService', function() {
const recipientPpk = await generateKeyPair(encryptionService(), '222222');
expect(ppk.id).not.toBe(recipientPpk.id);
let uploadedEmail: string = '';
let uploadedEmail = '';
let uploadedMasterKey: MasterKeyEntity = null;
const service = testShareFolderService({

View File

@@ -141,7 +141,7 @@ async function testMigrationE2EE(migrationVersion: number, maxSyncVersion: numbe
await expectNotThrow(async () => await checkTestData(testData));
}
let previousSyncTargetName: string = '';
let previousSyncTargetName = '';
describe('MigrationHandler', function() {

View File

@@ -637,7 +637,11 @@ function shimInit(options = null) {
}
// Open the file
return shim.openUrl(`file://${filepath}`);
// Don't use openUrl() there.
// The underneath require('electron').shell.openExternal() has a bug
// https://github.com/electron/electron/issues/31347
return shim.electronBridge().openItem(filepath);
};
shim.waitForFrame = () => {};

View File

@@ -301,20 +301,17 @@ function surroundKeywords(keywords, text, prefix, suffix, options = null) {
escapeHtml: false,
}, options);
if (!keywords.length) return text;
text = options.escapeHtml ? htmlentities(text) : text;
function escapeHtml(s) {
if (!options.escapeHtml) return s;
return htmlentities(s);
}
if (!keywords.length) return text;
let regexString = keywords
.map(k => {
if (k.type === 'regex') {
return escapeHtml(stringUtilsCommon.replaceRegexDiacritics(k.valueRegex));
return stringUtilsCommon.replaceRegexDiacritics(k.valueRegex);
} else {
const value = typeof k === 'string' ? k : k.value;
return escapeHtml(stringUtilsCommon.replaceRegexDiacritics(stringUtilsCommon.pregQuote(value)));
return stringUtilsCommon.replaceRegexDiacritics(stringUtilsCommon.pregQuote(value));
}
})
.join('|');

View File

@@ -0,0 +1,132 @@
import versionInfo from './versionInfo';
import { reg } from './registry';
import { Plugins } from './services/plugins/PluginService';
import Plugin from './services/plugins/Plugin';
jest.mock('./registry');
const info = jest.spyOn(console, 'info').mockImplementation(() => {});
const mockedVersion = jest.fn(() => 'test');
const mockedDb = { version: mockedVersion };
const packageInfo = {
'name': 'Joplin',
'version': '2.10.5',
'description': 'Joplin for Desktop',
'repository': {
'type': 'git',
'url': 'git+https://github.com/laurent22/joplin.git',
},
'author': 'Laurent Cozic',
'license': 'AGPL-3.0-or-later',
'bugs': {
'url': 'https://github.com/laurent22/joplin/issues',
},
'homepage': 'https://github.com/laurent22/joplin#readme',
'build': {
'appId': 'net.cozic.joplin-desktop',
},
'git': {
'branch': 'dev',
'hash': '1b527f2bb',
},
};
describe('getPluginLists', function() {
beforeAll(() => {
(reg.db as jest.Mock).mockReturnValue(mockedDb);
});
beforeEach(() => {
jest.clearAllMocks();
});
it('should not list any plugin when no plugin is installed', () => {
const v = versionInfo(packageInfo, {});
expect(v.body).toMatch(/Revision:\s[a-z0-9]{3,}\s\([a-zA-Z0-9-_/.]{1,}\)$/);
expect(v.message).toMatch(/Revision:\s[a-z0-9]{3,}\s\([a-zA-Z0-9-_/.]{1,}\)$/);
});
it('should list one plugin', () => {
const plugin: Plugin = new Plugin(
'',
{
manifest_version: 1,
id: '1',
name: 'Plugin1',
version: '1',
app_min_version: '1' },
'',
() => {},
''
);
const plugins: Plugins = {};
plugins[plugin.manifest.id] = plugin;
const v = versionInfo(packageInfo, plugins);
expect(v.body).toMatch(/\n\nPlugin1: 1/);
expect(v.message).toMatch(/\n\nPlugin1: 1/);
});
it('should show a list of three plugins', () => {
const plugins: Plugins = {};
for (let i = 1; i <= 3; i++) {
const plugin: Plugin = new Plugin(
'',
{
manifest_version: i,
id: i.toString(),
name: `Plugin${i}`,
version: '1',
app_min_version: '1' },
'',
() => {},
''
);
plugins[plugin.manifest.id] = plugin;
}
const v = versionInfo(packageInfo, plugins);
expect(v.body).toMatch(/\n\nPlugin1: 1\nPlugin2: 1\nPlugin3: 1/);
expect(v.message).toMatch(/\n\nPlugin1: 1\nPlugin2: 1\nPlugin3: 1/);
});
it('should show an abridged list of plugins in message and the full list in body', () => {
const plugins: Plugins = {};
for (let i = 1; i <= 21; i++) {
const plugin: Plugin = new Plugin(
'',
{
manifest_version: i,
id: i.toString(),
name: `Plugin${i}`,
version: '1',
app_min_version: '1' },
'',
() => {},
''
);
plugins[plugin.manifest.id] = plugin;
}
const v = versionInfo(packageInfo, plugins);
const body = '\n';
for (let i = 1; i <= 21; i++) {
body.concat(`\nPlugin${i}: 1`);
}
expect(v.body).toMatch(new RegExp(body));
const message = '\n';
for (let i = 1; i <= 20; i++) {
message.concat(`\nPlugin${i}: 1`);
}
message.concat('\n...');
expect(v.message).toMatch(new RegExp(message));
});
info.mockReset();
});

View File

@@ -1,8 +1,44 @@
import { _ } from './locale';
import Setting from './models/Setting';
import { reg } from './registry';
import { Plugins } from './services/plugins/PluginService';
export default function versionInfo(packageInfo: any) {
interface PluginList {
completeList: string;
summary: string;
}
function getPluginLists(plugins: Plugins): PluginList {
const pluginList = [];
if (Object.keys(plugins).length > 0) {
for (const pluginId in plugins) {
pluginList.push(`${plugins[pluginId].manifest.name}: ${plugins[pluginId].manifest.version}`);
}
}
let completeList = '';
let summary = '';
if (pluginList.length > 0) {
completeList = ['\n', ...pluginList].join('\n');
if (pluginList.length > 20) {
summary = [
'\n',
...[...pluginList].filter((_, index) => index < 20),
'...',
].join('\n');
} else {
summary = completeList;
}
}
return {
completeList,
summary,
};
}
export default function versionInfo(packageInfo: any, plugins: Plugins) {
const p = packageInfo;
let gitInfo = '';
if ('git' in p) {
@@ -32,9 +68,11 @@ export default function versionInfo(packageInfo: any) {
console.info(gitInfo);
}
const pluginList = getPluginLists(plugins);
return {
header: header.join('\n'),
body: body.join('\n'),
message: header.concat(body).join('\n'),
body: body.join('\n').concat(pluginList.completeList),
message: header.concat(body).join('\n').concat(pluginList.summary),
};
}

View File

@@ -19,9 +19,9 @@
"author": "Joplin",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/pdfjs-dist": "2.10.378",
"@types/react": "16.14.34",
"@types/react": "16.14.35",
"@types/react-dom": "18.0.10",
"@types/styled-components": "5.1.25",
"babel-jest": "29.3.1",
@@ -29,7 +29,7 @@
"jest": "29.3.1",
"jest-environment-jsdom": "29.3.1",
"style-loader": "3.3.1",
"ts-jest": "29.0.3",
"ts-jest": "29.0.5",
"ts-loader": "9.4.2",
"typescript": "4.9.4",
"webpack": "5.74.0",

View File

@@ -271,7 +271,7 @@ async function commandVersion() {
}
async function main() {
const scriptName: string = 'plugin-repo-cli';
const scriptName = 'plugin-repo-cli';
const commands: Record<string, Function> = {
build: commandBuild,
@@ -279,8 +279,8 @@ async function main() {
updateRelease: commandUpdateRelease,
};
let selectedCommand: string = '';
let selectedCommandArgs: string = '';
let selectedCommand = '';
let selectedCommandArgs = '';
function setSelectedCommand(name: string, args: any) {
selectedCommand = name;

View File

@@ -28,7 +28,7 @@
},
"devDependencies": {
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"jest": "29.3.1",
"source-map-loader": "4.0.1",

View File

@@ -291,7 +291,7 @@ export default {
// set KaTeX as the renderer for markdown-it-simplemath
const katexInline = function(latex: string) {
katexOptions.displayMode = false;
let outputHtml: string = '';
let outputHtml = '';
try {
outputHtml = renderToStringWithCache(latex, katexOptions);
} catch (error) {
@@ -306,7 +306,7 @@ export default {
const katexBlock = function(latex: string) {
katexOptions.displayMode = true;
let outputHtml: string = '';
let outputHtml = '';
try {
outputHtml = renderToStringWithCache(latex, katexOptions);
} catch (error) {

View File

@@ -18,7 +18,7 @@
"author": "",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"jest": "29.3.1",
"typescript": "4.9.4"

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.10.5",
"version": "2.10.8",
"private": true,
"scripts": {
"start-dev": "yarn run build && JOPLIN_IS_TESTING=1 nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
@@ -36,14 +36,14 @@
"fs-extra": "11.1.0",
"html-entities": "1.4.0",
"jquery": "3.6.3",
"knex": "2.3.0",
"knex": "2.4.2",
"koa": "2.14.1",
"markdown-it": "13.0.1",
"mustache": "4.2.0",
"nanoid": "2.1.11",
"node-cron": "3.0.2",
"node-env-file": "0.1.8",
"nodemailer": "6.8.0",
"nodemailer": "6.9.1",
"nodemon": "2.0.20",
"pg": "8.8.0",
"pretty-bytes": "5.6.0",
@@ -59,22 +59,22 @@
},
"devDependencies": {
"@joplin/tools": "~2.10",
"@rmp135/sql-ts": "1.15.1",
"@rmp135/sql-ts": "1.16.0",
"@types/formidable": "2.0.5",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/jest-expect-message": "1.0.4",
"@types/jsdom": "16.2.15",
"@types/koa": "2.13.5",
"@types/markdown-it": "12.2.3",
"@types/mustache": "4.2.2",
"@types/nodemailer": "6.4.7",
"@types/yargs": "17.0.19",
"@types/yargs": "17.0.20",
"@types/zxcvbn": "4.4.1",
"gulp": "4.0.2",
"jest": "29.3.1",
"jest-expect-message": "1.1.3",
"jsdom": "16.7.0",
"jsdom": "21.0.0",
"node-mocks-http": "1.12.1",
"source-map-support": "0.5.21",
"typescript": "4.9.4"

View File

@@ -11,7 +11,7 @@ interface PackageJson {
const packageJson: PackageJson = require(`${__dirname}/packageInfo.js`);
let runningInDocker_: boolean = false;
let runningInDocker_ = false;
export function runningInDocker(): boolean {
return runningInDocker_;

View File

@@ -1,4 +1,3 @@
import { ModelType } from '@joplin/lib/BaseModel';
import { resourceBlobPath } from '../utils/joplinUtils';
import { Change, ChangeType, Item, Share, ShareType, ShareUserStatus, User, Uuid } from '../services/database/types';
import { unique } from '../utils/array';
@@ -198,20 +197,12 @@ export default class ShareModel extends BaseModel<Share> {
};
const handleCreated = async (change: Change, item: Item, share: Share) => {
// console.info('CREATE ITEM', item);
// console.info('CHANGE', change);
// if (![ModelType.Note, ModelType.Folder, ModelType.Resource].includes(item.jop_type)) return;
if (!item.jop_share_id) return;
const shareUserIds = await this.allShareUserIds(share);
for (const shareUserId of shareUserIds) {
if (shareUserId === change.user_id) continue;
await addUserItem(shareUserId, item.id);
if (item.jop_type === ModelType.Resource) {
// const resourceItem = await this.models().item().loadByName(change.user_id, resourceBlobPath(
}
}
};
@@ -228,7 +219,15 @@ export default class ShareModel extends BaseModel<Share> {
const shareUserIds = await this.allShareUserIds(previousShare);
for (const shareUserId of shareUserIds) {
if (shareUserId === change.user_id) continue;
await removeUserItem(shareUserId, item.id);
try {
await removeUserItem(shareUserId, item.id);
} catch (error) {
if (error.httpCode === ErrorNotFound.httpCode) {
logger.warn('Could not remove a user item because it has already been removed:', error);
} else {
throw error;
}
}
}
}
@@ -299,12 +298,33 @@ export default class ShareModel extends BaseModel<Share> {
for (const change of changes) {
const item = items.find(i => i.id === change.item_id);
if (change.type === ChangeType.Create) {
await handleCreated(change, item, shares.find(s => s.id === item.jop_share_id));
}
// Item associated with the change may have been
// deleted, so take this into account.
if (item) {
// When a folder is unshared, the share object is
// deleted, then all items that were shared get their
// 'share_id' property set to an empty string. This is
// all done client side.
//
// However it means that if a share object is deleted
// but the items are not synced, we'll find items that
// are associated with a share that no longer exists.
// This is fine, but we need to handle it properly
// below, otherwise the share update process will fail.
if (change.type === ChangeType.Update) {
await handleUpdated(change, item, shares.find(s => s.id === item.jop_share_id));
const itemShare = shares.find(s => s.id === item.jop_share_id);
if (change.type === ChangeType.Create) {
if (!itemShare) {
logger.warn(`Found an item (${item.id}) associated with a share that no longer exists (${item.jop_share_id}) - skipping it`);
} else {
await handleCreated(change, item, itemShare);
}
}
if (change.type === ChangeType.Update) {
await handleUpdated(change, item, itemShare);
}
}
// We don't need to handle ChangeType.Delete because when an

View File

@@ -82,10 +82,10 @@ export default class UserItemModel extends BaseModel<UserItem> {
.where('user_items.user_id', '=', userId);
}
public async deleteByUserItem(userId: Uuid, itemId: Uuid): Promise<void> {
public async deleteByUserItem(userId: Uuid, itemId: Uuid, options: UserItemDeleteOptions = null): Promise<void> {
const userItem = await this.byUserAndItemId(userId, itemId);
if (!userItem) throw new ErrorNotFound(`No such user_item: ${userId} / ${itemId}`);
await this.deleteBy({ byUserItem: userItem });
await this.deleteBy({ ...options, byUserItem: userItem });
}
public async deleteByItemIds(itemIds: Uuid[], options: UserItemDeleteOptions = null): Promise<void> {

View File

@@ -22,7 +22,7 @@ export default async function(config: StorageDriverConfig | number, db: DbConnec
...options,
};
let storageId: number = 0;
let storageId = 0;
if (typeof config === 'number') {
storageId = config;

View File

@@ -1,5 +1,5 @@
import { ChangeType, Share, ShareType, ShareUser, ShareUserStatus } from '../../services/database/types';
import { beforeAllDb, afterAllTests, beforeEachDb, createUserAndSession, models, createNote, createFolder, updateItem, createItemTree, makeNoteSerializedBody, updateNote, expectHttpError, createResource } from '../../utils/testing/testUtils';
import { beforeAllDb, afterAllTests, beforeEachDb, createUserAndSession, models, createNote, createFolder, updateItem, createItemTree, makeNoteSerializedBody, updateNote, expectHttpError, createResource, expectNotThrow } from '../../utils/testing/testUtils';
import { postApi, patchApi, getApi, deleteApi } from '../../utils/testing/apiUtils';
import { PaginatedDeltaChanges } from '../../models/ChangeModel';
import { inviteUserToShare, shareFolderWithUser } from '../../utils/testing/shareApiUtils';
@@ -367,6 +367,90 @@ describe('shares.folder', function() {
expect(newChildren.items[0].id).toBe(folderItem1.id);
});
test('should not throw an error if an item is associated with a share that no longer exists', async function() {
const { session: session1 } = await createUserAndSession(1);
const { session: session2 } = await createUserAndSession(2);
const { share } = await shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', {
'000000000000000000000000000000F1': {},
});
await createNote(session1.id, {
id: '00000000000000000000000000000007',
share_id: share.id,
});
await models().share().delete(share.id);
await expectNotThrow(async () => await models().share().updateSharedItems3());
});
test('should not throw an error if a change is associated with an item that no longer exists', async function() {
const { session: session1 } = await createUserAndSession(1);
const { session: session2 } = await createUserAndSession(2);
const { share: share1 } = await shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', {
'000000000000000000000000000000F1': {},
});
const { share: share2 } = await shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F2', {
'000000000000000000000000000000F2': {},
});
const item1 = await createNote(session1.id, {
id: '00000000000000000000000000000007',
share_id: share1.id,
});
await createNote(session1.id, {
id: '00000000000000000000000000000008',
share_id: share2.id,
});
await models().item().delete(item1.id);
await models().share().updateSharedItems3();
await expectNotThrow(async () => await models().share().updateSharedItems3());
});
test('should not throw an error if a user no longer has a user item, and the target share changes', async function() {
const { user: user1, session: session1 } = await createUserAndSession(1);
const { user: user2, session: session2 } = await createUserAndSession(2);
await shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', [
{
id: '000000000000000000000000000000F1',
children: [
{
id: '00000000000000000000000000000001',
},
],
},
]);
const { share: share2 } = await shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F5', [
{
id: '000000000000000000000000000000F5',
children: [],
},
]);
const noteItem = await models().item().loadByJopId(user1.id, '00000000000000000000000000000001');
// Note is moved from a shared folder to a different shared folder
await models().item().saveForUser(user1.id, {
id: noteItem.id,
jop_parent_id: '000000000000000000000000000000F5',
jop_share_id: share2.id,
});
await models().userItem().deleteByUserItem(user2.id, noteItem.id, { recordChanges: false });
await expectNotThrow(async () => await models().share().updateSharedItems3());
});
test('should unshare a deleted item', async function() {
const { user: user1, session: session1 } = await createUserAndSession(1);
const { user: user2, session: session2 } = await createUserAndSession(2);

View File

@@ -24,7 +24,7 @@ interface ResetPasswordFields {
const subRoutes: Record<string, RouteHandler> = {
forgot: async (_path: SubPath, ctx: AppContext) => {
let confirmationMessage: string = '';
let confirmationMessage = '';
if (ctx.method === 'POST') {
const fields = await bodyFields<ForgotPasswordFields>(ctx.req);
@@ -46,7 +46,7 @@ const subRoutes: Record<string, RouteHandler> = {
},
reset: async (_path: SubPath, ctx: AppContext) => {
let successMessage: string = '';
let successMessage = '';
let error: Error = null;
const token = ctx.query.token as string;

View File

@@ -105,7 +105,7 @@ export default class UserDeletionService extends BaseService {
}
let error: any = null;
let success: boolean = true;
let success = true;
try {
await this.models.userDeletion().start(deletion.id);

View File

@@ -54,11 +54,12 @@ export async function createTestUsers(db: DbConnection, config: Config, options:
await dropTables(db);
await migrateLatest(db);
for (let userNum = 1; userNum <= 2; userNum++) {
for (let userNum = 1; userNum <= 3; userNum++) {
await models.user().save({
email: `user${userNum}@example.com`,
password,
full_name: `User ${userNum}`,
account_type: AccountType.Pro,
});
}

View File

@@ -2,7 +2,7 @@ import sqlts from '@rmp135/sql-ts';
require('source-map-support').install();
const dbFilePath: string = `${__dirname}/../../src/services/database/types.ts`;
const dbFilePath = `${__dirname}/../../src/services/database/types.ts`;
const fileReplaceWithinMarker = '// AUTO-GENERATED-TYPES';
@@ -74,7 +74,7 @@ function insertContentIntoFile(filePath: string, markerOpen: string, markerClose
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
let content: string = fs.readFileSync(filePath, 'utf-8');
// [^]* matches any character including new lines
const regex: RegExp = new RegExp(`${markerOpen}[^]*?${markerClose}`);
const regex = new RegExp(`${markerOpen}[^]*?${markerClose}`);
if (!content.match(regex)) throw new Error(`Could not find markers: ${markerOpen}`);
content = content.replace(regex, `${markerOpen}\n${contentToInsert}\n${markerClose}`);
fs.writeFileSync(filePath, content);

View File

@@ -121,11 +121,28 @@ async function main() {
const baseUrl = 'https://api.github.com/repos/laurent22/joplin/releases?page=';
// GitHub release API has been broken for a few years now - a fetch for a
// particular page may or may not return the page. So here we fetch the page
// multiple times until we get it. If we don't get it after that, we can
// assume there's really no page there. Without this hack, we get stuff like
// this, where the changelog is partly cleared, then restored on next
// update:
//
// - https://github.com/laurent22/joplin/commit/907422cefaeff52fe909278e40145812cc0d1303
// - https://github.com/laurent22/joplin/commit/07535a494e5c700adce89835d1fb3dc077600240
const multiFetch = async (url) => {
for (let i = 0; i < 3; i++) {
const response = await fetch(url);
const output = await response.json();
if (output && output.length) return output;
}
return null;
};
let pageNum = 1;
while (true) {
console.info(`Build stats: Page ${pageNum}`);
const response = await fetch(`${baseUrl}${pageNum}`);
const releases = await response.json();
const releases = await multiFetch(`${baseUrl}${pageNum}`);
if (!releases || !releases.length) break;
processReleases(releases);
pageNum++;

View File

@@ -7,6 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: MrKanister <pueblos_spatulas@aleeas.com>\n"
"Language-Team: \n"
"Language: de_DE\n"
@@ -318,6 +320,8 @@ msgstr "Erweiterte Optionen"
msgid ""
"All data, including notes, notebooks and tags will be permanently deleted."
msgstr ""
"Alle Daten, inklusive Notizen, Notizbücher und Schlagwörter werden permanent "
"gelöscht."
#: packages/app-desktop/gui/Sidebar/Sidebar.tsx:484
#: packages/app-mobile/components/screens/notes.js:204
@@ -744,9 +748,8 @@ msgid "Close"
msgstr "Schließen"
#: packages/app-mobile/components/Dropdown.tsx:166
#, fuzzy
msgid "Close dropdown"
msgstr "Fenster schließen"
msgstr "Auswahlliste schließen"
#: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:24
#: packages/app-desktop/gui/MenuBar.tsx:593
@@ -865,7 +868,7 @@ msgstr "Von %s bereitgestellte Inhalte"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:64
msgid "Continue"
msgstr ""
msgstr "Fortfahren"
#: packages/app-mobile/components/screens/Note.tsx:931
msgid "Convert to note"
@@ -971,9 +974,8 @@ msgstr ""
"Der Fehler war: „%s“"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:55
#, fuzzy
msgid "Could not switch profile: %s"
msgstr "Erweiterung konnte nicht installiert werden: %s"
msgstr "Konnte das Profil nicht wechseln: %s"
#: packages/lib/components/EncryptionConfigScreen/utils.ts:220
msgid "Could not upgrade master key: %s"
@@ -990,7 +992,7 @@ msgstr ""
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:28
msgid "Could not verify your identify"
msgstr ""
msgstr "Konnte deine Identität nicht verifizieren."
#: packages/app-desktop/gui/PromptDialog.min.js:235
#: packages/app-desktop/gui/PromptDialog.tsx:260
@@ -1218,9 +1220,8 @@ msgid "Delete plugin \"%s\"?"
msgstr "Erweiterung „%s“ löschen?"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:101
#, fuzzy
msgid "Delete profile \"%s\""
msgstr "Notiz „%s“ löschen?"
msgstr "Lösche Profil \"%s\""
#: packages/app-mobile/components/ScreenHeader.tsx:419
msgid "Delete selected notes"
@@ -1239,9 +1240,8 @@ msgstr ""
"freigegebene Notizbuch."
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:97
#, fuzzy
msgid "Delete this profile?"
msgstr "Sollen diese %d Notizen gelöscht werden?"
msgstr "Dieses Profil löschen?"
#: packages/lib/Synchronizer.ts:186
msgid "Deleted local items: %d."
@@ -1489,9 +1489,8 @@ msgid "Edit notebook"
msgstr "Notizbuch bearbeiten"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:87
#, fuzzy
msgid "Edit profile"
msgstr "Profil exportieren"
msgstr "Profil bearbeiten"
#: packages/app-desktop/commands/editProfileConfig.ts:9
msgid "Edit profile configuration..."
@@ -1584,7 +1583,7 @@ msgstr "Audiospieler aktivieren"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:55
msgid "Enable biometrics authentication?"
msgstr ""
msgstr "Biometrische Authentifizierung aktivieren?"
#: packages/lib/models/Setting.ts:1147
msgid "Enable deflist syntax"
@@ -2548,9 +2547,8 @@ msgid "Manage multiple users"
msgstr "Verwalte mehrere Nutzer"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:611
#, fuzzy
msgid "Manage profiles"
msgstr "Profil aktualisieren"
msgstr "Profile verwalten"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:320
msgid "Manage your plugins"
@@ -2814,9 +2812,8 @@ msgid "Not generated"
msgstr "Nicht erzeugt"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:64
#, fuzzy
msgid "Not now"
msgstr "Mach es jetzt"
msgstr "Nicht jetzt"
#: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:110
#: packages/server/src/models/UserModel.ts:215
@@ -3290,9 +3287,8 @@ msgid "Profile"
msgstr "Profil"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:95
#, fuzzy
msgid "Profile name"
msgstr "Profilname:"
msgstr "Profilname"
#: packages/app-desktop/gui/MainScreen/commands/addProfile.ts:17
msgid "Profile name:"
@@ -3303,9 +3299,8 @@ msgid "Profile Version: %s"
msgstr "Profil-Version: %s"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:155
#, fuzzy
msgid "Profiles"
msgstr "Profil"
msgstr "Profile"
#: packages/app-mobile/components/screens/Note.tsx:945
msgid "Properties"
@@ -4136,6 +4131,8 @@ msgid ""
"The active profile cannot be deleted. Switch to a different profile and try "
"again."
msgstr ""
"Das aktive Profil kann nicht gelöscht werden. Bitte wechsle in ein anderes "
"Profil und versuche es erneut."
#: packages/app-desktop/bridge.ts:280
msgid ""
@@ -4209,7 +4206,7 @@ msgstr ""
#: packages/lib/services/profileConfig/index.ts:104
msgid "The default profile cannot be deleted"
msgstr ""
msgstr "Das Standardprofil kann nicht gelöscht werden."
#: packages/lib/models/Setting.ts:1367
msgid ""
@@ -4371,7 +4368,7 @@ msgstr ""
#: packages/app-mobile/components/screens/ConfigScreen.tsx:341
msgid "There are unsaved changes."
msgstr ""
msgstr "Es gibt ungespeicherte Änderungen."
#: packages/app-desktop/gui/NoteList/NoteList.tsx:487
msgid ""
@@ -4602,6 +4599,8 @@ msgid ""
"To switch the profile, the app is going to close and you will need to "
"restart it."
msgstr ""
"Um das Profil zu wechseln, wird die App beendet und muss von dir neu "
"gestartet werden."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:651
msgid ""
@@ -4840,7 +4839,7 @@ msgstr "Nutzung: %s"
#: packages/lib/models/Setting.ts:1629
msgid "Use biometrics to secure access to the app"
msgstr ""
msgstr "Verwende Biometrie, um den Zugriff zu der App abzusichern."
#: packages/app-cli/app/command-ls.js:32 packages/app-cli/app/command-tag.js:18
msgid ""
@@ -4881,6 +4880,8 @@ msgid ""
"Use your biometrics to secure access to your application. You can always set "
"it up later in Settings."
msgstr ""
"Verwende Biometrie, um den Zugriff zu deiner Anwendung abzusichern. Du "
"kannst sie jederzeit in den Einstellungen einrichten."
#: packages/lib/models/Setting.ts:1245
msgid ""
@@ -4917,7 +4918,7 @@ msgstr "Gültig"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:24
msgid "Verify your identity"
msgstr ""
msgstr "Verifiziere deine Identität"
#: packages/app-desktop/gui/NoteList/NoteList.tsx:167
msgid "View"

View File

@@ -7,6 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: X3NO <X3NO@disroot.org>\n"
"Language-Team: \n"
"Language: pl_PL\n"
@@ -15,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 "
"|| n%100>14) ? 1 : 2);\n"
"X-Generator: Poedit 3.1\n"
"X-Generator: Poedit 3.2.2\n"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:657
msgid "- Camera: to allow taking a picture and attaching it to a note."
@@ -95,7 +97,7 @@ msgstr "%d dni"
#: packages/lib/utils/joplinCloud.ts:136 packages/lib/utils/joplinCloud.ts:137
#: packages/lib/utils/joplinCloud.ts:138
msgid "%d GB"
msgstr ""
msgstr "%d GB"
#: packages/lib/utils/joplinCloud.ts:133 packages/lib/utils/joplinCloud.ts:134
#: packages/lib/utils/joplinCloud.ts:135
@@ -113,13 +115,12 @@ msgstr "%d godzin"
#: packages/lib/utils/joplinCloud.ts:124 packages/lib/utils/joplinCloud.ts:125
#: packages/lib/utils/joplinCloud.ts:126
msgid "%d MB"
msgstr ""
msgstr "%d MB"
#: packages/lib/utils/joplinCloud.ts:121 packages/lib/utils/joplinCloud.ts:122
#: packages/lib/utils/joplinCloud.ts:123
#, fuzzy
msgid "%d MB per note or attachment"
msgstr "Załączniki notatki"
msgstr "%d MB na notatkę lub załącznik"
#: packages/lib/models/Setting.ts:1343 packages/lib/models/Setting.ts:1344
#: packages/lib/models/Setting.ts:1345
@@ -257,7 +258,7 @@ msgstr "Akceptuj"
#: packages/server/src/routes/admin/users.ts:138
msgid "Account"
msgstr ""
msgstr "Konto"
#: packages/app-desktop/gui/ResourceScreen.tsx:95
msgid "Action"
@@ -282,9 +283,8 @@ msgid "Add body"
msgstr "Dodaj tekst"
#: packages/app-mobile/components/ActionButton.tsx:59
#, fuzzy
msgid "Add new"
msgstr "Dodaj tytuł"
msgstr "Dodaj nowy"
#: packages/app-desktop/gui/MainScreen/commands/setTags.ts:38
msgid "Add or remove tags:"
@@ -323,6 +323,7 @@ msgstr "Narzędzia zaawansowane"
msgid ""
"All data, including notes, notebooks and tags will be permanently deleted."
msgstr ""
"Wszystkie dane, włączając notatki, notatniki i tagi zostaną trwale usunięte."
#: packages/app-desktop/gui/Sidebar/Sidebar.tsx:484
#: packages/app-mobile/components/screens/notes.js:204
@@ -387,9 +388,8 @@ msgid "Aritim Dark"
msgstr "Aritim ciemny"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:206
#, fuzzy
msgid "Attach"
msgstr "Załącz..."
msgstr "Załącz"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:53
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:591
@@ -484,7 +484,7 @@ msgstr "Cofnij"
#: packages/lib/utils/joplinCloud.ts:294
msgid "Basic"
msgstr ""
msgstr "Podstawowy"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:33
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:132
@@ -852,7 +852,7 @@ msgstr "Treść dostarczona przez %s"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:64
msgid "Continue"
msgstr ""
msgstr "Kontynuuj"
#: packages/app-mobile/components/screens/Note.tsx:931
msgid "Convert to note"
@@ -976,7 +976,7 @@ msgstr ""
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:28
msgid "Could not verify your identify"
msgstr ""
msgstr "Nie można zweryfikować tożsamości"
#: packages/app-desktop/gui/PromptDialog.min.js:235
#: packages/app-desktop/gui/PromptDialog.tsx:260
@@ -1473,9 +1473,8 @@ msgid "Edit notebook"
msgstr "Edytuj notatnik"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:87
#, fuzzy
msgid "Edit profile"
msgstr "Eksportuj profil"
msgstr "Edytuj profil"
#: packages/app-desktop/commands/editProfileConfig.ts:9
msgid "Edit profile configuration..."
@@ -1567,7 +1566,7 @@ msgstr "Aktywuj odtwarzacz muzyki"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:55
msgid "Enable biometrics authentication?"
msgstr ""
msgstr "Włączyć uwierzytelnianie biometryczne?"
#: packages/lib/models/Setting.ts:1147
msgid "Enable deflist syntax"
@@ -1838,7 +1837,7 @@ msgstr "Filtrowanie znaczników"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:218
msgid "Find and replace"
msgstr ""
msgstr "Znajdź i zamień"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:250
#, fuzzy
@@ -4575,9 +4574,8 @@ msgid "to-do"
msgstr "zadanie"
#: packages/app-mobile/components/note-item.js:143
#, fuzzy
msgid "to-do: %s"
msgstr "zadanie"
msgstr "zadanie: %s"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:112
msgid "Toggle comment"

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,12 @@
"@joplin/lib": "~2.10",
"@joplin/renderer": "~2.10",
"@types/node-fetch": "2.6.2",
"@types/yargs": "17.0.19",
"@types/yargs": "17.0.20",
"dayjs": "1.11.7",
"execa": "4.1.0",
"fs-extra": "11.1.0",
"gettext-parser": "6.0.0",
"glob": "8.0.3",
"glob": "8.1.0",
"markdown-it": "13.0.1",
"md5-file": "5.0.0",
"moment": "2.29.4",
@@ -43,9 +43,9 @@
},
"devDependencies": {
"@joplin/fork-htmlparser2": "^4.1.41",
"@rmp135/sql-ts": "1.15.1",
"@rmp135/sql-ts": "1.16.0",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/mustache": "4.2.2",
"@types/node": "18.11.18",
"gettext-extractor": "3.6.1",

View File

@@ -82,12 +82,11 @@ async function insertChangelog(tag: string, changelogPath: string, changelog: st
export function releaseFinalGitCommands(appName: string, newVersion: string, newTag: string): string {
const finalCmds = [
'git pull',
'git add -A',
`git commit -m "${appName} ${newVersion}"`,
`git tag "${newTag}"`,
'git push',
'git push --tags',
`git push origin refs/tags/${newTag}`,
];
return finalCmds.join(' && ');

View File

@@ -18,7 +18,7 @@ const readProp = (line: string): string[] => {
export const stripOffFrontMatter = (md: string): MarkdownAndFrontMatter => {
if (md.indexOf('---') !== 0) return { doc: md };
let state: string = 'start';
let state = 'start';
const lines = md.split('\n');
const docLines: string[] = [];

View File

@@ -13,7 +13,7 @@
"dependencies": {
"css": "3.0.0",
"html-entities": "1.4.0",
"jsdom": "15.2.1"
"jsdom": "21.0.0"
},
"devDependencies": {
"browserify": "14.5.0",

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,18 @@
# Joplin Server Changelog
## [server-v2.10.8](https://github.com/laurent22/joplin/releases/tag/server-v2.10.8) - 2023-02-09T18:24:27Z
- Fixed: Fixed sharing issue when a user no longer has a user item associated with their account (293f621)
## [server-v2.10.7](https://github.com/laurent22/joplin/releases/tag/server-v2.10.7) - 2023-02-09T15:57:31Z
- Fixed: Fixed sharing issue for changes that are associated with deleted items (05c17fb)
## [server-v2.10.6](https://github.com/laurent22/joplin/releases/tag/server-v2.10.6) - 2023-02-06T19:01:36Z
- New: Add in ability to use Postgres connection string in configuration (#6836 by [@halkeye](https://github.com/halkeye))
- Fixed: Fixed issue when an item is associated with a share that no longer exists (ba5f0bc)
## [server-v2.10.5](https://github.com/laurent22/joplin/releases/tag/server-v2.10.5) - 2022-12-26T12:09:13Z
- Fixed: Fixed regression that would prevent styles from being loaded in published notes (#7525)

View File

@@ -175,6 +175,94 @@
"created_at": "2023-01-19T20:42:01Z",
"repoId": 79162682,
"pullRequestNo": 7637
},
{
"name": "Theta-Dev",
"id": 19636565,
"comment_id": 1399629078,
"created_at": "2023-01-22T22:42:51Z",
"repoId": 79162682,
"pullRequestNo": 7653
},
{
"name": "trevor-james-nangosha",
"id": 75243400,
"comment_id": 1401905502,
"created_at": "2023-01-24T12:56:21Z",
"repoId": 79162682,
"pullRequestNo": 7663
},
{
"name": "krote5k",
"id": 40627944,
"comment_id": 1403569652,
"created_at": "2023-01-25T12:59:29Z",
"repoId": 79162682,
"pullRequestNo": 7665
},
{
"name": "majsterkovic",
"id": 29355048,
"comment_id": 1404244043,
"created_at": "2023-01-25T21:26:07Z",
"repoId": 79162682,
"pullRequestNo": 7668
},
{
"name": "carlosngo",
"id": 36130773,
"comment_id": 1407364680,
"created_at": "2023-01-28T10:06:46Z",
"repoId": 79162682,
"pullRequestNo": 7681
},
{
"name": "tessus",
"id": 223439,
"comment_id": 1409554180,
"created_at": "2023-01-31T00:13:01Z",
"repoId": 79162682,
"pullRequestNo": 7697
},
{
"name": "deepampriyadarshi",
"id": 65447610,
"comment_id": 1412201437,
"created_at": "2023-02-01T15:00:37Z",
"repoId": 79162682,
"pullRequestNo": 7708
},
{
"name": "abhiippili",
"id": 89311258,
"comment_id": 1412417388,
"created_at": "2023-02-01T17:12:03Z",
"repoId": 79162682,
"pullRequestNo": 7709
},
{
"name": "julien-me",
"id": 32807437,
"comment_id": 1415829940,
"created_at": "2023-02-03T12:51:01Z",
"repoId": 79162682,
"pullRequestNo": 7711
},
{
"name": "TaoK",
"id": 531704,
"comment_id": 1418164148,
"created_at": "2023-02-05T17:06:14Z",
"repoId": 79162682,
"pullRequestNo": 7729
},
{
"name": "rakeshhotker",
"id": 55056403,
"comment_id": 1422063650,
"created_at": "2023-02-08T06:07:07Z",
"repoId": 79162682,
"pullRequestNo": 7745
}
]
}

160
readme/gsoc2023/ideas.md Normal file
View File

@@ -0,0 +1,160 @@
# GSoC 2023 Ideas
2023 is Joplin third round at Google Summer of Code. Detailed information on how to get involved and apply are given in the [general Summer of Code introduction](https://joplinapp.org/gsoc2023/)
**These are all proposals! We are open to new ideas you might have!!** Do you have an awesome idea you want to work on with Joplin but that is not among the ideas below? That's cool. We love that! But please do us a favour: Get in touch with a mentor early on and make sure your project is realistic and within the scope of Joplin. This year's themes are:
- **Plugins** - leverage the Joplin plugin API to add new functionalities
- **External apps** - leverage the Joplin public API to create external applications
- **Independent modules** - create self-contains modules within the core application
- And you are welcome to suggest your own ideas.
# Information for Contributors
These ideas were contributed by our developers and users. They are sometimes vague or incomplete. If you wish to submit a proposal based on these ideas, you are urged to contact the developers and find out more about the particular suggestion you're looking at.
Becoming accepted as a Google Summer of Code contributor is quite competitive. Accepted contributors typically have thoroughly researched the technologies of their proposed project and have been in frequent contact with potential mentors. **Simply copying and pasting an idea here will not work.** On the other hand, creating a completely new idea without first consulting potential mentors rarely works.
# List of ideas
## 1. Plugin system on mobile
The plugin system is currently available on desktop and CLI. We believe it could work on mobile too although some work will have to be done to make the plugin API compatible, as well as add a mechanism to load plugins.
Expected Outcome: Allow loading and running plugins on mobile
Difficulty Level: High
Skills Required: TypeScript, React Native
Potential Mentor(s):
Expected size of project: 350 hours
## 2. Seamless desktop application updates
The desktop application currently supports automatic updates, however the process is not particularly smooth: the user is presented with a modal dialog, where they need to click "Download" and that opens the default browser to download the file. Then they need to run this file and go through the installer.
We would like to make this process smoother:
- The installer should be automatically downloaded in the background
- It should then install the app automatically when the next time the app is started
- And this should work at least on Windows and macOS (Linux may be special due to the different distribution methods)
Expected Outcome: The app shall inform the user that an update is available. If an update shall be applied, the installer runs the update process fully automatically in the background during the next startup. It shall be explored if a live update is feasible and how conflicts can be resolved as used files are to be replaced.
Difficulty Level: Medium
Skills Required: TypeScript, React. Some knowledge of Electron and electron-builder.
Potential Mentor(s):
Expected size of project: 175 hours
## 3. Improve PDF export
Joplin uses Chrome's built-in print to PDF function which is very limited. This can be improved by using a 3rd party library to convert notes to PDF. Applies to desktop and CLI versions.
Potential benefits:
* Export multiple notes as a single PDF
* Embedding attachments (see https://github.com/laurent22/joplin/issues/5943)
* Delay export until the note is fully rendered (https://discourse.joplinapp.org/t/ability-to-delay-pdf-export-to-allow-plugins-to-render/22159)
Expected Outcome: PDF export no longer relies on Chrome print to pdf
Difficulty level: Medium
Skills Required: Typescript, Javascript.
Potential Mentor(s):
Expected size of project: 350 hours
## 4. Desktop application integration testing
The desktop app front end has a few unit tests to verify things like React hooks and certain utility functions. However we currently have no integration testing to verify for example that a change in one component didn't break something in another component. This project would be about setting up this integration testing for the desktop app. You would do the setup and probably also write a few tests to demonstrate that it's working as expected. More info at https://www.electronjs.org/docs/latest/tutorial/automated-testing
Expected Outcome: The student will have a good understanding on how to setup automated testing of the desktop app, and will have implemented automated testing for at least a subset of the application (e.g. Markdown editor and WYSIWYG editor)
Difficulty Level: High
Skills Required: TypeScript, JavaScript, Electron.
Potential Mentor(s):
Expected size of project: 350 hours
## 5. OCR plugin
It is possible to add support for OCR content in Joplin via the [Tesseract library](http://tesseract.projectnaptha.com/). A first step would be to assess the feasibility of this project by integrating the lib in the desktop app and trying to OCR an image. OCR support should be implemented as a service of the desktop app. It would extract the text from the images, and append the content as plain text to the notes.
Expected Outcome: A plugin for the desktop app that extract text from images and attach it to the note.
Difficulty Level: High
Skills Required: JavaScript, Image processing
Potential Mentor(s):
## 6. Voice to text on mobile
Add support for voice to text on mobile.
Expected Outcome: Open a note, select the "Voice to text" feature, and start recording. That should automatically convert the audio to text and add it to the note.
Difficulty Level: High
Skills Required: JavaScript, React Native
Potential Mentor(s):
## 7. PDF annotations
We would like to add annotation support to the beta PDF viewer on desktop. The annotation tools should be similar to what's in Apple Preview for instance - ability to draw over a PDF, to add text boxes, to draw lines and arrow, etc. These annotations must be saved to the file.
Expected Outcome: Add annotations to a PDF file
Difficulty Level: High
Skills Required: JavaScript
Potential Mentor(s):
## 8. Plugin inspector
Electron provides an API that allows inspecting any sub-process it creates. We can use that to monitor the performance of each plugin - how much CPU they use, how much memory, etc. We would also like to display an alert in the app if a plugin is using too much resources over a long period of time.
Expected Outcome: A module that interacts with the Electron API and gather information about the plugin processes. A window that displays the list of plugin along with CPU and memory information. An alert when a plugin uses too much resources.
Difficulty Level: High
Skills Required: JavaScript, Electron
Potential Mentor(s):
## 9. Template insertion tool
Joplinc can store general templates as notes that can be used in various context. For example, it could have email templates that could be inserted into a Thunderbird email. Or code snippets that could be inserted into a text editor. The workflow will be as follow
- User presses a global shortcut, for example Ctrl+Alt+T, from any text editor (email client, code editor, etc.)
- A floating window is opened, from which the user can pick a note
- Once a note is selected, its content is inserted into the text editor
Expected Outcome: This can developed as an external application or possibly as part of the core application, but some changes will have to be made to allow this. It needs to work at least on Windows and macOS.
Difficulty Level: High
Skills Required: JavaScript, Windows/macOS programming
Potential Mentor(s):
# More info
- Make sure you read the [Joplin Google Summer of Code Introduction](https://joplinapp.org/gsoc2023/)
- To build the application, please read [BUILD.md](https://github.com/laurent22/joplin/blob/dev/BUILD.md)
- And before creating a pull request, please read the [pull request guidelines](https://joplinapp.org/gsoc2023/pull_request_guidelines/)

165
readme/gsoc2023/index.md Normal file
View File

@@ -0,0 +1,165 @@
# Google Summer of Code 2023
Joplin has a young but well proven history. All contributors, Joplin users and developers are welcome to participate in the hopefully third year Summer of Code program with Joplin. This year the main themes will be:
- **Mobile and tablet development** - we want to improve the mobile/tablet application on iOS and Android.
- **Plugin and external apps** - leverage the Joplin API to create plugins and external apps.
- And you are welcome to suggest your own ideas.
Mentors, administrators and contributors: read [Summer of Code](https://developers.google.com/open-source/gsoc) occasionally. Also read the [Summer of Code FAQ](https://developers.google.com/open-source/gsoc/faq).
**Please read this page carefully as most likely it will have all the answers to the questions you might have, such as how to build the app, how to contribute and what are the rules for submitting a pull request.**
All participants will need a Google account in order to join the program. So, save time and create one now. In addition, all participants need to join the [Joplin Forum](https://discourse.joplinapp.org).
## How to contribute
We suggest you read carefully these important documents and bookmark the links as you will need to refer to them throughout GSoC:
- [How to submit a pull request for GSoC](https://joplinapp.org/gsoc2023/pull_request_guidelines/)
- [How to build the apps](https://github.com/laurent22/joplin/blob/dev/BUILD.md)
- [How to contribute](https://github.com/laurent22/joplin/blob/dev/CONTRIBUTING.md)
## Programming Language
- Any new application or plugin should be done using TypeScript.
- For web publishing, please use WebPack.
- For UI, we use React/Redux. Make sure you use React Hooks when creating new components.
- For styling, we use SASS.
In general, all applications share the same back-end written in TypeScript or JavaScript (Node.js), with Redux for state management. The back-end runs locally.
The desktop GUI, as listed on the [Joplin's website](https://joplinapp.org/help/#installation) is done using Electron and React.
The mobile app is done using React Native.
Submissions and ideas for projects in any other language should specifically mention the choice.
## Instructions for contributors
Contributors wishing to participate in Summer of Code must realize, that this is an important professional opportunity. You will be required to produce applicable and readable code for Joplin in 3 months. Your mentors, will dedicate a portion of their time to mentoring you. Therefore, we seek candidates who are committed to helping Joplin and its community long-term and are willing to both do quality work, and be proactive in communicating with your mentor(s).
You don't have to be a proven developer - in fact, this whole program is meant to facilitate joining Joplin and other Open Source communities. However, experience in coding and/or experience with the above mentioned programming languages and the applications is welcome.
You should start learning the components that you plan on working on before the start date. Support can be found in the forum and on our dedicated discourse channel. You should plan to communicate with your team several times per week, and formally report progress and plans weekly. You are free to choose the format, it can be a sophisticated online document or simple continuous blog on GitHub.
Contributors who neglect active communication will be failed!
## How to create your first pull request
Before you can be accepted as a contributor we expect you to write some code and link that work on your proposal. As a first pull request, we suggest one of the following:
- Fix a [high priority](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is:open+is:issue+label:bug+label:high) or [medium priority](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is:open+is:issue+label:bug+label:medium) bug. This is something we highly value and is a good way to get a deep understanding of certain parts of the codebase.
- Alternatively you may browse the [GitHub Issues](https://github.com/laurent22/joplin/issues) to find something that can be worked on. Note that this is a difficult way to get a pull request in, so make sure the issue you choose has a very clear technical spec. If we need to discuss how it should work or what it should do in the pull request, it means there was no consensus for this feature, and we are likely to close the pull request.
- Please **do not submit a pull request just to fix some typo**.
Before submitting a pull request, please make sure you read the [pull request guidelines for GSoC 2023](https://joplinapp.org/gsoc2023/pull_request_guidelines/).
## General instructions
First of all, please read the above referenced resources and the [GSoC FAQ](https://developers.google.com/open-source/gsoc/faq). Pay special attention to the **Eligibility** section of the FAQ.
## Recommended steps
1. Join the [Joplin Forum](https://discourse.joplinapp.org), introduce yourself in a structured manner, share your GitHub username, and meet your fellow developers in the [GSoC category](https://discourse.joplinapp.org/c/gsoc). The subject of the topic shall contain your username, e.g. _Introducing \<username>_.
2. Read Contributor proposal guidelines and the [GSoC Contributor/Student Guide](https://google.github.io/gsocguides/student/)
3. Take a look at the [list of ideas](https://joplinapp.org/gsoc2023/ideas/). You can have you own idea added by posting it in the [Features category](https://discourse.joplinapp.org/c/features)
4. Come up with project that you're interested in and discuss it in [Features category](https://discourse.joplinapp.org/c/features)
5. Write a first draft and get someone to review it
6. Remember: you must link to work such as commits in your proposal. A private place will be created within the forum for that purposes.
7. Read [How to write a kickass proposal for GSoC](http://teom.org/blog/kde/how-to-write-a-kick-ass-proposal-for-google-summer-of-code/)
8. Submit proposal using [Google's web interface](https://summerofcode.withgoogle.com/) ahead of the deadline
9. Submit proof of enrolment well ahead of the deadline
Coming up with an interesting idea is probably the most difficult part. It should be something interesting for Joplin, for Open Source in general and for you. And it must be something that you can realistically achieve in the time available to you.
A good start is finding out what the most pressing issues are in the projects in which you are interested. Join the forum and subscribe to GitHub repository for that project or go into its discourse channel: meet developers and your potential mentor, as well as start learning the code-base. We recommend strongly getting involved in advance of the beginning of GSoC, and we will look favourably on applications from contributors who have already started to act like Open Source developers.
## Contributor proposal guidelines
A project proposal is what you will be judged upon. Write a clear proposal on what you plan to do, the scope of your project, and why we should choose you to do it. Proposals are the basis of the GSoC projects and therefore one of the most important things to do well. The proposal is not only the basis of our decision of which contributor to choose, it has also an effect on Google's decision as to how many contributor slots are assigned to Joplin.
Below is the application template:
> **Introduction**
>
> Every software project should solve a problem. Before offering the solution (your Google Summer of Code project), you should first define the problem. What’s the current state of things? What’s the issue you wish to solve and why? Then you should conclude with a sentence or two about your solution. Include links to discussions, features, or bugs that describe the problem further if necessary.
>
> **Project goals**
>
> Be short and to the point, and perhaps format it as a list. Propose a clear list of deliverables, explaining exactly what you promise to do and what you do not plan to do. “Future developments” can be mentioned, but your promise for the Google Summer of Code term is what counts.
>
> **Implementation**
>
> Be detailed. Describe what you plan to do as a solution for the problem you defined above. Include technical details, showing that you understand the technology. Illustrate key technical elements of your proposed solution in reasonable detail. Include writing unit tests throughout the coding period, as well as code documentation. These critical elements cannot be left to the last few weeks of the program. If user documentation will be required, or apidox, etc. these should be written during each week, not at the end.
>
> **Timeline**
>
> Show that you understand the problem, have a solution, have also broken it down into manageable parts, and that you have a realistic plan on how to accomplish your goal. Here you set expectations, so don’t make promises you can’t keep. A modest, realistic and detailed timeline is better than promising the impossible.
>
> If you have other commitments during GSoC, such as a job, vacation, exams, internship, seminars, or papers to write, disclose them here. GSoC should be treated like a full-time job, and we will expect approximately 40 hours of work per week. *If you have conflicts, explain how you will work around them.* If you are found to have conflicts which you did not disclose, you may be failed.
>
> Open and clear communication is of utmost importance. **Include your plans for communication in your proposal; daily if possible.** You will need to initiate weekly formal communication such as a blog post on to be agreed placed. Lack of communication will result in you being failed.
>
> **About me**
>
> Provide your contact information (IRC nick, email, IM, phone) and write a few sentences about you and why you think you are the best for this job. **Prior contributions to Joplin are required; list your commits.** Name people (other developers, students, professors) who can act as a reference for you. Mention your field of study if necessary. Now is the time to join the relevant irc/telegram channels, mail lists and blog feeds. We want you to be a part of our community, not just contribute your code.
>
> *Tell us if you are submitting proposals to other organizations, and whether or not you would choose Joplin if given the choice.*
>
> *Other things to think about:*
>
> - Are you comfortable working independently under a supervisor or mentor who is several thousand miles away, and perhaps 12 time zones away? How will you work with your mentor to track your work? Have you worked in this style before?
>
> - If your native language is not English, are you comfortable working closely with a supervisor whose native language is English? What is your native language, as that may help us find a mentor who has the same native language?
>
> - After you have written your proposal, you should get it reviewed. Do not rely on the Joplin mentors to do it for you via the web interface, although we will try to comment on every proposal. It is wise to ask a colleague or a developer to critique your proposal. Clarity and completeness are important.
## Hints
**Submit your proposal early**: early submissions get more attention from developers because that they have more time to read them. The more people see your proposal, the more it will be discussed.
**Do not leave it all to the last minute**: while it is Google that is operating the webserver, it would be wise to expect a last-minute overload on the server. So, be sure you send your application and proof of enrolment before the final rush. Also, applications submitted very late will get the least attention from mentors, so you may get a lower vote because of that. Submitting a draft early will give time for feedback from prospective mentors.
**Keep it simple**: Be concise and precise. Provide a clear, descriptive title. "My Project" is the worst possible title!
**Know what you are talking about**: Do not submit proposals that cannot be accomplished over a summer or that are not related to Joplin. If your idea is unusual, be sure to explain why you have chosen Joplin to be your mentoring organization.
There could be exceptional reason to accept proposal what cannot be finished over the summer if either it is clearly recognisable that there will be commitment beyond the summer period or the project can be well separated in sub-project. If you want to go that way, your proposal must be very easy readable to allow us to evaluate the changes of a project going through several coding programs.
**Aim wide**: submit more than one proposal. You are allowed to submit to another organisation as well. If you do submit more than one proposal, tell us that and which proposal you would choose, if both were selected. Former students would advise you to do one or two kick-ass proposals rather than trying to do three.
## Accepted Contributors
Your primary responsibility is finishing your project under the guidance of your mentors. To do that, you must submit code regularly and stay in frequent and effective communication with your mentors and team. To pass the evaluations, you must do both the communication **and** the coding plus documentation.
All contributors will create a report page by tool up to their choice. Keep this up-to-date, as this is one of our primary evaluation tools.
## Instructions for mentors
### Ideas
If you're a Joplin developer or motivated user and you wish to participate in Summer of Code, make a proposal in the the [Features category of the Joplin Forum](https://discourse.joplinapp.org/c/features), based what your Joplin project needs.
If you wish to mentor, please read the [GSoC Mentor Guide](https://google.github.io/gsocguides/mentor/org-application) and the [Summer of Code FAQ](https://developers.google.com/open-source/gsoc/faq#general). Also, please contact the [staff](https://discourse.joplinapp.org/g/staff) and get the go-ahead from them before editing the ideas page, adding your idea.
Your idea proposal should be a brief description of what the project is, what the desired goals would be, what the contributor should know and an email address for contact. Contributors are not required to follow your idea to the letter, so regard your proposal as inspiration for them.
### Mentoring
Anyone developer can be a mentor if you meet the GSoC eligibility requirements. We will potentially assign a contributor to you who has never worked on such a large project and will need some help. Make sure you're up for the task. Mentoring takes time, and lots and lots of communication.
Before subscribing yourself as a mentor, please make sure that the [staff](https://discourse.joplinapp.org/g/staff) is aware of that. Ask them to send the Summer of Code Administrators an email confirming your involvement in the team. This is just a formality to make sure you are a real person we can trust; the administrators cannot know all active developers by their Google account ID. Then drop us a message in the forum.
Prospective mentors should read the [mentoring guide](http://www.booki.cc/gsoc-mentoring). Also, Federico Mena-Quintero has written some helpful information based on his experiences in previous years. [His HOWTO](https://people.gnome.org/~federico/docs/summer-of-code-mentoring-howto/index.html) has some useful suggestions for anyone planning to mentor this year.
You will subscribe to the relevant tags in the forum to discuss ideas. You will need to read the proposals as they come in, and vote on the proposals. Daily communication is required with your contributor during the Community Bonding period, and multiple times per week during the coding period.
Finally, know that we will never assign you to a project you do not want to work on. We will not assign you more projects than you can/want to take on either. And you will have a backup mentor, just in case something unforeseen takes place.
## Ideas
Please see below for a list of project ideas:
https://joplinapp.org/gsoc2023/ideas/

View File

@@ -0,0 +1,31 @@
# Pull request guidelines
Due to our limited resources and in order to give everyone a chance to submit a pull request, we have put restrictions in place this year. If you want to submit a pull request, please take into account the following rules:
0. In general please only work on issues that have been triaged - those are issues with labels such as "high", "medium" or "enhancement". It means an admin looked at it and added it to the backlog.
1. Bug fixes are always welcome. Start by reviewing the list of bugs with [high priority](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abug+label%3Ahigh) or [medium priority](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abug+label%3Amedium).
2. Alternatively you may look at the [Good first issues](https://github.com/laurent22/joplin/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
3. Also check the backlog of possible [feature requests](https://github.com/laurent22/joplin/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement). Some of those are complex and not a good fit for a first pull request, but others are more simple so you might want to consider these.
4. Finally you may implement a plugin in your own repository. Note that we will look at your code, so make sure your choose something not too trivial, so that you can really showcase your skills. Please announce your plugin on the forum, in the [#plugins category](https://discourse.joplinapp.org/c/development/plugins/18).
1. You may not work on issues created by yourself (or your friends). It is likely we will close such issues.
2. Each contributor **may only create one pull request at a time**. Once your pull request has been merged, you can post a second one. We have this rule in place due to our limited resources - if everyone was allowed to post multiple pull requests we will not be able to review them properly. It is also better for you because you only need to care about one PR - so spend time making sure it is as good as it can be - make sure it works well, has test units, documentation and screenshots (if relevant).
3. If the pull request has serious issues, or would require a significant rewrite to be acceptable, we might close it and you will not be allowed to open a new one. So **please be careful when posting a PR**.
4. **If you are borrowing code, please disclose it**. It is fine and sometimes even recommended to borrow code, but we need to know about it to assess your work.
5. **All pull request must have test units**. In some cases it might be almost impossible to add such tests (for example integration tests), but for anything else we insist on having them, and we may close the pull request if we see they could have been added but weren't. If you don't know how to add test units, please ask on the forum or Discord. If really it's not possible to add tests, we'll let you know at this point. Also please check again the [Automated Tests](https://github.com/laurent22/joplin/blob/dev/CONTRIBUTING.md#automated-tests) documentation for more information.
6. **No Work In Progress**. ONLY completed and working pull requests, and with test units, will be accepted. A WIP would fall under rule 3 and be closed immediately.
7. Please **do not `@mention` contributors and mentors and do not ask for pull request reviews**. Sometimes it takes time before we can review your pull request or answer your questions but we'll get to it sooner or later. `@mentioning` someone just adds to the pile of notifications we get and it won't make us look at your issue faster.
8. **Do not force push**. If you make changes to your pull request, please simply add a new commit as that makes it easy for us to review your new changes. If you force push, we'll have to review everything from the beginning.
These rules we hope are fair to everyone, to contributors and maintainers, however if something is unclear or you have any question about them, please let us know!

View File

@@ -20,7 +20,7 @@ Whenever the app is built, this root style.scss file is compiled and a style.css
## CSS class framework
We use [rscss](https://rscss.io/index.html) to structure the components and classes. The main things to remember are:
We use [rscss](https://ricostacruz.com/rscss/index.html) to structure the components and classes. The main things to remember are:
- Components should be named with **at least two words**. eg `.search-form`
@@ -75,4 +75,4 @@ Do **NOT** do this:
The goal of this approach is to specifically target the elements that you need and nothing else. Over the long term is makes managing the CSS easier.
For more details, see the [RSCSS documentation](https://rscss.io/index.html).
For more details, see the [RSCSS documentation](https://ricostacruz.com/rscss/index.html).

View File

@@ -1,19 +1,285 @@
---
updated: 2023-01-15T00:44:00Z
updated: 2023-02-06T10:17:42Z
---
# Joplin statistics
| Name | Value |
| ----- | ----- |
| Total Windows downloads | 0 |
| Total macOs downloads | 0 |
| Total Linux downloads | 0 |
| Windows % | NaN% |
| macOS % | NaN% |
| Linux % | NaN% |
| Total Windows downloads | 3,009,411 |
| Total macOs downloads | 1,121,272 |
| Total Linux downloads | 916,873 |
| Windows % | 60% |
| macOS % | 22% |
| Linux % | 18% |
(p) Indicates pre-releases
| Version | Date | Windows | macOS | Linux | Total |
| ----- | ----- | ----- | ----- | ----- | ----- |
| ----- | ----- | ----- | ----- | ----- | ----- |
| [v2.10.4](https://github.com/laurent22/joplin/releases/tag/v2.10.4) (p) | 2023-01-05T13:09:20Z | 4,725 | 958 | 1,093 | 6,776 |
| [v2.10.3](https://github.com/laurent22/joplin/releases/tag/v2.10.3) (p) | 2022-12-31T15:53:23Z | 1,288 | 286 | 321 | 1,895 |
| [v2.10.2](https://github.com/laurent22/joplin/releases/tag/v2.10.2) (p) | 2022-12-18T18:05:08Z | 2,686 | 543 | 598 | 3,827 |
| [v2.9.17](https://github.com/laurent22/joplin/releases/tag/v2.9.17) | 2022-11-15T10:28:37Z | 152,936 | 60,742 | 35,554 | 249,232 |
| [v2.9.12](https://github.com/laurent22/joplin/releases/tag/v2.9.12) (p) | 2022-11-01T17:06:05Z | 9,894 | 581 | 513 | 10,988 |
| [v2.9.11](https://github.com/laurent22/joplin/releases/tag/v2.9.11) (p) | 2022-10-23T16:09:58Z | 2,062 | 508 | 692 | 3,262 |
| [v2.9.4](https://github.com/laurent22/joplin/releases/tag/v2.9.4) (p) | 2022-08-18T16:52:26Z | 6,978 | 1,843 | 2,169 | 10,990 |
| [v2.9.3](https://github.com/laurent22/joplin/releases/tag/v2.9.3) (p) | 2022-08-18T13:11:09Z | 282 | 72 | 247 | 601 |
| [v2.9.2](https://github.com/laurent22/joplin/releases/tag/v2.9.2) (p) | 2022-08-12T18:12:12Z | 1,457 | 429 | 0 | 1,886 |
| [v2.9.1](https://github.com/laurent22/joplin/releases/tag/v2.9.1) (p) | 2022-07-11T09:59:32Z | 6,407 | 1,320 | 1,372 | 9,099 |
| [v2.8.8](https://github.com/laurent22/joplin/releases/tag/v2.8.8) | 2022-05-17T14:48:06Z | 345,781 | 113,382 | 113,302 | 572,465 |
| [v2.8.7](https://github.com/laurent22/joplin/releases/tag/v2.8.7) (p) | 2022-05-06T11:34:27Z | 2,602 | 344 | 372 | 3,318 |
| [v2.8.6](https://github.com/laurent22/joplin/releases/tag/v2.8.6) (p) | 2022-05-03T10:08:25Z | 2,253 | 383 | 305 | 2,941 |
| [v2.8.5](https://github.com/laurent22/joplin/releases/tag/v2.8.5) (p) | 2022-04-27T13:51:50Z | 2,244 | 343 | 323 | 2,910 |
| [v2.8.4](https://github.com/laurent22/joplin/releases/tag/v2.8.4) (p) | 2022-04-19T18:00:09Z | 2,747 | 556 | 304 | 3,607 |
| [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (p) | 2022-04-14T11:35:45Z | 2,169 | 257 | 240 | 2,666 |
| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 153,400 | 56,660 | 51,156 | 261,216 |
| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 32,497 | 16,744 | 4,768 | 54,009 |
| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 52,928 | 25,695 | 11,683 | 90,306 |
| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 2,933 | 445 | 437 | 3,815 |
| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 2,041 | 178 | 141 | 2,360 |
| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 1,598 | 107 | 64 | 1,769 |
| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 3,867 | 750 | 801 | 5,418 |
| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 1,861 | 138 | 115 | 2,114 |
| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 1,903 | 165 | 92 | 2,160 |
| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 132,682 | 51,147 | 49,199 | 233,028 |
| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 16,977 | 9,468 | 3,161 | 29,606 |
| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 2,053 | 146 | 82 | 2,281 |
| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 2,095 | 238 | 146 | 2,479 |
| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 1,392 | 36 | 14 | 1,442 |
| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 2,136 | 270 | 179 | 2,585 |
| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 3,902 | 775 | 684 | 5,361 |
| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 80,115 | 32,468 | 25,185 | 137,768 |
| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 45,066 | 19,001 | 10,044 | 74,111 |
| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 13,890 | 6,542 | 2,292 | 22,724 |
| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 1,731 | 190 | 150 | 2,071 |
| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 1,744 | 163 | 91 | 1,998 |
| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 3,203 | 553 | 555 | 4,311 |
| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 44,744 | 19,949 | 9,761 | 74,454 |
| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 4,190 | 887 | 927 | 6,004 |
| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 57,048 | 23,222 | 15,859 | 96,129 |
| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 8,069 | 1,756 | 516 | 10,341 |
| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 2,148 | 236 | 190 | 2,574 |
| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 2,930 | 443 | 501 | 3,874 |
| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 2,188 | 253 | 207 | 2,648 |
| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 2,420 | 361 | 342 | 3,123 |
| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 1,959 | 189 | 161 | 2,309 |
| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 1,677 | 134 | 76 | 1,887 |
| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 2,554 | 355 | 319 | 3,228 |
| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 82,446 | 31,382 | 33,097 | 146,925 |
| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 15,853 | 6,860 | 4,040 | 26,753 |
| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 16,114 | 7,496 | 2,575 | 26,185 |
| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 8,866 | 4,603 | 941 | 14,410 |
| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 2,277 | 261 | 190 | 2,728 |
| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 1,953 | 191 | 116 | 2,260 |
| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 3,853 | 721 | 630 | 5,204 |
| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 47,305 | 18,779 | 16,749 | 82,833 |
| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 3,480 | 399 | 376 | 4,255 |
| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 31,161 | 12,174 | 12,712 | 56,047 |
| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 14,832 | 6,386 | 3,611 | 24,829 |
| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 2,295 | 233 | 184 | 2,712 |
| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 2,425 | 291 | 198 | 2,914 |
| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 24,454 | 9,240 | 9,816 | 43,510 |
| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 3,485 | 917 | 372 | 4,774 |
| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 2,418 | 289 | 875 | 3,582 |
| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 1,946 | 225 | 572 | 2,743 |
| [v2.0.4](https://github.com/laurent22/joplin/releases/tag/v2.0.4) (p) | 2021-06-02T12:54:17Z | 1,597 | 387 | 374 | 2,358 |
| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 3,644 | 485 | 1,663 | 5,792 |
| [v2.0.1](https://github.com/laurent22/joplin/releases/tag/v2.0.1) (p) | 2021-05-15T13:22:58Z | 873 | 268 | 1,017 | 2,158 |
| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 39,069 | 16,266 | 19,398 | 74,733 |
| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 1,943 | 134 | 453 | 2,530 |
| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 2,846 | 303 | 934 | 4,083 |
| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 3,160 | 433 | 1,282 | 4,875 |
| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 4,262 | 823 | 2,450 | 7,535 |
| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 117,913 | 42,821 | 64,336 | 225,070 |
| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,214 | 4,854 | 4,485 | 23,553 |
| [v1.7.9](https://github.com/laurent22/joplin/releases/tag/v1.7.9) (p) | 2021-01-28T09:50:21Z | 502 | 133 | 498 | 1,133 |
| [v1.7.6](https://github.com/laurent22/joplin/releases/tag/v1.7.6) (p) | 2021-01-27T10:36:05Z | 316 | 93 | 288 | 697 |
| [v1.7.5](https://github.com/laurent22/joplin/releases/tag/v1.7.5) (p) | 2021-01-26T09:53:05Z | 408 | 205 | 454 | 1,067 |
| [v1.7.4](https://github.com/laurent22/joplin/releases/tag/v1.7.4) (p) | 2021-01-22T17:58:38Z | 695 | 204 | 625 | 1,524 |
| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 20,704 | 7,702 | 7,605 | 36,011 |
| [v1.7.3](https://github.com/laurent22/joplin/releases/tag/v1.7.3) (p) | 2021-01-20T11:23:50Z | 349 | 77 | 442 | 868 |
| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 12,532 | 4,637 | 4,543 | 21,712 |
| [v1.6.6](https://github.com/laurent22/joplin/releases/tag/v1.6.6) | 2021-01-09T16:15:31Z | 12,702 | 3,417 | 4,794 | 20,913 |
| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 2,183 | 77 | 308 | 2,568 |
| [v1.6.4](https://github.com/laurent22/joplin/releases/tag/v1.6.4) (p) | 2021-01-07T19:11:32Z | 392 | 78 | 204 | 674 |
| [v1.6.2](https://github.com/laurent22/joplin/releases/tag/v1.6.2) (p) | 2021-01-04T22:34:55Z | 673 | 228 | 590 | 1,491 |
| [v1.5.14](https://github.com/laurent22/joplin/releases/tag/v1.5.14) | 2020-12-30T01:48:46Z | 13,010 | 5,206 | 5,529 | 23,745 |
| [v1.6.1](https://github.com/laurent22/joplin/releases/tag/v1.6.1) (p) | 2020-12-29T19:37:45Z | 171 | 38 | 168 | 377 |
| [v1.5.13](https://github.com/laurent22/joplin/releases/tag/v1.5.13) | 2020-12-29T18:29:15Z | 646 | 219 | 203 | 1,068 |
| [v1.5.12](https://github.com/laurent22/joplin/releases/tag/v1.5.12) | 2020-12-28T15:14:08Z | 2,417 | 1,769 | 923 | 5,109 |
| [v1.5.11](https://github.com/laurent22/joplin/releases/tag/v1.5.11) | 2020-12-27T19:54:07Z | 14,175 | 4,630 | 4,279 | 23,084 |
| [v1.5.10](https://github.com/laurent22/joplin/releases/tag/v1.5.10) (p) | 2020-12-26T12:35:36Z | 294 | 107 | 270 | 671 |
| [v1.5.9](https://github.com/laurent22/joplin/releases/tag/v1.5.9) (p) | 2020-12-23T18:01:08Z | 327 | 372 | 411 | 1,110 |
| [v1.5.8](https://github.com/laurent22/joplin/releases/tag/v1.5.8) (p) | 2020-12-20T09:45:19Z | 566 | 165 | 644 | 1,375 |
| [v1.5.7](https://github.com/laurent22/joplin/releases/tag/v1.5.7) (p) | 2020-12-10T12:58:33Z | 889 | 254 | 994 | 2,137 |
| [v1.5.4](https://github.com/laurent22/joplin/releases/tag/v1.5.4) (p) | 2020-12-05T12:07:49Z | 693 | 167 | 635 | 1,495 |
| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 27,769 | 13,527 | 11,679 | 52,975 |
| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,519 | 3,884 | 3,142 | 18,545 |
| [v1.4.16](https://github.com/laurent22/joplin/releases/tag/v1.4.16) | 2020-11-27T19:40:16Z | 1,492 | 833 | 601 | 2,926 |
| [v1.4.15](https://github.com/laurent22/joplin/releases/tag/v1.4.15) | 2020-11-27T13:25:43Z | 909 | 487 | 276 | 1,672 |
| [v1.4.12](https://github.com/laurent22/joplin/releases/tag/v1.4.12) | 2020-11-23T18:58:07Z | 3,057 | 1,334 | 1,308 | 5,699 |
| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 2,930 | 161 | 595 | 3,686 |
| [v1.4.10](https://github.com/laurent22/joplin/releases/tag/v1.4.10) (p) | 2020-11-14T09:53:15Z | 646 | 198 | 686 | 1,530 |
| [v1.4.9](https://github.com/laurent22/joplin/releases/tag/v1.4.9) (p) | 2020-11-11T14:23:17Z | 831 | 144 | 404 | 1,379 |
| [v1.4.7](https://github.com/laurent22/joplin/releases/tag/v1.4.7) (p) | 2020-11-07T18:23:29Z | 524 | 175 | 517 | 1,216 |
| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 33,018 | 11,342 | 10,521 | 54,881 |
| [v1.3.17](https://github.com/laurent22/joplin/releases/tag/v1.3.17) (p) | 2020-11-06T11:35:15Z | 50 | 28 | 26 | 104 |
| [v1.4.6](https://github.com/laurent22/joplin/releases/tag/v1.4.6) (p) | 2020-11-05T22:44:12Z | 659 | 96 | 55 | 810 |
| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,578 | 1,303 | 849 | 4,730 |
| [v1.3.11](https://github.com/laurent22/joplin/releases/tag/v1.3.11) (p) | 2020-10-31T13:22:20Z | 701 | 190 | 484 | 1,375 |
| [v1.3.10](https://github.com/laurent22/joplin/releases/tag/v1.3.10) (p) | 2020-10-29T13:27:14Z | 378 | 119 | 319 | 816 |
| [v1.3.9](https://github.com/laurent22/joplin/releases/tag/v1.3.9) (p) | 2020-10-23T16:04:26Z | 840 | 247 | 636 | 1,723 |
| [v1.3.8](https://github.com/laurent22/joplin/releases/tag/v1.3.8) (p) | 2020-10-21T18:46:29Z | 521 | 121 | 333 | 975 |
| [v1.3.7](https://github.com/laurent22/joplin/releases/tag/v1.3.7) (p) | 2020-10-20T11:35:55Z | 298 | 89 | 345 | 732 |
| [v1.3.5](https://github.com/laurent22/joplin/releases/tag/v1.3.5) (p) | 2020-10-17T14:26:35Z | 475 | 137 | 409 | 1,021 |
| [v1.3.3](https://github.com/laurent22/joplin/releases/tag/v1.3.3) (p) | 2020-10-17T10:56:57Z | 122 | 50 | 36 | 208 |
| [v1.3.2](https://github.com/laurent22/joplin/releases/tag/v1.3.2) (p) | 2020-10-11T20:39:49Z | 671 | 186 | 571 | 1,428 |
| [v1.3.1](https://github.com/laurent22/joplin/releases/tag/v1.3.1) (p) | 2020-10-11T15:10:18Z | 85 | 55 | 47 | 187 |
| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 46,803 | 17,750 | 14,053 | 78,606 |
| [v1.2.4](https://github.com/laurent22/joplin/releases/tag/v1.2.4) (p) | 2020-09-30T07:34:29Z | 823 | 252 | 802 | 1,877 |
| [v1.2.3](https://github.com/laurent22/joplin/releases/tag/v1.2.3) (p) | 2020-09-29T15:13:02Z | 221 | 68 | 84 | 373 |
| [v1.2.2](https://github.com/laurent22/joplin/releases/tag/v1.2.2) (p) | 2020-09-22T20:31:55Z | 1,104 | 210 | 642 | 1,956 |
| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 27,959 | 13,515 | 7,757 | 49,231 |
| [v1.1.3](https://github.com/laurent22/joplin/releases/tag/v1.1.3) (p) | 2020-09-17T10:30:37Z | 575 | 156 | 468 | 1,199 |
| [v1.1.2](https://github.com/laurent22/joplin/releases/tag/v1.1.2) (p) | 2020-09-15T12:58:38Z | 382 | 122 | 256 | 760 |
| [v1.1.1](https://github.com/laurent22/joplin/releases/tag/v1.1.1) (p) | 2020-09-11T23:32:47Z | 538 | 203 | 355 | 1,096 |
| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 22,361 | 10,020 | 5,650 | 38,031 |
| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,828 | 6,429 | 3,027 | 22,284 |
| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 26,249 | 5,934 | 5,117 | 37,300 |
| [v1.0.239](https://github.com/laurent22/joplin/releases/tag/v1.0.239) (p) | 2020-09-01T21:56:36Z | 922 | 234 | 407 | 1,563 |
| [v1.0.237](https://github.com/laurent22/joplin/releases/tag/v1.0.237) (p) | 2020-08-29T15:38:04Z | 596 | 932 | 345 | 1,873 |
| [v1.0.236](https://github.com/laurent22/joplin/releases/tag/v1.0.236) (p) | 2020-08-28T09:16:54Z | 322 | 120 | 110 | 552 |
| [v1.0.235](https://github.com/laurent22/joplin/releases/tag/v1.0.235) (p) | 2020-08-18T22:08:01Z | 1,996 | 498 | 928 | 3,422 |
| [v1.0.234](https://github.com/laurent22/joplin/releases/tag/v1.0.234) (p) | 2020-08-17T23:13:02Z | 616 | 133 | 107 | 856 |
| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 45,565 | 18,209 | 12,367 | 76,141 |
| [v1.0.232](https://github.com/laurent22/joplin/releases/tag/v1.0.232) (p) | 2020-07-28T22:34:40Z | 660 | 231 | 186 | 1,077 |
| [v1.0.227](https://github.com/laurent22/joplin/releases/tag/v1.0.227) | 2020-07-07T20:44:54Z | 41,279 | 15,290 | 9,647 | 66,216 |
| [v1.0.226](https://github.com/laurent22/joplin/releases/tag/v1.0.226) (p) | 2020-07-04T10:21:26Z | 4,927 | 2,261 | 694 | 7,882 |
| [v1.0.224](https://github.com/laurent22/joplin/releases/tag/v1.0.224) | 2020-06-20T22:26:08Z | 24,955 | 11,015 | 6,014 | 41,984 |
| [v1.0.223](https://github.com/laurent22/joplin/releases/tag/v1.0.223) (p) | 2020-06-20T11:51:27Z | 194 | 122 | 85 | 401 |
| [v1.0.221](https://github.com/laurent22/joplin/releases/tag/v1.0.221) (p) | 2020-06-20T01:44:20Z | 862 | 215 | 218 | 1,295 |
| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,552 | 9,934 | 6,423 | 48,909 |
| [v1.0.218](https://github.com/laurent22/joplin/releases/tag/v1.0.218) | 2020-06-07T10:43:34Z | 14,560 | 6,982 | 3,131 | 24,673 |
| [v1.0.217](https://github.com/laurent22/joplin/releases/tag/v1.0.217) (p) | 2020-06-06T15:17:27Z | 233 | 104 | 62 | 399 |
| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 39,325 | 14,306 | 10,190 | 63,821 |
| [v1.0.214](https://github.com/laurent22/joplin/releases/tag/v1.0.214) (p) | 2020-05-21T17:15:15Z | 6,715 | 3,478 | 770 | 10,963 |
| [v1.0.212](https://github.com/laurent22/joplin/releases/tag/v1.0.212) (p) | 2020-05-21T07:48:39Z | 219 | 77 | 54 | 350 |
| [v1.0.211](https://github.com/laurent22/joplin/releases/tag/v1.0.211) (p) | 2020-05-20T08:59:16Z | 307 | 141 | 94 | 542 |
| [v1.0.209](https://github.com/laurent22/joplin/releases/tag/v1.0.209) (p) | 2020-05-17T18:32:51Z | 1,400 | 861 | 154 | 2,415 |
| [v1.0.207](https://github.com/laurent22/joplin/releases/tag/v1.0.207) (p) | 2020-05-10T16:37:35Z | 1,204 | 272 | 1,024 | 2,500 |
| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,261 | 20,059 | 18,186 | 92,506 |
| [v1.0.200](https://github.com/laurent22/joplin/releases/tag/v1.0.200) | 2020-04-12T12:17:46Z | 9,572 | 4,898 | 1,909 | 16,379 |
| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,598 | 5,896 | 3,798 | 29,292 |
| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 23,025 | 9,706 | 6,189 | 38,920 |
| [v1.0.195](https://github.com/laurent22/joplin/releases/tag/v1.0.195) | 2020-03-22T19:56:12Z | 19,110 | 7,956 | 4,512 | 31,578 |
| [v1.0.194](https://github.com/laurent22/joplin/releases/tag/v1.0.194) (p) | 2020-03-14T00:00:32Z | 1,290 | 1,391 | 523 | 3,204 |
| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,742 | 10,924 | 7,421 | 47,087 |
| [v1.0.192](https://github.com/laurent22/joplin/releases/tag/v1.0.192) (p) | 2020-03-06T23:27:52Z | 487 | 128 | 95 | 710 |
| [v1.0.190](https://github.com/laurent22/joplin/releases/tag/v1.0.190) (p) | 2020-03-06T01:22:22Z | 390 | 98 | 91 | 579 |
| [v1.0.189](https://github.com/laurent22/joplin/releases/tag/v1.0.189) (p) | 2020-03-04T17:27:15Z | 359 | 102 | 102 | 563 |
| [v1.0.187](https://github.com/laurent22/joplin/releases/tag/v1.0.187) (p) | 2020-03-01T12:31:06Z | 930 | 242 | 279 | 1,451 |
| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,429 | 28,786 | 22,566 | 122,781 |
| [v1.0.178](https://github.com/laurent22/joplin/releases/tag/v1.0.178) | 2020-01-20T19:06:45Z | 17,602 | 5,970 | 2,595 | 26,167 |
| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 1,960 | 445 | 713 | 3,118 |
| [v1.0.176](https://github.com/laurent22/joplin/releases/tag/v1.0.176) (p) | 2019-12-14T10:36:44Z | 3,130 | 2,541 | 475 | 6,146 |
| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,515 | 16,967 | 16,586 | 107,068 |
| [v1.0.174](https://github.com/laurent22/joplin/releases/tag/v1.0.174) | 2019-11-12T18:20:58Z | 30,582 | 11,749 | 8,232 | 50,563 |
| [v1.0.173](https://github.com/laurent22/joplin/releases/tag/v1.0.173) | 2019-11-11T08:33:35Z | 5,105 | 2,087 | 752 | 7,944 |
| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,675 | 8,775 | 7,686 | 44,136 |
| [v1.0.169](https://github.com/laurent22/joplin/releases/tag/v1.0.169) | 2019-09-27T18:35:13Z | 17,182 | 5,927 | 3,759 | 26,868 |
| [v1.0.168](https://github.com/laurent22/joplin/releases/tag/v1.0.168) | 2019-09-25T21:21:38Z | 5,338 | 2,279 | 722 | 8,339 |
| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 16,836 | 5,711 | 3,708 | 26,255 |
| [v1.0.166](https://github.com/laurent22/joplin/releases/tag/v1.0.166) | 2019-09-09T17:35:54Z | 1,964 | 567 | 240 | 2,771 |
| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 19,051 | 6,986 | 5,471 | 31,508 |
| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,323 | 6,357 | 4,141 | 29,821 |
| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,679 | 7,763 | 8,110 | 46,552 |
| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,203 | 2,184 | 1,153 | 8,540 |
| [v1.0.158](https://github.com/laurent22/joplin/releases/tag/v1.0.158) | 2019-05-27T19:01:18Z | 9,829 | 3,554 | 1,941 | 15,324 |
| [v1.0.157](https://github.com/laurent22/joplin/releases/tag/v1.0.157) | 2019-05-26T17:55:53Z | 2,186 | 849 | 296 | 3,331 |
| [v1.0.153](https://github.com/laurent22/joplin/releases/tag/v1.0.153) (p) | 2019-05-15T06:27:29Z | 857 | 109 | 111 | 1,077 |
| [v1.0.152](https://github.com/laurent22/joplin/releases/tag/v1.0.152) | 2019-05-13T09:08:07Z | 13,883 | 4,441 | 4,066 | 22,390 |
| [v1.0.151](https://github.com/laurent22/joplin/releases/tag/v1.0.151) | 2019-05-12T15:14:32Z | 1,961 | 542 | 964 | 3,467 |
| [v1.0.150](https://github.com/laurent22/joplin/releases/tag/v1.0.150) | 2019-05-12T11:27:48Z | 431 | 142 | 74 | 647 |
| [v1.0.148](https://github.com/laurent22/joplin/releases/tag/v1.0.148) (p) | 2019-05-08T19:12:24Z | 135 | 62 | 100 | 297 |
| [v1.0.145](https://github.com/laurent22/joplin/releases/tag/v1.0.145) | 2019-05-03T09:16:53Z | 7,017 | 2,867 | 1,443 | 11,327 |
| [v1.0.143](https://github.com/laurent22/joplin/releases/tag/v1.0.143) | 2019-04-22T10:51:38Z | 11,926 | 3,559 | 2,786 | 18,271 |
| [v1.0.142](https://github.com/laurent22/joplin/releases/tag/v1.0.142) | 2019-04-02T16:44:51Z | 14,763 | 4,573 | 4,734 | 24,070 |
| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,643 | 4,180 | 3,384 | 21,207 |
| [v1.0.139](https://github.com/laurent22/joplin/releases/tag/v1.0.139) (p) | 2019-03-09T10:06:48Z | 128 | 71 | 53 | 252 |
| [v1.0.138](https://github.com/laurent22/joplin/releases/tag/v1.0.138) (p) | 2019-03-03T17:23:00Z | 157 | 95 | 89 | 341 |
| [v1.0.137](https://github.com/laurent22/joplin/releases/tag/v1.0.137) (p) | 2019-03-03T01:12:51Z | 596 | 63 | 89 | 748 |
| [v1.0.135](https://github.com/laurent22/joplin/releases/tag/v1.0.135) | 2019-02-27T23:36:57Z | 12,635 | 3,964 | 4,084 | 20,683 |
| [v1.0.134](https://github.com/laurent22/joplin/releases/tag/v1.0.134) | 2019-02-27T10:21:44Z | 1,474 | 574 | 224 | 2,272 |
| [v1.0.132](https://github.com/laurent22/joplin/releases/tag/v1.0.132) | 2019-02-26T23:02:05Z | 1,095 | 459 | 100 | 1,654 |
| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 9,881 | 3,178 | 2,937 | 15,996 |
| [v1.0.126](https://github.com/laurent22/joplin/releases/tag/v1.0.126) (p) | 2019-02-09T19:46:16Z | 938 | 80 | 121 | 1,139 |
| [v1.0.125](https://github.com/laurent22/joplin/releases/tag/v1.0.125) | 2019-01-26T18:14:33Z | 10,295 | 3,565 | 1,708 | 15,568 |
| [v1.0.120](https://github.com/laurent22/joplin/releases/tag/v1.0.120) | 2019-01-10T21:42:53Z | 15,624 | 5,214 | 6,524 | 27,362 |
| [v1.0.119](https://github.com/laurent22/joplin/releases/tag/v1.0.119) | 2018-12-18T12:40:22Z | 8,913 | 3,268 | 2,019 | 14,200 |
| [v1.0.118](https://github.com/laurent22/joplin/releases/tag/v1.0.118) | 2019-01-11T08:34:13Z | 722 | 255 | 94 | 1,071 |
| [v1.0.117](https://github.com/laurent22/joplin/releases/tag/v1.0.117) | 2018-11-24T12:05:24Z | 16,268 | 4,903 | 6,387 | 27,558 |
| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 3,786 | 1,130 | 719 | 5,635 |
| [v1.0.115](https://github.com/laurent22/joplin/releases/tag/v1.0.115) | 2018-11-16T16:52:02Z | 3,662 | 1,309 | 807 | 5,778 |
| [v1.0.114](https://github.com/laurent22/joplin/releases/tag/v1.0.114) | 2018-10-24T20:14:10Z | 11,401 | 3,508 | 3,835 | 18,744 |
| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,168 | 3,350 | 3,688 | 19,206 |
| [v1.0.110](https://github.com/laurent22/joplin/releases/tag/v1.0.110) | 2018-09-29T12:29:21Z | 966 | 418 | 124 | 1,508 |
| [v1.0.109](https://github.com/laurent22/joplin/releases/tag/v1.0.109) | 2018-09-27T18:01:41Z | 2,108 | 713 | 336 | 3,157 |
| [v1.0.108](https://github.com/laurent22/joplin/releases/tag/v1.0.108) (p) | 2018-09-29T18:49:29Z | 35 | 28 | 21 | 84 |
| [v1.0.107](https://github.com/laurent22/joplin/releases/tag/v1.0.107) | 2018-09-16T19:51:07Z | 7,159 | 2,143 | 1,717 | 11,019 |
| [v1.0.106](https://github.com/laurent22/joplin/releases/tag/v1.0.106) | 2018-09-08T15:23:40Z | 4,564 | 1,463 | 323 | 6,350 |
| [v1.0.105](https://github.com/laurent22/joplin/releases/tag/v1.0.105) | 2018-09-05T11:29:36Z | 4,661 | 1,596 | 1,463 | 7,720 |
| [v1.0.104](https://github.com/laurent22/joplin/releases/tag/v1.0.104) | 2018-06-28T20:25:36Z | 15,076 | 4,709 | 7,369 | 27,154 |
| [v1.0.103](https://github.com/laurent22/joplin/releases/tag/v1.0.103) | 2018-06-21T19:38:13Z | 2,059 | 894 | 685 | 3,638 |
| [v1.0.101](https://github.com/laurent22/joplin/releases/tag/v1.0.101) | 2018-06-17T18:35:11Z | 1,315 | 615 | 415 | 2,345 |
| [v1.0.100](https://github.com/laurent22/joplin/releases/tag/v1.0.100) | 2018-06-14T17:41:43Z | 892 | 440 | 251 | 1,583 |
| [v1.0.99](https://github.com/laurent22/joplin/releases/tag/v1.0.99) | 2018-06-10T13:18:23Z | 1,260 | 604 | 386 | 2,250 |
| [v1.0.97](https://github.com/laurent22/joplin/releases/tag/v1.0.97) | 2018-06-09T19:23:34Z | 320 | 162 | 65 | 547 |
| [v1.0.96](https://github.com/laurent22/joplin/releases/tag/v1.0.96) | 2018-05-26T16:36:39Z | 2,727 | 1,232 | 1,707 | 5,666 |
| [v1.0.95](https://github.com/laurent22/joplin/releases/tag/v1.0.95) | 2018-05-25T13:04:30Z | 424 | 226 | 129 | 779 |
| [v1.0.94](https://github.com/laurent22/joplin/releases/tag/v1.0.94) | 2018-05-21T20:52:59Z | 1,139 | 593 | 407 | 2,139 |
| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,796 | 1,253 | 768 | 3,817 |
| [v1.0.91](https://github.com/laurent22/joplin/releases/tag/v1.0.91) | 2018-05-10T14:48:04Z | 832 | 560 | 319 | 1,711 |
| [v1.0.89](https://github.com/laurent22/joplin/releases/tag/v1.0.89) | 2018-05-09T13:05:05Z | 500 | 242 | 121 | 863 |
| [v1.0.85](https://github.com/laurent22/joplin/releases/tag/v1.0.85) | 2018-05-01T21:08:24Z | 1,657 | 959 | 643 | 3,259 |
| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,452 | 2,540 | 2,668 | 10,660 |
| [v1.0.82](https://github.com/laurent22/joplin/releases/tag/v1.0.82) | 2018-03-31T19:16:31Z | 696 | 415 | 132 | 1,243 |
| [v1.0.81](https://github.com/laurent22/joplin/releases/tag/v1.0.81) | 2018-03-28T08:13:58Z | 1,005 | 607 | 793 | 2,405 |
| [v1.0.79](https://github.com/laurent22/joplin/releases/tag/v1.0.79) | 2018-03-23T18:00:11Z | 934 | 546 | 393 | 1,873 |
| [v1.0.78](https://github.com/laurent22/joplin/releases/tag/v1.0.78) | 2018-03-17T15:27:18Z | 1,315 | 902 | 881 | 3,098 |
| [v1.0.77](https://github.com/laurent22/joplin/releases/tag/v1.0.77) | 2018-03-16T15:12:35Z | 182 | 111 | 53 | 346 |
| [v1.0.72](https://github.com/laurent22/joplin/releases/tag/v1.0.72) | 2018-03-14T09:44:35Z | 415 | 265 | 65 | 745 |
| [v1.0.70](https://github.com/laurent22/joplin/releases/tag/v1.0.70) | 2018-02-28T20:04:30Z | 1,860 | 1,059 | 1,263 | 4,182 |
| [v1.0.67](https://github.com/laurent22/joplin/releases/tag/v1.0.67) | 2018-02-19T22:51:08Z | 1,818 | 613 | 0 | 2,431 |
| [v1.0.66](https://github.com/laurent22/joplin/releases/tag/v1.0.66) | 2018-02-18T23:09:09Z | 332 | 142 | 91 | 565 |
| [v1.0.65](https://github.com/laurent22/joplin/releases/tag/v1.0.65) | 2018-02-17T20:02:25Z | 197 | 135 | 139 | 471 |
| [v1.0.64](https://github.com/laurent22/joplin/releases/tag/v1.0.64) | 2018-02-16T00:58:20Z | 1,088 | 551 | 1,130 | 2,769 |
| [v1.0.63](https://github.com/laurent22/joplin/releases/tag/v1.0.63) | 2018-02-14T19:40:36Z | 306 | 167 | 98 | 571 |
| [v1.0.62](https://github.com/laurent22/joplin/releases/tag/v1.0.62) | 2018-02-12T20:19:58Z | 569 | 307 | 375 | 1,251 |
| [v0.10.61](https://github.com/laurent22/joplin/releases/tag/v0.10.61) | 2018-02-08T18:27:39Z | 975 | 642 | 969 | 2,586 |
| [v0.10.60](https://github.com/laurent22/joplin/releases/tag/v0.10.60) | 2018-02-06T13:09:56Z | 726 | 529 | 558 | 1,813 |
| [v0.10.54](https://github.com/laurent22/joplin/releases/tag/v0.10.54) | 2018-01-31T20:21:30Z | 1,824 | 1,467 | 330 | 3,621 |
| [v0.10.52](https://github.com/laurent22/joplin/releases/tag/v0.10.52) | 2018-01-31T19:25:18Z | 50 | 641 | 23 | 714 |
| [v0.10.51](https://github.com/laurent22/joplin/releases/tag/v0.10.51) | 2018-01-28T18:47:02Z | 1,332 | 1,607 | 334 | 3,273 |
| [v0.10.48](https://github.com/laurent22/joplin/releases/tag/v0.10.48) | 2018-01-23T11:19:51Z | 1,968 | 1,760 | 37 | 3,765 |
| [v0.10.47](https://github.com/laurent22/joplin/releases/tag/v0.10.47) | 2018-01-16T17:27:17Z | 1,233 | 1,279 | 73 | 2,585 |
| [v0.10.43](https://github.com/laurent22/joplin/releases/tag/v0.10.43) | 2018-01-08T10:12:10Z | 3,446 | 2,364 | 1,215 | 7,025 |
| [v0.10.41](https://github.com/laurent22/joplin/releases/tag/v0.10.41) | 2018-01-05T20:38:12Z | 1,040 | 1,555 | 247 | 2,842 |
| [v0.10.40](https://github.com/laurent22/joplin/releases/tag/v0.10.40) | 2018-01-02T23:16:57Z | 1,599 | 1,795 | 344 | 3,738 |
| [v0.10.39](https://github.com/laurent22/joplin/releases/tag/v0.10.39) | 2017-12-11T21:19:44Z | 5,926 | 4,395 | 3,291 | 13,612 |
| [v0.10.38](https://github.com/laurent22/joplin/releases/tag/v0.10.38) | 2017-12-08T10:12:06Z | 1,055 | 1,240 | 314 | 2,609 |
| [v0.10.37](https://github.com/laurent22/joplin/releases/tag/v0.10.37) | 2017-12-07T19:38:05Z | 272 | 857 | 91 | 1,220 |
| [v0.10.36](https://github.com/laurent22/joplin/releases/tag/v0.10.36) | 2017-12-05T09:34:40Z | 1,021 | 1,370 | 446 | 2,837 |
| [v0.10.35](https://github.com/laurent22/joplin/releases/tag/v0.10.35) | 2017-12-02T15:56:08Z | 1,583 | 1,558 | 752 | 3,893 |
| [v0.10.34](https://github.com/laurent22/joplin/releases/tag/v0.10.34) | 2017-12-02T14:50:28Z | 97 | 682 | 68 | 847 |
| [v0.10.33](https://github.com/laurent22/joplin/releases/tag/v0.10.33) | 2017-12-02T13:20:39Z | 67 | 673 | 33 | 773 |
| [v0.10.31](https://github.com/laurent22/joplin/releases/tag/v0.10.31) | 2017-12-01T09:56:44Z | 898 | 1,466 | 416 | 2,780 |
| [v0.10.30](https://github.com/laurent22/joplin/releases/tag/v0.10.30) | 2017-11-30T20:28:16Z | 728 | 1,381 | 431 | 2,540 |
| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,362 | 1,714 | 886 | 3,962 |
| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 197 | 715 | 271 | 1,183 |
| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 154 | 709 | 6,642 | 7,505 |
| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 142 | 674 | 45 | 861 |
| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 92 | 661 | 31 | 784 |
| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 60 | 654 | 25 | 739 |
| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 50 | 662 | 34 | 746 |
| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 38 | 664 | 30 | 732 |

View File

@@ -88,10 +88,12 @@
"@babel/runtime",
"@types/react-native",
"androidx.appcompat:appcompat",
"babel-plugin-module-resolver",
"com.android.tools.build:gradle",
"com.facebook.flipper:flipper-fresco-plugin",
"com.facebook.flipper:flipper-network-plugin",
"com.facebook.flipper:flipper",
"com.google.code.gson:gson",
"com.google.code.gson",
"de.undercouch:gradle-download-task",
"jsc-android",
@@ -112,25 +114,18 @@
"labels": ["automerge"],
},
{
"matchUpdateTypes": ["minor", "patch"],
"matchUpdateTypes": ["major", "minor", "patch"],
"automerge": true,
"groupName": "buildTools",
"labels": ["automerge"],
"extends": ["schedule:monthly"],
"matchPackageNames": [
// AWS packages are updated too frequently and we can assume minor
// updates are stable.
"@aws-sdk/client-s3",
"@aws-sdk/s3-request-presigner",
"aws-sdk",
"matchPackagePatterns": [
// If the apps build and all tests pass, we can assume that Yarn
// and TypeScript are safe to upgrade. They are frequently
// updated so having them here reduces noise.
"eslint",
"eslint-config-prettier",
"eslint-interactive",
"@typescript-eslint/eslint-plugin",
"@typescript-eslint/parser",
"eslint-*",
"@typescript-eslint/*",
"yarn",
"typescript",
"prettier",
@@ -138,6 +133,19 @@
// If it builds, it should be safe to merge @types/* packages
"@types/*",
],
}
},
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"groupName": "aws",
"labels": ["automerge"],
"extends": ["schedule:monthly"],
"matchPackagePatterns": [
// AWS packages are updated too frequently and we can assume minor
// updates are stable.
"@aws-sdk/*",
"aws-sdk",
],
},
]
}

893
yarn.lock

File diff suppressed because it is too large Load Diff