Merge branch 'dev' into release-2.8

pull/6446/head
Laurent Cozic 2022-04-19 15:54:35 +01:00
commit 89a498b886
49 changed files with 821 additions and 723 deletions

View File

@ -562,6 +562,9 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
packages/app-desktop/gui/NoteList/commands/index.d.ts
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/commands/index.js.map
packages/app-desktop/gui/NoteList/types.d.ts
packages/app-desktop/gui/NoteList/types.js
packages/app-desktop/gui/NoteList/types.js.map
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map

3
.gitignore vendored
View File

@ -552,6 +552,9 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
packages/app-desktop/gui/NoteList/commands/index.d.ts
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/commands/index.js.map
packages/app-desktop/gui/NoteList/types.d.ts
packages/app-desktop/gui/NoteList/types.js
packages/app-desktop/gui/NoteList/types.js.map
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -70,7 +70,7 @@ A community maintained list of these distributions can be found here: [Unofficia
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@ -354,8 +354,7 @@ class Application extends BaseApplication {
}
// Loads app-wide styles. (Markdown preview-specific styles loaded in app.js)
const filename = Setting.custom_css_files.JOPLIN_APP;
await injectCustomStyles('appStyles', `${dir}/${filename}`);
await injectCustomStyles('appStyles', Setting.customCssFilePath(Setting.customCssFilenames.JOPLIN_APP));
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
@ -433,7 +432,7 @@ class Application extends BaseApplication {
});
// Loads custom Markdown preview styles
const cssString = await loadCustomCss(`${Setting.value('profileDir')}/userstyle.css`);
const cssString = await loadCustomCss(Setting.customCssFilePath(Setting.customCssFilenames.RENDERED_MARKDOWN));
this.store().dispatch({
type: 'CUSTOM_CSS_APPEND',
css: cssString,
@ -522,6 +521,7 @@ class Application extends BaseApplication {
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
};

View File

@ -265,7 +265,7 @@ export class Bridge {
}
}
restart() {
restart(linuxSafeRestart = true) {
// Note that in this case we are not sending the "appClose" event
// to notify services and component that the app is about to close
// but for the current use-case it's not really needed.
@ -276,7 +276,7 @@ export class Bridge {
execPath: process.env.PORTABLE_EXECUTABLE_FILE,
};
app.relaunch(options);
} else if (shim.isLinux()) {
} else if (shim.isLinux() && linuxSafeRestart) {
this.showInfoMessageBox(_('The app is now going to close. Please relaunch it to complete the process.'));
} else {
app.relaunch();

View File

@ -10,17 +10,17 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, profileIndex: number) => {
execute: async (context: CommandContext, profileId: string) => {
const currentConfig = context.state.profileConfig;
if (currentConfig.currentProfile === profileIndex) return;
if (currentConfig.currentProfileId === profileId) return;
const newConfig: ProfileConfig = {
...currentConfig,
currentProfile: profileIndex,
currentProfileId: profileId,
};
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
bridge().restart();
bridge().restart(false);
},
};
};

View File

@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '../../lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile1',
@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 0);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 0));
},
};
};

View File

@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '../../lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile2',
@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 1);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 1));
},
};
};

View File

@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '../../lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile3',
@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 2);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 2));
},
};
};

View File

@ -8,7 +8,7 @@ import NoteEditor from '../NoteEditor/NoteEditor';
import NoteContentPropertiesDialog from '../NoteContentPropertiesDialog';
import ShareNoteDialog from '../ShareNoteDialog';
import CommandService from '@joplin/lib/services/CommandService';
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import { PluginHtmlContents, PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import Sidebar from '../Sidebar/Sidebar';
import UserWebview from '../../services/plugins/UserWebview';
import UserWebviewDialog from '../../services/plugins/UserWebviewDialog';
@ -53,6 +53,7 @@ interface LayerModalState {
interface Props {
plugins: PluginStates;
pluginHtmlContents: PluginHtmlContents;
pluginsLoaded: boolean;
hasNotesBeingSaved: boolean;
dispatch: Function;
@ -723,12 +724,13 @@ class MainScreenComponent extends React.Component<Props, State> {
}
} else {
const { view, plugin } = viewInfo;
const html = this.props.pluginHtmlContents[plugin.id]?.[view.id] ?? '';
return <UserWebview
key={view.id}
viewId={view.id}
themeId={this.props.themeId}
html={view.html}
html={html}
scripts={view.scripts}
pluginId={plugin.id}
borderBottom={true}
@ -762,12 +764,13 @@ class MainScreenComponent extends React.Component<Props, State> {
const { plugin, view } = info;
if (view.containerType !== ContainerType.Dialog) continue;
if (!view.opened) continue;
const html = this.props.pluginHtmlContents[plugin.id]?.[view.id] ?? '';
output.push(<UserWebviewDialog
key={view.id}
viewId={view.id}
themeId={this.props.themeId}
html={view.html}
html={html}
scripts={view.scripts}
pluginId={plugin.id}
buttons={view.buttons}
@ -865,6 +868,7 @@ const mapStateToProps = (state: AppState) => {
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
pluginsLegacy: state.pluginsLegacy,
plugins: state.pluginService.plugins,
pluginHtmlContents: state.pluginService.pluginHtmlContents,
customCss: state.customCss,
editorNoteStatuses: state.editorNoteStatuses,
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),

View File

@ -19,10 +19,10 @@ export const runtime = (comp: any): CommandRuntime => {
value: '',
onClose: async (answer: string) => {
if (answer) {
const newConfig = await createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfile = newConfig.profiles.length - 1;
const { newConfig, newProfile } = createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfileId = newProfile.id;
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
bridge().restart();
bridge().restart(false);
}
comp.setState({ promptOptions: null });

View File

@ -86,14 +86,14 @@ const useSwitchProfileMenuItems = (profileConfig: ProfileConfig, menuItemDic: an
menuItem = {
label: profile.name,
click: () => {
void CommandService.instance().execute('switchProfile', i);
void CommandService.instance().execute('switchProfile', profile.id);
},
};
}
menuItem.label = profile.name;
menuItem.type = 'checkbox';
menuItem.checked = profileConfig.currentProfile === i;
menuItem.checked = profileConfig.currentProfileId === profile.id;
switchProfileMenuItems.push(menuItem);
}
@ -211,6 +211,12 @@ function useMenu(props: Props) {
const [keymapLastChangeTime, setKeymapLastChangeTime] = useState(Date.now());
const [modulesLastChangeTime, setModulesLastChangeTime] = useState(Date.now());
// We use a ref here because the plugin state can change frequently when
// switching note since any plugin view might be rendered again. However we
// need this plugin state only in a click handler when exporting notes, and
// for that a ref is sufficient.
const pluginsRef = useRef(props.plugins);
const onMenuItemClick = useCallback((commandName: string) => {
void CommandService.instance().execute(commandName);
}, []);
@ -371,7 +377,7 @@ function useMenu(props: Props) {
(action: any) => props.dispatch(action),
module,
{
plugins: props.plugins,
plugins: pluginsRef.current,
customCss: props.customCss,
}
);
@ -905,7 +911,6 @@ function useMenu(props: Props) {
modulesLastChangeTime,
props['spellChecker.language'],
props['spellChecker.enabled'],
props.plugins,
props.customCss,
props.locale,
props.profileConfig,

View File

@ -39,7 +39,7 @@ class NavigatorComponent extends Component {
};
return (
<div style={this.props.style}>
<div style={this.props.style} className={this.props.className}>
<Screen style={screenStyle} {...screenProps} />
</div>
);

View File

@ -30,11 +30,11 @@ export interface ContextMenuItems {
[key: string]: ContextMenuItem;
}
export async function resourceInfo(options: ContextMenuOptions): Promise<any> {
export async function resourceInfo(options: ContextMenuOptions) {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const filePath = resource ? Resource.fullPath(resource) : null;
const resourcePath = resource ? Resource.fullPath(resource) : null;
const filename = resource ? (resource.filename ? resource.filename : resource.title) : options.filename ? options.filename : '';
return { resource, filePath, filename };
return { resource, resourcePath, filename };
}
export function textToDataUri(text: string, mime: string): string {

View File

@ -1,3 +1,5 @@
import * as React from 'react';
import { useMemo, useEffect, useState, useRef, useCallback } from 'react';
import { AppState } from '../../app.reducer';
import eventManager from '@joplin/lib/eventManager';
import NoteListUtils from '../utils/NoteListUtils';
@ -11,12 +13,12 @@ import CommandService from '@joplin/lib/services/CommandService';
import shim from '@joplin/lib/shim';
import styled from 'styled-components';
import { themeStyle } from '@joplin/lib/theme';
const React = require('react');
const { ItemList } = require('../ItemList.min.js');
const { connect } = require('react-redux');
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import { Props } from './types';
import usePrevious from '../hooks/usePrevious';
const commands = [
require('./commands/focusElementNoteList'),
@ -29,50 +31,48 @@ const StyledRoot = styled.div`
border-right: 1px solid ${(props: any) => props.theme.dividerColor};
`;
class NoteListComponent extends React.Component {
constructor() {
super();
const itemAnchorRefs_: any = {
current: {},
};
CommandService.instance().componentRegisterCommands(this, commands);
export const itemAnchorRef = (itemId: string) => {
if (itemAnchorRefs_.current[itemId] && itemAnchorRefs_.current[itemId].current) return itemAnchorRefs_.current[itemId].current;
return null;
};
this.itemHeight = 34;
const NoteListComponent = (props: Props) => {
const [dragOverTargetNoteIndex, setDragOverTargetNoteIndex] = useState(null);
const [width, setWidth] = useState(0);
const [, setHeight] = useState(0);
this.state = {
dragOverTargetNoteIndex: null,
width: 0,
height: 0,
useEffect(() => {
itemAnchorRefs_.current = {};
CommandService.instance().registerCommands(commands);
return () => {
itemAnchorRefs_.current = {};
CommandService.instance().unregisterCommands(commands);
};
}, []);
this.noteListRef = React.createRef();
this.itemListRef = React.createRef();
this.itemAnchorRefs_ = {};
const itemHeight = 34;
this.renderItem = this.renderItem.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.noteItem_titleClick = this.noteItem_titleClick.bind(this);
this.noteItem_noteDragOver = this.noteItem_noteDragOver.bind(this);
this.noteItem_noteDrop = this.noteItem_noteDrop.bind(this);
this.noteItem_checkboxClick = this.noteItem_checkboxClick.bind(this);
this.noteItem_dragStart = this.noteItem_dragStart.bind(this);
this.onGlobalDrop_ = this.onGlobalDrop_.bind(this);
this.registerGlobalDragEndEvent_ = this.registerGlobalDragEndEvent_.bind(this);
this.unregisterGlobalDragEndEvent_ = this.unregisterGlobalDragEndEvent_.bind(this);
this.itemContextMenu = this.itemContextMenu.bind(this);
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
}
const focusItemIID_ = useRef<any>(null);
const noteListRef = useRef(null);
const itemListRef = useRef(null);
style() {
if (this.styleCache_ && this.styleCache_[this.props.themeId]) return this.styleCache_[this.props.themeId];
let globalDragEndEventRegistered_ = false;
const theme = themeStyle(this.props.themeId);
const style = useMemo(() => {
const theme = themeStyle(props.themeId);
const style = {
return {
root: {
backgroundColor: theme.backgroundColor,
},
listItem: {
maxWidth: '100%',
height: this.itemHeight,
height: itemHeight,
boxSizing: 'border-box',
display: 'flex',
alignItems: 'stretch',
@ -99,76 +99,71 @@ class NoteListComponent extends React.Component {
textDecoration: 'line-through',
},
};
}, [props.themeId, itemHeight]);
this.styleCache_ = {};
this.styleCache_[this.props.themeId] = style;
return style;
}
itemContextMenu(event: any) {
const itemContextMenu = useCallback((event: any) => {
const currentItemId = event.currentTarget.getAttribute('data-id');
if (!currentItemId) return;
let noteIds = [];
if (this.props.selectedNoteIds.indexOf(currentItemId) < 0) {
if (props.selectedNoteIds.indexOf(currentItemId) < 0) {
noteIds = [currentItemId];
} else {
noteIds = this.props.selectedNoteIds;
noteIds = props.selectedNoteIds;
}
if (!noteIds.length) return;
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: this.props.notes,
dispatch: this.props.dispatch,
watchedNoteFiles: this.props.watchedNoteFiles,
plugins: this.props.plugins,
inConflictFolder: this.props.selectedFolderId === Folder.conflictFolderId(),
customCss: this.props.customCss,
notes: props.notes,
dispatch: props.dispatch,
watchedNoteFiles: props.watchedNoteFiles,
plugins: props.plugins,
inConflictFolder: props.selectedFolderId === Folder.conflictFolderId(),
customCss: props.customCss,
});
menu.popup(bridge().window());
}
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles,props.plugins, props.selectedFolderId, props.customCss]);
onGlobalDrop_() {
this.unregisterGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: null });
}
const onGlobalDrop_ = () => {
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
};
registerGlobalDragEndEvent_() {
if (this.globalDragEndEventRegistered_) return;
this.globalDragEndEventRegistered_ = true;
document.addEventListener('dragend', this.onGlobalDrop_);
}
const registerGlobalDragEndEvent_ = () => {
if (globalDragEndEventRegistered_) return;
globalDragEndEventRegistered_ = true;
document.addEventListener('dragend', onGlobalDrop_);
};
unregisterGlobalDragEndEvent_() {
this.globalDragEndEventRegistered_ = false;
document.removeEventListener('dragend', this.onGlobalDrop_);
}
const unregisterGlobalDragEndEvent_ = () => {
globalDragEndEventRegistered_ = false;
document.removeEventListener('dragend', onGlobalDrop_);
};
dragTargetNoteIndex_(event: any) {
return Math.abs(Math.round((event.clientY - this.itemListRef.current.offsetTop() + this.itemListRef.current.offsetScroll()) / this.itemHeight));
}
const dragTargetNoteIndex_ = (event: any) => {
return Math.abs(Math.round((event.clientY - itemListRef.current.offsetTop() + itemListRef.current.offsetScroll()) / itemHeight));
};
noteItem_noteDragOver(event: any) {
if (this.props.notesParentType !== 'Folder') return;
const noteItem_noteDragOver = (event: any) => {
if (props.notesParentType !== 'Folder') return;
const dt = event.dataTransfer;
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
const newIndex = this.dragTargetNoteIndex_(event);
if (this.state.dragOverTargetNoteIndex === newIndex) return;
this.registerGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: newIndex });
const newIndex = dragTargetNoteIndex_(event);
if (dragOverTargetNoteIndex === newIndex) return;
registerGlobalDragEndEvent_();
setDragOverTargetNoteIndex(newIndex);
}
}
};
async noteItem_noteDrop(event: any) {
if (this.props.notesParentType !== 'Folder') return;
const noteItem_noteDrop = async (event: any) => {
if (props.notesParentType !== 'Folder') return;
if (this.props.noteSortOrder !== 'order') {
if (props.noteSortOrder !== 'order') {
const doIt = await bridge().showConfirmMessageBox(_('To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"', _('Custom order'), _('View'), _('Sort notes by')), {
buttons: [_('Do it now'), _('Cancel')],
});
@ -181,17 +176,17 @@ class NoteListComponent extends React.Component {
// TODO: check that parent type is folder
const dt = event.dataTransfer;
this.unregisterGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: null });
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
const targetNoteIndex = this.dragTargetNoteIndex_(event);
const targetNoteIndex = dragTargetNoteIndex_(event);
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
void Note.insertNotesAt(this.props.selectedFolderId, noteIds, targetNoteIndex);
}
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex);
};
async noteItem_checkboxClick(event: any, item: any) {
const noteItem_checkboxClick = async (event: any, item: any) => {
const checked = event.target.checked;
const newNote = {
id: item.id,
@ -199,37 +194,37 @@ class NoteListComponent extends React.Component {
};
await Note.save(newNote, { userSideValidation: true });
eventManager.emit('todoToggle', { noteId: item.id, note: newNote });
}
};
async noteItem_titleClick(event: any, item: any) {
const noteItem_titleClick = async (event: any, item: any) => {
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_TOGGLE',
id: item.id,
});
} else if (event.shiftKey) {
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_EXTEND',
id: item.id,
});
} else {
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT',
id: item.id,
});
}
}
};
noteItem_dragStart(event: any) {
const noteItem_dragStart = (event: any) => {
let noteIds = [];
// Here there is two cases:
// - If multiple notes are selected, we drag the group
// - If only one note is selected, we drag the note that was clicked on (which might be different from the currently selected note)
if (this.props.selectedNoteIds.length >= 2) {
noteIds = this.props.selectedNoteIds;
if (props.selectedNoteIds.length >= 2) {
noteIds = props.selectedNoteIds;
} else {
const clickedNoteId = event.currentTarget.getAttribute('data-id');
if (clickedNoteId) noteIds.push(clickedNoteId);
@ -240,61 +235,66 @@ class NoteListComponent extends React.Component {
event.dataTransfer.setDragImage(new Image(), 1, 1);
event.dataTransfer.clearData();
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
}
};
renderItem(item: any, index: number) {
const renderItem = useCallback((item: any, index: number) => {
const highlightedWords = () => {
if (this.props.notesParentType === 'Search') {
const query = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
if (props.notesParentType === 'Search') {
const query = BaseModel.byId(props.searches, props.selectedSearchId);
if (query) {
return this.props.highlightedWords;
return props.highlightedWords;
}
}
return [];
};
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
const ref = this.itemAnchorRefs_[item.id];
if (!itemAnchorRefs_.current[item.id]) itemAnchorRefs_.current[item.id] = React.createRef();
const ref = itemAnchorRefs_.current[item.id];
return <NoteListItem
ref={ref}
key={item.id}
style={this.style()}
style={style}
item={item}
index={index}
themeId={this.props.themeId}
width={this.state.width}
height={this.itemHeight}
dragItemIndex={this.state.dragOverTargetNoteIndex}
themeId={props.themeId}
width={width}
height={itemHeight}
dragItemIndex={dragOverTargetNoteIndex}
highlightedWords={highlightedWords()}
isProvisional={this.props.provisionalNoteIds.includes(item.id)}
isSelected={this.props.selectedNoteIds.indexOf(item.id) >= 0}
isWatched={this.props.watchedNoteFiles.indexOf(item.id) < 0}
itemCount={this.props.notes.length}
onCheckboxClick={this.noteItem_checkboxClick}
onDragStart={this.noteItem_dragStart}
onNoteDragOver={this.noteItem_noteDragOver}
onNoteDrop={this.noteItem_noteDrop}
onTitleClick={this.noteItem_titleClick}
onContextMenu={this.itemContextMenu}
isProvisional={props.provisionalNoteIds.includes(item.id)}
isSelected={props.selectedNoteIds.indexOf(item.id) >= 0}
isWatched={props.watchedNoteFiles.indexOf(item.id) < 0}
itemCount={props.notes.length}
onCheckboxClick={noteItem_checkboxClick}
onDragStart={noteItem_dragStart}
onNoteDragOver={noteItem_noteDragOver}
onNoteDrop={noteItem_noteDrop}
onTitleClick={noteItem_titleClick}
onContextMenu={itemContextMenu}
/>;
}
}, [style, props.themeId, width, itemHeight, dragOverTargetNoteIndex, props.provisionalNoteIds, props.selectedNoteIds, props.watchedNoteFiles,
props.notes,
props.notesParentType,
props.searches,
props.selectedSearchId,
props.highlightedWords,
]);
itemAnchorRef(itemId: string) {
if (this.itemAnchorRefs_[itemId] && this.itemAnchorRefs_[itemId].current) return this.itemAnchorRefs_[itemId].current;
return null;
}
const previousSelectedNoteIds = usePrevious(props.selectedNoteIds, []);
const previousNotes = usePrevious(props.notes, []);
const previousVisible = usePrevious(props.visible, false);
componentDidUpdate(prevProps: any) {
if (prevProps.selectedNoteIds !== this.props.selectedNoteIds && this.props.selectedNoteIds.length === 1) {
const id = this.props.selectedNoteIds[0];
const doRefocus = this.props.notes.length < prevProps.notes.length;
useEffect(() => {
if (previousSelectedNoteIds !== props.selectedNoteIds && props.selectedNoteIds.length === 1) {
const id = props.selectedNoteIds[0];
const doRefocus = props.notes.length < previousNotes.length;
for (let i = 0; i < this.props.notes.length; i++) {
if (this.props.notes[i].id === id) {
this.itemListRef.current.makeItemIndexVisible(i);
for (let i = 0; i < props.notes.length; i++) {
if (props.notes[i].id === id) {
itemListRef.current.makeItemIndexVisible(i);
if (doRefocus) {
const ref = this.itemAnchorRef(id);
const ref = itemAnchorRef(id);
if (ref) ref.focus();
}
break;
@ -302,24 +302,24 @@ class NoteListComponent extends React.Component {
}
}
if (prevProps.visible !== this.props.visible) {
this.updateSizeState();
if (previousVisible !== props.visible) {
updateSizeState();
}
}
}, [previousSelectedNoteIds,previousNotes, previousVisible, props.selectedNoteIds, props.notes]);
scrollNoteIndex_(keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) {
const scrollNoteIndex_ = (keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) => {
if (keyCode === 33) {
// Page Up
noteIndex -= (this.itemListRef.current.visibleItemCount() - 1);
noteIndex -= (itemListRef.current.visibleItemCount() - 1);
} else if (keyCode === 34) {
// Page Down
noteIndex += (this.itemListRef.current.visibleItemCount() - 1);
noteIndex += (itemListRef.current.visibleItemCount() - 1);
} else if ((keyCode === 35 && ctrlKey) || (keyCode === 40 && metaKey)) {
// CTRL+End, CMD+Down
noteIndex = this.props.notes.length - 1;
noteIndex = props.notes.length - 1;
} else if ((keyCode === 36 && ctrlKey) || (keyCode === 38 && metaKey)) {
// CTRL+Home, CMD+Up
@ -334,31 +334,31 @@ class NoteListComponent extends React.Component {
noteIndex += 1;
}
if (noteIndex < 0) noteIndex = 0;
if (noteIndex > this.props.notes.length - 1) noteIndex = this.props.notes.length - 1;
if (noteIndex > props.notes.length - 1) noteIndex = props.notes.length - 1;
return noteIndex;
}
};
async onKeyDown(event: any) {
const onKeyDown = async (event: any) => {
const keyCode = event.keyCode;
const noteIds = this.props.selectedNoteIds;
const noteIds = props.selectedNoteIds;
if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode == 36)) {
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
let noteIndex = BaseModel.modelIndexById(props.notes, noteId);
noteIndex = this.scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
noteIndex = scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
const newSelectedNote = this.props.notes[noteIndex];
const newSelectedNote = props.notes[noteIndex];
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT',
id: newSelectedNote.id,
});
this.itemListRef.current.makeItemIndexVisible(noteIndex);
itemListRef.current.makeItemIndexVisible(noteIndex);
this.focusNoteId_(newSelectedNote.id);
focusNoteId_(newSelectedNote.id);
event.preventDefault();
}
@ -373,7 +373,7 @@ class NoteListComponent extends React.Component {
// SPACE
event.preventDefault();
const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
const notes = BaseModel.modelsByIds(props.notes, noteIds);
const todos = notes.filter((n: any) => !!n.is_todo);
if (!todos.length) return;
@ -382,7 +382,7 @@ class NoteListComponent extends React.Component {
await Note.save(toggledTodo);
}
this.focusNoteId_(todos[0].id);
focusNoteId_(todos[0].id);
}
if (keyCode === 9) {
@ -400,62 +400,63 @@ class NoteListComponent extends React.Component {
// Ctrl+A key
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_ALL',
});
}
}
};
focusNoteId_(noteId: string) {
const focusNoteId_ = (noteId: string) => {
// - We need to focus the item manually otherwise focus might be lost when the
// list is scrolled and items within it are being rebuilt.
// - We need to use an interval because when leaving the arrow pressed, the rendering
// of items might lag behind and so the ref is not yet available at this point.
if (!this.itemAnchorRef(noteId)) {
if (this.focusItemIID_) shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = shim.setInterval(() => {
if (this.itemAnchorRef(noteId)) {
this.itemAnchorRef(noteId).focus();
shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
if (!itemAnchorRef(noteId)) {
if (focusItemIID_.current) shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = shim.setInterval(() => {
if (itemAnchorRef(noteId)) {
itemAnchorRef(noteId).focus();
shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = null;
}
}, 10);
} else {
this.itemAnchorRef(noteId).focus();
itemAnchorRef(noteId).focus();
}
}
};
updateSizeState() {
this.setState({
width: this.noteListRef.current.clientWidth,
height: this.noteListRef.current.clientHeight,
});
}
const updateSizeState = () => {
setWidth(noteListRef.current.clientWidth);
setHeight(noteListRef.current.clientHeight);
};
resizableLayout_resize() {
this.updateSizeState();
}
const resizableLayout_resize = () => {
updateSizeState();
};
componentDidMount() {
this.props.resizableLayoutEventEmitter.on('resize', this.resizableLayout_resize);
this.updateSizeState();
}
useEffect(() => {
props.resizableLayoutEventEmitter.on('resize', resizableLayout_resize);
return () => {
props.resizableLayoutEventEmitter.off('resize', resizableLayout_resize);
};
}, [props.resizableLayoutEventEmitter]);
componentWillUnmount() {
if (this.focusItemIID_) {
shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
}
useEffect(() => {
updateSizeState();
this.props.resizableLayoutEventEmitter.off('resize', this.resizableLayout_resize);
return () => {
if (focusItemIID_.current) {
shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = null;
}
CommandService.instance().componentUnregisterCommands(commands);
};
}, []);
CommandService.instance().componentUnregisterCommands(commands);
}
const renderEmptyList = () => {
if (props.notes.length) return null;
renderEmptyList() {
if (this.props.notes.length) return null;
const theme = themeStyle(this.props.themeId);
const theme = themeStyle(props.themeId);
const padding = 10;
const emptyDivStyle = {
padding: `${padding}px`,
@ -464,39 +465,35 @@ class NoteListComponent extends React.Component {
backgroundColor: theme.backgroundColor,
fontFamily: theme.fontFamily,
};
// emptyDivStyle.width = emptyDivStyle.width - padding * 2;
// emptyDivStyle.height = emptyDivStyle.height - padding * 2;
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
}
return <div style={emptyDivStyle}>{props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
};
renderItemList(style: any) {
if (!this.props.notes.length) return null;
const renderItemList = () => {
if (!props.notes.length) return null;
return (
<ItemList
ref={this.itemListRef}
disabled={this.props.isInsertingNotes}
itemHeight={this.style().listItem.height}
ref={itemListRef}
disabled={props.isInsertingNotes}
itemHeight={style.listItem.height}
className={'note-list'}
items={this.props.notes}
style={style}
itemRenderer={this.renderItem}
onKeyDown={this.onKeyDown}
items={props.notes}
style={props.size}
itemRenderer={renderItem}
onKeyDown={onKeyDown}
/>
);
}
};
render() {
if (!this.props.size) throw new Error('props.size is required');
if (!props.size) throw new Error('props.size is required');
return (
<StyledRoot ref={this.noteListRef}>
{this.renderEmptyList()}
{this.renderItemList(this.props.size)}
</StyledRoot>
);
}
}
return (
<StyledRoot ref={noteListRef}>
{renderEmptyList()}
{renderItemList()}
</StyledRoot>
);
};
const mapStateToProps = (state: AppState) => {
return {

View File

@ -1,6 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer';
import { itemAnchorRef } from '../NoteList';
export const declaration: CommandDeclaration = {
name: 'focusElementNoteList',
@ -8,13 +9,13 @@ export const declaration: CommandDeclaration = {
parentLabel: () => _('Focus'),
};
export const runtime = (comp: any): CommandRuntime => {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, noteId: string = null) => {
noteId = noteId || stateUtils.selectedNoteId(context.state);
if (noteId) {
const ref = comp.itemAnchorRef(noteId);
const ref = itemAnchorRef(noteId);
if (ref) ref.focus();
}
},

View File

@ -0,0 +1,24 @@
import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
export interface Props {
themeId: any;
selectedNoteIds: string[];
notes: NoteEntity[];
dispatch: Function;
watchedNoteFiles: any[];
plugins: PluginStates;
selectedFolderId: string;
customCss: string;
notesParentType: string;
noteSortOrder: string;
resizableLayoutEventEmitter: any;
isInsertingNotes: boolean;
folders: FolderEntity[];
size: any;
searches: any[];
selectedSearchId: string;
highlightedWords: string[];
provisionalNoteIds: string[];
visible: boolean;
}

View File

@ -228,7 +228,7 @@ class RootComponent extends React.Component<Props, any> {
<StyleSheetContainer themeId={this.props.themeId}></StyleSheetContainer>
<MenuBar/>
<GlobalStyle/>
<Navigator style={navigatorStyle} screens={screens} />
<Navigator style={navigatorStyle} screens={screens} className={`profile-${this.props.profileConfigCurrentProfileId}`} />
{this.renderModalMessage(this.modalDialogProps())}
{this.renderDialogs()}
</ThemeProvider>
@ -245,6 +245,7 @@ const mapStateToProps = (state: AppState) => {
themeId: state.settings.theme,
needApiAuth: state.needApiAuth,
dialogs: state.dialogs,
profileConfigCurrentProfileId: state.profileConfig.currentProfileId,
};
};

View File

@ -1,4 +1,5 @@
import Logger from '@joplin/lib/Logger';
import time from '@joplin/lib/time';
const logger = Logger.create('BackOffHandler');
@ -9,23 +10,39 @@ const logger = Logger.create('BackOffHandler');
// When a plugin needs to be throttled that way a warning is displayed so
// that the author gets an opportunity to fix it.
//
// 2. If the plugin makes many simultaneous calls (over 100), the handler throws
// an exception to stop the plugin. In that case the plugin will be broken,
// but most plugins will not get this error anyway because call are usually
// made in sequence. It might reveal a bug though - for example if the plugin
// 2. If the plugin makes many simultaneous calls, the handler throws an
// exception to stop the plugin. In that case the plugin will be broken, but
// most plugins will not get this error anyway because call are usually made
// in sequence. It might reveal a bug though - for example if the plugin
// makes a call every 1 second, but does not wait for the response (or assume
// the response will come in less than one second). In that case, the back
// off intervals combined with the incorrect code will make the plugin fail.
export default class BackOffHandler {
private backOffIntervals_ = Array(100).fill(0).concat([0, 1, 1, 2, 3, 5, 8]);
// The current logic is:
//
// - Up to 200 calls per 10 seconds without restrictions
// - For calls 200 to 300, a 1 second wait time is applied
// - Over 300 calls, a 2 seconds wait time is applied
// - After 10 seconds without making any call, the limits are reset (back to
// 0 second between calls).
//
// If more than 50 simultaneous calls are being throttled, it's a bug in the
// plugin (not waiting for API responses), so we stop responding and throw
// an error.
private backOffIntervals_ =
Array(200).fill(0).concat(
Array(100).fill(1)).concat(
[2]);
private lastRequestTime_ = 0;
private pluginId_: string;
private resetBackOffInterval_ = (this.backOffIntervals_[this.backOffIntervals_.length - 1] + 1) * 1000;
private resetBackOffInterval_ = 10 * 1000; // (this.backOffIntervals_[this.backOffIntervals_.length - 1] + 1) * 1000;
private backOffIndex_ = 0;
private waitCount_ = 0;
private maxWaitCount_ = 100;
private maxWaitCount_ = 50;
public constructor(pluginId: string) {
this.pluginId_ = pluginId;
@ -51,21 +68,13 @@ export default class BackOffHandler {
this.waitCount_++;
// For now don't actually apply a backoff and don't abort.
logger.warn(`Plugin ${this.pluginId_}: Applying a backoff of ${interval} seconds due to frequent plugin API calls. Consider reducing the number of calls, caching the data, or requesting more data per call. API call was: `, path, args, `[Wait count: ${this.waitCount_}]`);
if (this.waitCount_ > this.maxWaitCount_) logger.error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API calls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
await time.sleep(interval);
this.waitCount_--;
// if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
// await time.sleep(interval);
// this.waitCount_--;
}
}

View File

@ -157,6 +157,8 @@ export default class PluginRunner extends BasePluginRunner {
const debugMappedArgs = fullPath.includes('setHtml') ? '<hidden>' : mappedArgs;
logger.debug(`Got message (3): ${fullPath}`, debugMappedArgs);
this.recordCallStat(plugin.id);
try {
await this.backOffHandler(plugin.id).wait(fullPath, debugMappedArgs);
} catch (error) {

View File

@ -86,7 +86,7 @@ class Dropdown extends React.Component {
if (this.props.labelTransform && this.props.labelTransform === 'trim') headerLabel = headerLabel.trim();
const closeList = () => {
if (this.props.onClose()) this.props.onClose();
if (this.props.onClose) this.props.onClose();
this.setState({ listVisible: false });
};
@ -116,7 +116,7 @@ class Dropdown extends React.Component {
onPress={() => {
this.updateHeaderCoordinates();
this.setState({ listVisible: true });
if (this.props.onOpen()) this.props.onOpen();
if (this.props.onOpen) this.props.onOpen();
}}
>
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>

View File

@ -492,7 +492,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 81;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
@ -521,7 +521,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 81;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
@ -667,7 +667,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 81;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -698,7 +698,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 81;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;

View File

@ -2,7 +2,7 @@
"name": "@joplin/app-mobile",
"description": "Joplin for Mobile",
"license": "MIT",
"version": "0.8.9",
"version": "2.8.0",
"private": true,
"scripts": {
"start": "react-native start --reset-cache",
@ -15,8 +15,8 @@
"postinstall": "jetify && yarn run build"
},
"dependencies": {
"@joplin/lib": "~2.6",
"@joplin/renderer": "~2.6",
"@joplin/lib": "~2.8",
"@joplin/renderer": "~2.8",
"@react-native-community/clipboard": "^1.5.0",
"@react-native-community/datetimepicker": "^3.0.3",
"@react-native-community/geolocation": "^2.0.2",
@ -75,7 +75,7 @@
"@codemirror/lang-markdown": "^0.18.4",
"@codemirror/state": "^0.18.7",
"@codemirror/view": "^0.18.19",
"@joplin/tools": "~2.6",
"@joplin/tools": "~2.8",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/node": "^14.14.6",

View File

@ -36,6 +36,50 @@ function shimInit() {
return temp;
};
// This function can be used to debug "Network Request Failed" errors. It
// uses the native XMLHttpRequest which is more likely to get the proper
// response and error message.
shim.debugFetch = async (url, options = null) => {
options = {
method: 'GET',
headers: {},
...options,
};
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method, url, true);
for (const [key, value] of Object.entries(options.headers)) {
xhr.setRequestHeader(key, value);
}
xhr.onload = function() {
console.info('======================== XHR RESPONSE');
console.info(xhr.getAllResponseHeaders());
console.info('-------------------------------------');
// console.info(xhr.responseText);
console.info('======================== XHR RESPONSE');
resolve(xhr.responseText);
};
xhr.onerror = function() {
console.info('======================== XHR ERROR');
console.info(xhr.getAllResponseHeaders());
console.info('-------------------------------------');
console.info(xhr.responseText);
console.info('======================== XHR ERROR');
reject(new Error(xhr.responseText));
};
// TODO: Send POST data here if needed
xhr.send();
});
};
shim.fetch = async function(url, options = null) {
// The native fetch() throws an uncatchable error that crashes the
// app if calling it with an invalid URL such as '//.resource' or

View File

@ -738,7 +738,7 @@ export default class BaseApplication {
Setting.setConstant('tempDir', tempDir);
Setting.setConstant('pluginDataDir', `${profileDir}/plugin-data`);
Setting.setConstant('cacheDir', cacheDir);
Setting.setConstant('pluginDir', `${profileDir}/plugins`);
Setting.setConstant('pluginDir', `${rootProfileDir}/plugins`);
SyncTargetRegistry.addClass(SyncTargetNone);
SyncTargetRegistry.addClass(SyncTargetFilesystem);
@ -811,8 +811,12 @@ export default class BaseApplication {
appLogger.info(`Client ID: ${Setting.value('clientId')}`);
if (Setting.value('firstStart')) {
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
// If it's a sub-profile, the locale must come from the root
// profile.
if (!Setting.value('isSubProfile')) {
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
}
Setting.skipDefaultMigrations();
@ -825,9 +829,10 @@ export default class BaseApplication {
Setting.setValue('firstStart', 0);
} else {
Setting.applyDefaultMigrations();
setLocale(Setting.value('locale'));
}
setLocale(Setting.value('locale'));
if (Setting.value('env') === Env.Dev) {
// Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
// Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');

View File

@ -124,8 +124,15 @@ class FileApiDriverDropbox {
if (!options.responseFormat) options.responseFormat = 'text';
try {
// IMPORTANT:
//
// We cannot use POST here, because iOS (as of version 14?) doesn't
// support POST requests with an empty body:
//
// https://www.dropboxforum.com/t5/Dropbox-API-Support-Feedback/Error-1017-quot-cannot-parse-response-quot/td-p/589595
const response = await this.api().exec(
'POST',
'GET',
'files/download',
null,
{

View File

@ -9,6 +9,7 @@ import Tag from './Tag';
import ItemChange from './ItemChange';
import Resource from './Resource';
import { ResourceEntity } from '../services/database/types';
import { toForwardSlashes } from '../path-utils';
const ArrayUtils = require('../ArrayUtils.js');
async function allItems() {
@ -259,7 +260,8 @@ describe('models/Note', function() {
const t1 = r1.updated_time;
const t2 = r2.updated_time;
const resourceDirE = markdownUtils.escapeLinkUrl(resourceDir);
const resourceDirE = markdownUtils.escapeLinkUrl(toForwardSlashes(resourceDir));
const fileProtocol = `file://${process.platform === 'win32' ? '/' : ''}`;
const testCases = [
[
@ -285,17 +287,17 @@ describe('models/Note', function() {
[
true,
`![](:/${r1.id})`,
`![](file://${resourceDirE}/${r1.id}.jpg?t=${t1})`,
`![](${fileProtocol}${resourceDirE}/${r1.id}.jpg?t=${t1})`,
],
[
true,
`![](:/${r1.id}) ![](:/${r1.id}) ![](:/${r2.id})`,
`![](file://${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDirE}/${r2.id}.jpg?t=${t2})`,
`![](${fileProtocol}${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](${fileProtocol}${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](${fileProtocol}${resourceDirE}/${r2.id}.jpg?t=${t2})`,
],
[
true,
`![](:/${r3.id})`,
`![](file://${resourceDirE}/${r3.id}.pdf)`,
`![](${fileProtocol}${resourceDirE}/${r3.id}.pdf)`,
],
];

View File

@ -11,6 +11,7 @@ import Tag from './Tag';
const { sprintf } = require('sprintf-js');
import Resource from './Resource';
import syncDebugLog from '../services/synchronizer/syncDebugLog';
import { toFileProtocolPath, toForwardSlashes } from '../path-utils';
const { pregQuote, substrWithEllipsis } = require('../string-utils.js');
const { _ } = require('../locale');
const ArrayUtils = require('../ArrayUtils.js');
@ -167,7 +168,7 @@ export default class Note extends BaseItem {
// change, the preview is updated inside the note. This is not
// needed for other resources since they are simple links.
const timestampParam = isImage ? `?t=${resource.updated_time}` : '';
const resourcePath = options.useAbsolutePaths ? `file://${Resource.fullPath(resource)}${timestampParam}` : Resource.relativePath(resource);
const resourcePath = options.useAbsolutePaths ? toFileProtocolPath(Resource.fullPath(resource)) + timestampParam : Resource.relativePath(resource);
body = body.replace(new RegExp(`:/${id}`, 'gi'), markdownUtils.escapeLinkUrl(resourcePath));
}
@ -181,10 +182,14 @@ export default class Note extends BaseItem {
useAbsolutePaths: false,
}, options);
const resourceDir = toForwardSlashes(Setting.value('resourceDir'));
let pathsToTry = [];
if (options.useAbsolutePaths) {
pathsToTry.push(`file://${Setting.value('resourceDir')}`);
pathsToTry.push(`file://${shim.pathRelativeToCwd(Setting.value('resourceDir'))}`);
pathsToTry.push(`file://${resourceDir}`);
pathsToTry.push(`file:///${resourceDir}`);
pathsToTry.push(`file://${shim.pathRelativeToCwd(resourceDir)}`);
pathsToTry.push(`file:///${shim.pathRelativeToCwd(resourceDir)}`);
} else {
pathsToTry.push(Resource.baseRelativeDirectoryPath());
}

View File

@ -15,8 +15,9 @@ const switchToSubProfileSettings = async () => {
const rootProfileDir = Setting.value('profileDir');
const profileConfigPath = `${rootProfileDir}/profiles.json`;
let profileConfig = defaultProfileConfig();
profileConfig = createNewProfile(profileConfig, 'Sub-profile');
profileConfig.currentProfile = 1;
const { newConfig, newProfile } = createNewProfile(profileConfig, 'Sub-profile');
profileConfig = newConfig;
profileConfig.currentProfileId = newProfile.id;
await saveProfileConfig(profileConfigPath, profileConfig);
const { profileDir } = await initProfile(rootProfileDir);
await mkdirp(profileDir);
@ -272,7 +273,7 @@ describe('models/Setting', function() {
expect(Setting.value('style.editor.contentMaxWidth')).toBe(600); // Changed
}));
it('should load sub-profile settings', async () => {
it('should load sub-profile settings - 1', async () => {
await Setting.reset();
Setting.setValue('locale', 'fr_FR'); // Global setting
@ -293,7 +294,7 @@ describe('models/Setting', function() {
expect((await Setting.loadOne('sync.target')).value).toBe(undefined);
});
it('should save sub-profile settings', async () => {
it('should save sub-profile settings - 2', async () => {
await Setting.reset();
Setting.setValue('locale', 'fr_FR'); // Global setting
Setting.setValue('theme', Setting.THEME_DARK); // Global setting

View File

@ -243,7 +243,7 @@ class Setting extends BaseModel {
public static SYNC_UPGRADE_STATE_SHOULD_DO = 1; // Should be upgraded, but waiting for user to confirm
public static SYNC_UPGRADE_STATE_MUST_DO = 2; // Must be upgraded - on next restart, the upgrade will start
public static custom_css_files = {
public static customCssFilenames = {
JOPLIN_APP: 'userchrome.css',
RENDERED_MARKDOWN: 'userstyle.css',
};
@ -1177,7 +1177,7 @@ class Setting extends BaseModel {
'style.editor.contentMaxWidth': { value: 0, type: SettingItemType.Int, public: true, storage: SettingStorage.File, isGlobal: true,appTypes: [AppType.Desktop], section: 'appearance', label: () => _('Editor maximum width'), description: () => _('Set it to 0 to make it take the complete available space. Recommended width is 600.') },
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, public: false, appTypes: [AppType.Desktop] },
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, isGlobal: true, public: false, appTypes: [AppType.Desktop] },
// TODO: Is there a better way to do this? The goal here is to simply have
// a way to display a link to the customizable stylesheets, not for it to
@ -1187,12 +1187,10 @@ class Setting extends BaseModel {
'style.customCss.renderedMarkdown': {
value: null,
onClick: () => {
const dir = Setting.value('profileDir');
const filename = Setting.custom_css_files.RENDERED_MARKDOWN;
const filepath = `${dir}/${filename}`;
const defaultContents = '/* For styling the rendered Markdown */';
shim.openOrCreateFile(filepath, defaultContents);
shim.openOrCreateFile(
this.customCssFilePath(Setting.customCssFilenames.RENDERED_MARKDOWN),
'/* For styling the rendered Markdown */'
);
},
type: SettingItemType.Button,
public: true,
@ -1206,12 +1204,10 @@ class Setting extends BaseModel {
'style.customCss.joplinApp': {
value: null,
onClick: () => {
const dir = Setting.value('profileDir');
const filename = Setting.custom_css_files.JOPLIN_APP;
const filepath = `${dir}/${filename}`;
const defaultContents = `/* For styling the entire Joplin app (except the rendered Markdown, which is defined in \`${Setting.custom_css_files.RENDERED_MARKDOWN}\`) */`;
shim.openOrCreateFile(filepath, defaultContents);
shim.openOrCreateFile(
this.customCssFilePath(Setting.customCssFilenames.JOPLIN_APP),
`/* For styling the entire Joplin app (except the rendered Markdown, which is defined in \`${Setting.customCssFilenames.RENDERED_MARKDOWN}\`) */`
);
},
type: SettingItemType.Button,
public: true,
@ -1527,6 +1523,10 @@ class Setting extends BaseModel {
}
}
public static customCssFilePath(filename: string): string {
return `${this.value('rootProfileDir')}/${filename}`;
}
public static skipDefaultMigrations() {
logger.info('Skipping all default migrations...');

View File

@ -146,6 +146,10 @@ export function toSystemSlashes(path: string, os: string = null) {
return path.replace(/\\/g, '/');
}
export function toForwardSlashes(path: string) {
return toSystemSlashes(path, 'linux');
}
export function rtrimSlashes(path: string) {
return path.replace(/[\/\\]+$/, '');
}

View File

@ -199,6 +199,18 @@ export default class CommandService extends BaseService {
command.runtime = runtime;
}
public registerCommands(commands: any[]) {
for (const command of commands) {
CommandService.instance().registerRuntime(command.declaration.name, command.runtime());
}
}
public unregisterCommands(commands: any[]) {
for (const command of commands) {
CommandService.instance().unregisterRuntime(command.declaration.name);
}
}
public componentRegisterCommands(component: any, commands: any[]) {
for (const command of commands) {
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(component));

View File

@ -4,7 +4,21 @@ import Global from './api/Global';
export default abstract class BasePluginRunner extends BaseService {
async run(plugin: Plugin, sandbox: Global): Promise<void> {
// A dictionary with the plugin ID as key. Then each entry has a list
// of timestamp/call counts.
//
// 'org.joplinapp.plugins.ExamplePlugin': {
// 1650375620: 5, // 5 calls at second 1650375620
// 1650375621: 19, // 19 calls at second 1650375621
// 1650375623: 12,
// },
// 'org.joplinapp.plugins.AnotherOne': {
// 1650375620: 1,
// 1650375623: 4,
// };
private callStats_: Record<string, Record<number, number>> = {};
public async run(plugin: Plugin, sandbox: Global): Promise<void> {
throw new Error(`Not implemented: ${plugin} / ${sandbox}`);
}
@ -12,4 +26,26 @@ export default abstract class BasePluginRunner extends BaseService {
throw new Error('Not implemented: waitForSandboxCalls');
}
protected recordCallStat(pluginId: string) {
const timeSeconds = Math.floor(Date.now() / 1000);
if (!this.callStats_[pluginId]) this.callStats_[pluginId] = {};
if (!this.callStats_[pluginId][timeSeconds]) this.callStats_[pluginId][timeSeconds] = 0;
this.callStats_[pluginId][timeSeconds]++;
}
// Duration in seconds
public callStatsSummary(pluginId: string, duration: number): number[] {
const output: number[] = [];
const startTime = Math.floor(Date.now() / 1000 - duration);
const endTime = startTime + duration;
for (let t = startTime; t <= endTime; t++) {
const callCount = this.callStats_[pluginId][t];
output.push(callCount ? callCount : 0);
}
return output;
}
}

View File

@ -315,6 +315,10 @@ export default class PluginService extends BaseService {
return settings[pluginId].enabled !== false;
}
public callStatsSummary(pluginId: string, duration: number) {
return this.runner_.callStatsSummary(pluginId, duration);
}
public async loadAndRunPlugins(pluginDirOrPaths: string | string[], settings: PluginSettings, devMode: boolean = false) {
let pluginPaths = [];

View File

@ -33,14 +33,24 @@ export interface PluginStates {
[key: string]: PluginState;
}
export interface PluginHtmlContent {
[viewId: string]: string;
}
export interface PluginHtmlContents {
[pluginId: string]: PluginHtmlContent;
}
export interface State {
plugins: PluginStates;
pluginHtmlContents: PluginHtmlContents;
}
export const stateRootKey = 'pluginService';
export const defaultState: State = {
plugins: {},
pluginHtmlContents: {},
};
export const utils = {
@ -139,7 +149,12 @@ const reducer = (draftRoot: Draft<any>, action: any) => {
case 'PLUGIN_VIEW_PROP_SET':
(draft.plugins[action.pluginId].views[action.id] as any)[action.name] = action.value;
if (action.name !== 'html') {
(draft.plugins[action.pluginId].views[action.id] as any)[action.name] = action.value;
} else {
draft.pluginHtmlContents[action.pluginId] ??= {};
draft.pluginHtmlContents[action.pluginId][action.id] = action.value;
}
break;
case 'PLUGIN_VIEW_PROP_PUSH':

View File

@ -1,7 +1,7 @@
import { writeFile } from 'fs-extra';
import { createNewProfile, getProfileFullPath, loadProfileConfig, saveProfileConfig } from '.';
import { createNewProfile, getProfileFullPath, loadProfileConfig, migrateProfileConfig, saveProfileConfig } from '.';
import { tempFilePath } from '../../testing/test-utils';
import { defaultProfile, defaultProfileConfig, ProfileConfig } from './types';
import { CurrentProfileVersion, defaultProfile, defaultProfileConfig, DefaultProfileId, Profile, ProfileConfig } from './types';
describe('profileConfig/index', () => {
@ -17,7 +17,7 @@ describe('profileConfig/index', () => {
profiles: [
{
name: 'Testing',
path: '.',
id: DefaultProfileId,
},
],
};
@ -26,12 +26,12 @@ describe('profileConfig/index', () => {
const loadedConfig = await loadProfileConfig(filePath);
const expected: ProfileConfig = {
version: 1,
currentProfile: 0,
version: CurrentProfileVersion,
currentProfileId: DefaultProfileId,
profiles: [
{
name: 'Testing',
path: '.',
id: DefaultProfileId,
},
],
};
@ -50,36 +50,71 @@ describe('profileConfig/index', () => {
});
it('should get a profile full path', async () => {
const profile1 = {
const profile1: Profile = {
...defaultProfile(),
path: 'profile-abcd',
id: 'abcd',
};
const profile2 = {
const profile2: Profile = {
...defaultProfile(),
path: '.',
};
const profile3 = {
...defaultProfile(),
path: 'profiles/pro/',
id: DefaultProfileId,
};
expect(getProfileFullPath(profile1, '/test/root')).toBe('/test/root/profile-abcd');
expect(getProfileFullPath(profile2, '/test/root')).toBe('/test/root');
expect(getProfileFullPath(profile3, '/test/root')).toBe('/test/root/profiles/pro');
});
it('should create a new profile', async () => {
let config = defaultProfileConfig();
config = createNewProfile(config, 'new profile 1');
config = createNewProfile(config, 'new profile 2');
const r1 = createNewProfile(config, 'new profile 1');
const r2 = createNewProfile(r1.newConfig, 'new profile 2');
config = r2.newConfig;
expect(config.profiles.length).toBe(3);
expect(config.profiles[1].name).toBe('new profile 1');
expect(config.profiles[2].name).toBe('new profile 2');
expect(config.profiles[1].path).not.toBe(config.profiles[2].path);
expect(config.profiles[1].id).not.toBe(config.profiles[2].id);
});
it('should migrate profile config - version 1 to 2', async () => {
const migrated1 = migrateProfileConfig({
'version': 1,
'currentProfile': 2,
'profiles': [
{
'name': 'Default',
'path': '.',
},
{
'name': 'sub1',
'path': 'profile-sjn25kuh',
},
{
'name': 'sub2',
'path': 'profile-yufzkns3',
},
],
}, 2);
expect(migrated1).toEqual({
'version': 2,
'currentProfileId': 'yufzkns3',
'profiles': [
{
'name': 'Default',
'id': 'default',
},
{
'name': 'sub1',
'id': 'sjn25kuh',
},
{
'name': 'sub2',
'id': 'yufzkns3',
},
],
});
});
});

View File

@ -1,8 +1,34 @@
import { rtrimSlashes, trimSlashes } from '../../path-utils';
import { rtrimSlashes } from '../../path-utils';
import shim from '../../shim';
import { defaultProfile, defaultProfileConfig, Profile, ProfileConfig } from './types';
import { CurrentProfileVersion, defaultProfile, defaultProfileConfig, DefaultProfileId, Profile, ProfileConfig } from './types';
import { customAlphabet } from 'nanoid/non-secure';
export const migrateProfileConfig = (profileConfig: any, toVersion: number): ProfileConfig => {
let version = 2;
while (profileConfig.version < toVersion) {
if (profileConfig.version === 1) {
for (const profile of profileConfig.profiles) {
if (profile.path === '.') {
profile.id = DefaultProfileId;
} else {
profile.id = profile.path.split('-').pop();
}
delete profile.path;
}
const currentProfile = profileConfig.profiles[profileConfig.currentProfile];
profileConfig.currentProfileId = currentProfile.id;
delete profileConfig.currentProfile;
}
profileConfig.version = version;
version++;
}
return profileConfig;
};
export const loadProfileConfig = async (profileConfigPath: string): Promise<ProfileConfig> => {
if (!(await shim.fsDriver().exists(profileConfigPath))) {
return defaultProfileConfig();
@ -10,9 +36,11 @@ export const loadProfileConfig = async (profileConfigPath: string): Promise<Prof
try {
const configContent = await shim.fsDriver().readFile(profileConfigPath, 'utf8');
const parsed = JSON.parse(configContent) as ProfileConfig;
let parsed = JSON.parse(configContent) as ProfileConfig;
if (!parsed.profiles || !parsed.profiles.length) throw new Error(`Profile config should contain at least one profile: ${profileConfigPath}`);
parsed = migrateProfileConfig(parsed, CurrentProfileVersion);
const output: ProfileConfig = {
...defaultProfileConfig(),
...parsed,
@ -25,7 +53,7 @@ export const loadProfileConfig = async (profileConfigPath: string): Promise<Prof
};
}
if (output.currentProfile < 0 || output.currentProfile >= output.profiles.length) throw new Error(`Profile index out of range: ${output.currentProfile}`);
if (!output.profiles.find(p => p.id === output.currentProfileId)) throw new Error(`Current profile ID is invalid: ${output.currentProfileId}`);
return output;
} catch (error) {
error.message = `Could not parse profile configuration: ${profileConfigPath}: ${error.message}`;
@ -38,27 +66,39 @@ export const saveProfileConfig = async (profileConfigPath: string, config: Profi
};
export const getCurrentProfile = (config: ProfileConfig): Profile => {
return { ...config.profiles[config.currentProfile] };
return config.profiles.find(p => p.id === config.currentProfileId);
};
export const getProfileFullPath = (profile: Profile, rootProfilePath: string): string => {
let p = trimSlashes(profile.path);
if (p === '.') p = '';
return rtrimSlashes(`${rtrimSlashes(rootProfilePath)}/${p}`);
const folderName = profile.id === DefaultProfileId ? '' : `/profile-${profile.id}`;
return `${rtrimSlashes(rootProfilePath)}${folderName}`;
};
export const isSubProfile = (profile: Profile): boolean => {
return profile.id !== DefaultProfileId;
};
const profileIdGenerator = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8);
export const createNewProfile = (config: ProfileConfig, profileName: string) => {
const newConfig = {
const newConfig: ProfileConfig = {
...config,
profiles: config.profiles.slice(),
};
newConfig.profiles.push({
const newProfile: Profile = {
name: profileName,
path: `profile-${profileIdGenerator()}`,
});
id: profileIdGenerator(),
};
return newConfig;
newConfig.profiles.push(newProfile);
return {
newConfig: newConfig,
newProfile: newProfile,
};
};
export const profileIdByIndex = (config: ProfileConfig, index: number): string => {
return config.profiles[index].id;
};

View File

@ -1,16 +1,16 @@
import { getCurrentProfile, getProfileFullPath, loadProfileConfig } from '.';
import { getCurrentProfile, getProfileFullPath, isSubProfile, loadProfileConfig } from '.';
import Setting from '../../models/Setting';
export default async (rootProfileDir: string) => {
const profileConfig = await loadProfileConfig(`${rootProfileDir}/profiles.json`);
const profileDir = getProfileFullPath(getCurrentProfile(profileConfig), rootProfileDir);
const isSubProfile = profileConfig.currentProfile !== 0;
Setting.setConstant('isSubProfile', isSubProfile);
const isSub = isSubProfile(getCurrentProfile(profileConfig));
Setting.setConstant('isSubProfile', isSub);
Setting.setConstant('rootProfileDir', rootProfileDir);
Setting.setConstant('profileDir', profileDir);
return {
profileConfig,
profileDir,
isSubProfile,
isSubProfile: isSub,
};
};

View File

@ -1,25 +1,28 @@
export const DefaultProfileId = 'default';
export const CurrentProfileVersion = 2;
export interface Profile {
name: string;
path: string;
id: string;
}
export interface ProfileConfig {
version: number;
currentProfile: number;
currentProfileId: string;
profiles: Profile[];
}
export const defaultProfile = (): Profile => {
return {
name: 'Default',
path: '.',
id: DefaultProfileId,
};
};
export const defaultProfileConfig = (): ProfileConfig => {
return {
version: 1,
currentProfile: 0,
version: CurrentProfileVersion,
currentProfileId: DefaultProfileId,
profiles: [defaultProfile()],
};
};

View File

@ -7,7 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: MrKanister <pthrp_bnsrv@aleeas.com>\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: MrKanister <pueblos_spatulas@aleeas.com>\n"
"Language-Team: \n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
@ -402,7 +404,6 @@ msgid "Auto"
msgstr "Automatisch"
#: packages/server/src/services/TaskService.ts:39
#, fuzzy
msgid "Auto-add disabled accounts for deletion"
msgstr "Automatisches Hinzufügen deaktivierter Accounts zur Löschliste"
@ -1867,7 +1868,7 @@ msgstr "TLS-Zertifikatfehler ignorieren"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:102
msgid "Images"
msgstr ""
msgstr "Bilder"
#: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:170
#: packages/app-desktop/gui/MenuBar.tsx:484
@ -2745,9 +2746,8 @@ msgid "Or create an account."
msgstr "Oder erstelle ein Konto."
#: packages/app-desktop/gui/MenuBar.tsx:352
#, fuzzy
msgid "Other applications..."
msgstr "Beendet die Anwendung."
msgstr "Andere Anwendungen..."
#: packages/app-cli/app/command-import.js:27
msgid "Output format: %s"
@ -3254,14 +3254,12 @@ msgid "Select all"
msgstr "Alles auswählen"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:140
#, fuzzy
msgid "Select emoji..."
msgstr "Datum auswählen"
msgstr "Emoji auswählen..."
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:144
#, fuzzy
msgid "Select file..."
msgstr "Alles auswählen"
msgstr "Datei auswählen..."
#: packages/app-cli/app/command-server.js:38
msgid "Server is already running on port %d"

View File

@ -24,6 +24,8 @@ msgstr ""
"X-Generator: Poedit 2.4.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:565
msgid "- Camera: to allow taking a picture and attaching it to a note."
@ -275,11 +277,11 @@ msgstr "Agregar al diccionario"
#: packages/server/src/services/MustacheService.ts:183
#: packages/server/src/services/MustacheService.ts:307
msgid "Admin"
msgstr ""
msgstr "Administrador"
#: packages/server/src/routes/admin/dashboard.ts:10
msgid "Admin dashboard"
msgstr ""
msgstr "Tablero de Mandos de Administrador"
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:189
#: packages/app-desktop/gui/ClipperConfigScreen.tsx:147
@ -406,16 +408,15 @@ msgstr "Automático"
#: packages/server/src/services/TaskService.ts:39
msgid "Auto-add disabled accounts for deletion"
msgstr ""
msgstr "Añadir automáticamente las cuentas deshabilitadas para su eliminación"
#: packages/lib/models/Setting.ts:855
msgid "Auto-pair braces, parenthesis, quotations, etc."
msgstr "Autoemparejar llaves, paréntesis, comillas, etc."
#: packages/lib/models/Setting.ts:1195
#, fuzzy
msgid "Automatically check for updates"
msgstr "Comprobar actualizaciones..."
msgstr "Comprobar actualizaciones"
#: packages/lib/models/Setting.ts:772
msgid "Automatically switch theme to match system theme"
@ -725,7 +726,7 @@ msgstr "Completado: %s (%s)"
#: packages/server/src/services/TaskService.ts:37
msgid "Compress old changes"
msgstr ""
msgstr "Comprimir cambios antiguos"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:637
#: packages/app-mobile/components/side-menu-content.js:332
@ -787,9 +788,8 @@ msgid "Copy external link"
msgstr "Copiar enlace externo"
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:134
#, fuzzy
msgid "Copy image"
msgstr "Copiar token"
msgstr "Copiar imagen"
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:172
msgid "Copy Link Address"
@ -882,14 +882,12 @@ msgid "Create a notebook"
msgstr "Crea una libreta"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:161
#, fuzzy
msgid "Create notebook"
msgstr "Crea una libreta"
msgstr "Crea libreta"
#: packages/server/src/routes/admin/users.ts:171
#, fuzzy
msgid "Create user"
msgstr "Creado: %s"
msgstr "Crear usuario"
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:29
msgid "Created"
@ -981,7 +979,7 @@ msgstr "Oscuro"
#: packages/server/src/services/MustacheService.ts:136
msgid "Dashboard"
msgstr ""
msgstr "Tablero de Mandos"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:625
msgid "Database v%s"
@ -1038,14 +1036,12 @@ msgid "Delete attachment \"%s\"?"
msgstr "¿Borrar adjunto «%s»?"
#: packages/server/src/services/TaskService.ts:36
#, fuzzy
msgid "Delete expired sessions"
msgstr "Activar expresiones matemáticas"
msgstr "Borrar sesiones expiradas"
#: packages/server/src/services/TaskService.ts:31
#, fuzzy
msgid "Delete expired tokens"
msgstr "¿Borrar estas %d notas?"
msgstr "Borrar tokens expirados"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:88
msgid "Delete line"
@ -1366,12 +1362,12 @@ msgstr "Emacs"
#: packages/app-desktop/gui/SyncWizard/Dialog.tsx:236
#: packages/server/src/routes/admin/emails.ts:128
msgid "Email"
msgstr ""
msgstr "Email"
#: packages/server/src/routes/admin/emails.ts:112
#: packages/server/src/services/MustacheService.ts:152
msgid "Emails"
msgstr ""
msgstr "Emails"
#: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.tsx:194
msgid "emphasised text"
@ -1859,7 +1855,7 @@ msgstr "Ignorar errores en certificados TLS"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:102
msgid "Images"
msgstr ""
msgstr "Imágenes"
#: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:170
#: packages/app-desktop/gui/MenuBar.tsx:484
@ -2270,7 +2266,7 @@ msgstr "Cerrar sesión"
#: packages/server/src/services/MustacheService.ts:179
msgid "Logs"
msgstr ""
msgstr "Registros"
#: packages/app-desktop/gui/MenuBar.tsx:716
#: packages/app-mobile/components/screens/ConfigScreen.tsx:583
@ -2729,9 +2725,8 @@ msgid "Or create an account."
msgstr "Crea una nueva nota.O crea una cuenta."
#: packages/app-desktop/gui/MenuBar.tsx:352
#, fuzzy
msgid "Other applications..."
msgstr "Sale de la aplicación."
msgstr "Otras aplicaciones..."
#: packages/app-cli/app/command-import.js:27
msgid "Output format: %s"
@ -2935,19 +2930,19 @@ msgstr "Política de Privacidad"
#: packages/server/src/services/TaskService.ts:35
msgid "Process failed payment subscriptions"
msgstr ""
msgstr "Procesar las suscripciones de pago fallidas"
#: packages/server/src/services/TaskService.ts:33
msgid "Process oversized accounts"
msgstr ""
msgstr "Procesar cuentas de gran tamaño"
#: packages/server/src/services/TaskService.ts:38
msgid "Process user deletions"
msgstr ""
msgstr "Procesar eliminaciones de usuarios"
#: packages/server/src/routes/admin/users.ts:168
msgid "Profile"
msgstr ""
msgstr "Perfil"
#: packages/lib/versionInfo.ts:26
msgid "Profile Version: %s"
@ -3133,29 +3128,24 @@ msgid "S3"
msgstr "S3"
#: packages/lib/models/Setting.ts:547
#, fuzzy
msgid "S3 access key"
msgstr "Clave de acceso de AWS"
msgstr "Clave de acceso de S3"
#: packages/lib/models/Setting.ts:507
#, fuzzy
msgid "S3 bucket"
msgstr "AWS S3 bucket"
msgstr "S3 bucket"
#: packages/lib/models/Setting.ts:536
#, fuzzy
msgid "S3 region"
msgstr "Región de AWS"
msgstr "Región de S3"
#: packages/lib/models/Setting.ts:558
#, fuzzy
msgid "S3 secret key"
msgstr "Clave secreta de AWS"
msgstr "Clave secreta de S3"
#: packages/lib/models/Setting.ts:522
#, fuzzy
msgid "S3 URL"
msgstr "URL de AWS S3"
msgstr "URL de S3"
#: packages/app-desktop/gui/MainScreen/MainScreen.tsx:579
msgid ""
@ -3232,14 +3222,12 @@ msgid "Select all"
msgstr "Seleccionar todo"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:140
#, fuzzy
msgid "Select emoji..."
msgstr "Seleccione fecha"
msgstr "Seleccionar emoji..."
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:144
#, fuzzy
msgid "Select file..."
msgstr "Seleccionar todo"
msgstr "Seleccionar archivo..."
#: packages/app-cli/app/command-server.js:38
msgid "Server is already running on port %d"
@ -4272,14 +4260,12 @@ msgstr "Actualizar"
#: packages/server/src/routes/admin/users.ts:171
#: packages/server/src/routes/index/users.ts:89
#, fuzzy
msgid "Update profile"
msgstr "Exportar perfil"
msgstr "Actualizar perfil"
#: packages/server/src/services/TaskService.ts:32
#, fuzzy
msgid "Update total sizes"
msgstr "Elementos locales actualizados: %d."
msgstr "Actualizar tamaños totales"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:208
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:209
@ -4388,7 +4374,7 @@ msgstr ""
#: packages/server/src/services/MustacheService.ts:144
msgid "User deletions"
msgstr ""
msgstr "Eliminaciones de usuarios"
#: packages/server/src/routes/admin/users.ts:107
#: packages/server/src/services/MustacheService.ts:140

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2022-03-26\n"
"Last-Translator: mrkaato\n"
"PO-Revision-Date: 2022-04-15\n"
"Last-Translator: mrkaato0\n"
"Language-Team: \n"
"Language: fi_FI\n"
"MIME-Version: 1.0\n"

View File

@ -122,6 +122,7 @@ async function main() {
await updatePackageVersion(`${rootDir}/packages/app-cli/package.json`, majorMinorVersion, options);
await updatePackageVersion(`${rootDir}/packages/app-desktop/package.json`, majorMinorVersion, options);
await updatePackageVersion(`${rootDir}/packages/app-mobile/package.json`, majorMinorVersion, options);
await updatePackageVersion(`${rootDir}/packages/generator-joplin/package.json`, majorMinorVersion, options);
await updatePackageVersion(`${rootDir}/packages/htmlpack/package.json`, majorMinorVersion, options);
await updatePackageVersion(`${rootDir}/packages/lib/package.json`, majorMinorVersion, options);

View File

@ -117,6 +117,11 @@
"urlWebsite": "https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&mtm_kwd=joplinapp&mtm_source=joplinapp-webseite&mtm_medium=banner",
"title": "Hosting.de",
"imageName": "HostingDe.png"
},
{
"url": "https://residence-greece.com/",
"title": "Greece Golden Visa",
"imageName": "ResidenceGreece.jpg"
}
],
"orgsOld": []

View File

@ -1,5 +1,22 @@
# Joplin changelog
## [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (Pre-release) - 2022-04-14T11:35:45Z
- New: Add support for multiple profiles ([#6385](https://github.com/laurent22/joplin/issues/6385)) ([#591](https://github.com/laurent22/joplin/issues/591))
- New: Allow saving a Mermaid graph as a PNG or SVG via context menu ([#6126](https://github.com/laurent22/joplin/issues/6126)) ([#6100](https://github.com/laurent22/joplin/issues/6100) by [@asrient](https://github.com/asrient))
- New: Support for Joplin Cloud recursive linked notes (9d9420a)
- Improved: Dont unpin app from taskbar on update ([#6271](https://github.com/laurent22/joplin/issues/6271)) ([#4155](https://github.com/laurent22/joplin/issues/4155) by Daniel Aleksandersen)
- Improved: Make search engine filter keywords case insensitive ([#6267](https://github.com/laurent22/joplin/issues/6267)) ([#6266](https://github.com/laurent22/joplin/issues/6266) by [@JackGruber](https://github.com/JackGruber))
- Improved: Plugins: Add support for "categories" manifest field ([#6109](https://github.com/laurent22/joplin/issues/6109)) ([#5867](https://github.com/laurent22/joplin/issues/5867) by Mayank Bondre)
- Improved: Plugins: Allow updating a resource via the data API (74273cd)
- Improved: Automatically start sync after setting the sync parameters (ff066ba)
- Improved: Improve E2EE usability when accidentally creating multiple keys ([#6399](https://github.com/laurent22/joplin/issues/6399)) ([#6338](https://github.com/laurent22/joplin/issues/6338))
- Improved: Improved handling of ENTER and ESCAPE keys in dialogs ([#6194](https://github.com/laurent22/joplin/issues/6194))
- Fixed: Fixed color of published note on Light theme (21706fa)
- Fixed: Fixed creation of empty notebooks when importing directory of files ([#6274](https://github.com/laurent22/joplin/issues/6274)) ([#6197](https://github.com/laurent22/joplin/issues/6197) by [@Retrove](https://github.com/Retrove))
- Fixed: Fixes right click menu on Markdown Editor ([#6132](https://github.com/laurent22/joplin/issues/6132) by [@bishoy-magdy](https://github.com/bishoy-magdy))
- Fixed: Scroll jumps when typing if heavy scripts or many large elements are used ([#6383](https://github.com/laurent22/joplin/issues/6383)) ([#6074](https://github.com/laurent22/joplin/issues/6074) by Kenichi Kobayashi)
## [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) - 2022-03-17T13:03:23Z
- Improved: Handle invalid revision patches ([#6209](https://github.com/laurent22/joplin/issues/6209))

View File

@ -1,5 +1,14 @@
# Joplin iOS app changelog
## [ios-v12.7.2](https://github.com/laurent22/joplin/releases/tag/ios-v12.7.2) - 2022-04-15T11:07:27Z
- Improved: Allow filtering tags in tag dialog (#6221 by [@shinglyu](https://github.com/shinglyu))
- Improved: Handle invalid revision patches (#6209)
- Improved: Improve error message when revision metadata cannot be decoded, to improve debugging (a325bf6)
- Fixed: Ensure that note revision markup type is set correctly (#6261)
- Fixed: IOS and Dropbox synchronisation not working on iOS 15 (#6375)
- Fixed: The camera button remains clickable after taking a photo bug (#6222 by [@shinglyu](https://github.com/shinglyu))
## [ios-v12.7.1](https://github.com/laurent22/joplin/releases/tag/ios-v12.7.1) - 2022-02-14T14:10:49Z
- New: Add additional time format HH.mm (#6086 by [@vincentjocodes](https://github.com/vincentjocodes))

View File

@ -1,14 +1,14 @@
---
updated: 2022-03-20T00:37:32Z
updated: 2022-04-19T06:17:11Z
---
# Joplin statistics
| Name | Value |
| ----- | ----- |
| Total Windows downloads | 2,244,597 |
| Total macOs downloads | 889,023 |
| Total Linux downloads | 708,023 |
| Total Windows downloads | 2,315,940 |
| Total macOs downloads | 914,149 |
| Total Linux downloads | 728,950 |
| Windows % | 58% |
| macOS % | 23% |
| Linux % | 18% |
@ -17,78 +17,79 @@ updated: 2022-03-20T00:37:32Z
| Version | Date | Windows | macOS | Linux | Total |
| ----- | ----- | ----- | ----- | ----- | ----- |
| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 15,145 | 8,985 | 2,642 | 26,772 |
| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 30,487 | 16,564 | 4,679 | 51,730 |
| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 51,313 | 25,612 | 11,643 | 88,568 |
| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 1,715 | 435 | 428 | 2,578 |
| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 921 | 174 | 135 | 1,230 |
| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 586 | 99 | 55 | 740 |
| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 2,888 | 745 | 794 | 4,427 |
| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 845 | 133 | 108 | 1,086 |
| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 952 | 158 | 88 | 1,198 |
| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 130,779 | 51,059 | 48,240 | 230,078 |
| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 15,986 | 9,448 | 3,135 | 28,569 |
| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 1,156 | 141 | 77 | 1,374 |
| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 1,215 | 231 | 143 | 1,589 |
| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 507 | 26 | 12 | 545 |
| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 1,287 | 264 | 171 | 1,722 |
| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 2,953 | 771 | 669 | 4,393 |
| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 78,682 | 32,403 | 25,149 | 136,234 |
| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 43,828 | 18,959 | 10,013 | 72,800 |
| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 12,910 | 6,529 | 2,279 | 21,718 |
| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 879 | 185 | 133 | 1,197 |
| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 835 | 153 | 85 | 1,073 |
| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 2,103 | 549 | 548 | 3,200 |
| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 43,377 | 19,923 | 9,738 | 73,038 |
| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 3,241 | 878 | 919 | 5,038 |
| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 55,862 | 23,175 | 15,794 | 94,831 |
| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 7,169 | 1,750 | 506 | 9,425 |
| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 1,227 | 232 | 182 | 1,641 |
| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 1,880 | 439 | 495 | 2,814 |
| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 1,294 | 248 | 204 | 1,746 |
| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 1,571 | 357 | 338 | 2,266 |
| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 1,085 | 183 | 150 | 1,418 |
| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 791 | 128 | 72 | 991 |
| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 1,681 | 352 | 315 | 2,348 |
| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 81,073 | 31,347 | 33,050 | 145,470 |
| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 14,544 | 6,851 | 4,031 | 25,426 |
| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 14,958 | 7,485 | 2,565 | 25,008 |
| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 7,740 | 4,600 | 937 | 13,277 |
| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 1,390 | 256 | 186 | 1,832 |
| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 1,104 | 186 | 112 | 1,402 |
| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 3,006 | 716 | 626 | 4,348 |
| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 46,031 | 18,760 | 16,679 | 81,470 |
| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 2,419 | 396 | 372 | 3,187 |
| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 29,921 | 12,159 | 12,699 | 54,779 |
| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 13,817 | 6,376 | 3,602 | 23,795 |
| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 1,426 | 226 | 176 | 1,828 |
| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 1,574 | 288 | 194 | 2,056 |
| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 23,088 | 9,218 | 9,797 | 42,103 |
| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 2,395 | 911 | 364 | 3,670 |
| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 1,475 | 286 | 872 | 2,633 |
| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 1,080 | 222 | 569 | 1,871 |
| [v2.0.4](https://github.com/laurent22/joplin/releases/tag/v2.0.4) (p) | 2021-06-02T12:54:17Z | 1,437 | 384 | 371 | 2,192 |
| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 2,612 | 485 | 1,661 | 4,758 |
| [v2.0.1](https://github.com/laurent22/joplin/releases/tag/v2.0.1) (p) | 2021-05-15T13:22:58Z | 854 | 266 | 1,017 | 2,137 |
| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 37,788 | 16,250 | 19,384 | 73,422 |
| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 1,128 | 132 | 452 | 1,712 |
| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 1,865 | 303 | 934 | 3,102 |
| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 2,199 | 433 | 1,282 | 3,914 |
| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 3,429 | 823 | 2,447 | 6,699 |
| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 116,023 | 42,754 | 64,261 | 223,038 |
| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,026 | 4,853 | 4,471 | 23,350 |
| [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (p) | 2022-04-14T11:35:45Z | 765 | 215 | 211 | 1,191 |
| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 80,140 | 33,355 | 22,481 | 135,976 |
| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 30,805 | 16,708 | 4,728 | 52,241 |
| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 51,508 | 25,658 | 11,657 | 88,823 |
| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 1,837 | 439 | 432 | 2,708 |
| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 1,043 | 176 | 137 | 1,356 |
| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 711 | 101 | 58 | 870 |
| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 3,005 | 747 | 796 | 4,548 |
| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 962 | 135 | 110 | 1,207 |
| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 1,064 | 160 | 90 | 1,314 |
| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 131,057 | 51,082 | 48,789 | 230,928 |
| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 16,038 | 9,452 | 3,138 | 28,628 |
| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 1,189 | 141 | 77 | 1,407 |
| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 1,250 | 231 | 143 | 1,624 |
| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 551 | 31 | 12 | 594 |
| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 1,326 | 266 | 174 | 1,766 |
| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 2,993 | 773 | 671 | 4,437 |
| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 78,786 | 32,413 | 25,155 | 136,354 |
| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 43,910 | 18,971 | 10,022 | 72,903 |
| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 12,961 | 6,533 | 2,283 | 21,777 |
| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 918 | 187 | 135 | 1,240 |
| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 874 | 155 | 87 | 1,116 |
| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 2,159 | 551 | 550 | 3,260 |
| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 43,471 | 19,928 | 9,744 | 73,143 |
| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 3,289 | 880 | 921 | 5,090 |
| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 55,933 | 23,185 | 15,803 | 94,921 |
| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 7,212 | 1,752 | 510 | 9,474 |
| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 1,266 | 234 | 184 | 1,684 |
| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 1,932 | 441 | 497 | 2,870 |
| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 1,333 | 250 | 206 | 1,789 |
| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 1,608 | 359 | 340 | 2,307 |
| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 1,125 | 185 | 152 | 1,462 |
| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 830 | 130 | 74 | 1,034 |
| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 1,718 | 354 | 317 | 2,389 |
| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 81,160 | 31,353 | 33,057 | 145,570 |
| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 14,616 | 6,854 | 4,034 | 25,504 |
| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 15,029 | 7,488 | 2,569 | 25,086 |
| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 7,792 | 4,602 | 939 | 13,333 |
| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 1,429 | 258 | 188 | 1,875 |
| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 1,144 | 188 | 114 | 1,446 |
| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 3,046 | 718 | 628 | 4,392 |
| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 46,099 | 18,764 | 16,683 | 81,546 |
| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 2,468 | 398 | 374 | 3,240 |
| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 30,011 | 12,164 | 12,701 | 54,876 |
| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 13,863 | 6,380 | 3,605 | 23,848 |
| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 1,465 | 228 | 178 | 1,871 |
| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 1,614 | 290 | 196 | 2,100 |
| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 23,162 | 9,226 | 9,799 | 42,187 |
| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 2,443 | 913 | 367 | 3,723 |
| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 1,519 | 288 | 874 | 2,681 |
| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 1,119 | 224 | 571 | 1,914 |
| [v2.0.4](https://github.com/laurent22/joplin/releases/tag/v2.0.4) (p) | 2021-06-02T12:54:17Z | 1,458 | 384 | 371 | 2,213 |
| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 2,648 | 485 | 1,662 | 4,795 |
| [v2.0.1](https://github.com/laurent22/joplin/releases/tag/v2.0.1) (p) | 2021-05-15T13:22:58Z | 855 | 266 | 1,017 | 2,138 |
| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 37,857 | 16,250 | 19,385 | 73,492 |
| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 1,161 | 132 | 452 | 1,745 |
| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 1,914 | 303 | 934 | 3,151 |
| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 2,244 | 433 | 1,282 | 3,959 |
| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 3,464 | 823 | 2,447 | 6,734 |
| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 116,163 | 42,763 | 64,267 | 223,193 |
| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,057 | 4,853 | 4,475 | 23,385 |
| [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 | 310 | 93 | 287 | 690 |
| [v1.7.5](https://github.com/laurent22/joplin/releases/tag/v1.7.5) (p) | 2021-01-26T09:53:05Z | 386 | 204 | 454 | 1,044 |
| [v1.7.5](https://github.com/laurent22/joplin/releases/tag/v1.7.5) (p) | 2021-01-26T09:53:05Z | 388 | 204 | 454 | 1,046 |
| [v1.7.4](https://github.com/laurent22/joplin/releases/tag/v1.7.4) (p) | 2021-01-22T17:58:38Z | 693 | 204 | 625 | 1,522 |
| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 19,358 | 7,690 | 7,598 | 34,646 |
| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 19,454 | 7,690 | 7,598 | 34,742 |
| [v1.7.3](https://github.com/laurent22/joplin/releases/tag/v1.7.3) (p) | 2021-01-20T11:23:50Z | 345 | 76 | 442 | 863 |
| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 11,307 | 4,634 | 4,542 | 20,483 |
| [v1.6.6](https://github.com/laurent22/joplin/releases/tag/v1.6.6) | 2021-01-09T16:15:31Z | 12,538 | 3,417 | 4,793 | 20,748 |
| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 1,267 | 72 | 308 | 1,647 |
| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 11,369 | 4,634 | 4,542 | 20,545 |
| [v1.6.6](https://github.com/laurent22/joplin/releases/tag/v1.6.6) | 2021-01-09T16:15:31Z | 12,555 | 3,417 | 4,793 | 20,765 |
| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 1,312 | 72 | 308 | 1,692 |
| [v1.6.4](https://github.com/laurent22/joplin/releases/tag/v1.6.4) (p) | 2021-01-07T19:11:32Z | 391 | 78 | 203 | 672 |
| [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 | 11,771 | 5,203 | 5,526 | 22,500 |
| [v1.5.14](https://github.com/laurent22/joplin/releases/tag/v1.5.14) | 2020-12-30T01:48:46Z | 11,834 | 5,203 | 5,526 | 22,563 |
| [v1.6.1](https://github.com/laurent22/joplin/releases/tag/v1.6.1) (p) | 2020-12-29T19:37:45Z | 171 | 36 | 168 | 375 |
| [v1.5.13](https://github.com/laurent22/joplin/releases/tag/v1.5.13) | 2020-12-29T18:29:15Z | 629 | 218 | 200 | 1,047 |
| [v1.5.12](https://github.com/laurent22/joplin/releases/tag/v1.5.12) | 2020-12-28T15:14:08Z | 2,409 | 1,769 | 923 | 5,101 |
@ -98,19 +99,19 @@ updated: 2022-03-20T00:37:32Z
| [v1.5.8](https://github.com/laurent22/joplin/releases/tag/v1.5.8) (p) | 2020-12-20T09:45:19Z | 566 | 165 | 642 | 1,373 |
| [v1.5.7](https://github.com/laurent22/joplin/releases/tag/v1.5.7) (p) | 2020-12-10T12:58:33Z | 889 | 254 | 993 | 2,136 |
| [v1.5.4](https://github.com/laurent22/joplin/releases/tag/v1.5.4) (p) | 2020-12-05T12:07:49Z | 692 | 166 | 634 | 1,492 |
| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 26,523 | 13,446 | 11,679 | 51,648 |
| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,346 | 3,879 | 3,138 | 18,363 |
| [v1.4.16](https://github.com/laurent22/joplin/releases/tag/v1.4.16) | 2020-11-27T19:40:16Z | 1,479 | 828 | 596 | 2,903 |
| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 26,597 | 13,456 | 11,679 | 51,732 |
| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,377 | 3,879 | 3,138 | 18,394 |
| [v1.4.16](https://github.com/laurent22/joplin/releases/tag/v1.4.16) | 2020-11-27T19:40:16Z | 1,481 | 829 | 597 | 2,907 |
| [v1.4.15](https://github.com/laurent22/joplin/releases/tag/v1.4.15) | 2020-11-27T13:25:43Z | 897 | 486 | 274 | 1,657 |
| [v1.4.12](https://github.com/laurent22/joplin/releases/tag/v1.4.12) | 2020-11-23T18:58:07Z | 3,027 | 1,327 | 1,303 | 5,657 |
| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 1,798 | 159 | 593 | 2,550 |
| [v1.4.12](https://github.com/laurent22/joplin/releases/tag/v1.4.12) | 2020-11-23T18:58:07Z | 3,029 | 1,327 | 1,303 | 5,659 |
| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 1,857 | 159 | 593 | 2,609 |
| [v1.4.10](https://github.com/laurent22/joplin/releases/tag/v1.4.10) (p) | 2020-11-14T09:53:15Z | 631 | 198 | 686 | 1,515 |
| [v1.4.9](https://github.com/laurent22/joplin/releases/tag/v1.4.9) (p) | 2020-11-11T14:23:17Z | 696 | 143 | 404 | 1,243 |
| [v1.4.9](https://github.com/laurent22/joplin/releases/tag/v1.4.9) (p) | 2020-11-11T14:23:17Z | 707 | 143 | 404 | 1,254 |
| [v1.4.7](https://github.com/laurent22/joplin/releases/tag/v1.4.7) (p) | 2020-11-07T18:23:29Z | 520 | 175 | 516 | 1,211 |
| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 31,799 | 11,337 | 10,515 | 53,651 |
| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 31,861 | 11,337 | 10,515 | 53,713 |
| [v1.3.17](https://github.com/laurent22/joplin/releases/tag/v1.3.17) (p) | 2020-11-06T11:35:15Z | 50 | 27 | 25 | 102 |
| [v1.4.6](https://github.com/laurent22/joplin/releases/tag/v1.4.6) (p) | 2020-11-05T22:44:12Z | 515 | 95 | 55 | 665 |
| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,430 | 1,301 | 848 | 4,579 |
| [v1.4.6](https://github.com/laurent22/joplin/releases/tag/v1.4.6) (p) | 2020-11-05T22:44:12Z | 532 | 95 | 55 | 682 |
| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,438 | 1,301 | 848 | 4,587 |
| [v1.3.11](https://github.com/laurent22/joplin/releases/tag/v1.3.11) (p) | 2020-10-31T13:22:20Z | 699 | 189 | 484 | 1,372 |
| [v1.3.10](https://github.com/laurent22/joplin/releases/tag/v1.3.10) (p) | 2020-10-29T13:27:14Z | 378 | 118 | 319 | 815 |
| [v1.3.9](https://github.com/laurent22/joplin/releases/tag/v1.3.9) (p) | 2020-10-23T16:04:26Z | 839 | 245 | 636 | 1,720 |
@ -120,65 +121,65 @@ updated: 2022-03-20T00:37:32Z
| [v1.3.3](https://github.com/laurent22/joplin/releases/tag/v1.3.3) (p) | 2020-10-17T10:56:57Z | 121 | 49 | 36 | 206 |
| [v1.3.2](https://github.com/laurent22/joplin/releases/tag/v1.3.2) (p) | 2020-10-11T20:39:49Z | 667 | 184 | 568 | 1,419 |
| [v1.3.1](https://github.com/laurent22/joplin/releases/tag/v1.3.1) (p) | 2020-10-11T15:10:18Z | 85 | 54 | 46 | 185 |
| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 45,407 | 17,739 | 14,047 | 77,193 |
| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 45,498 | 17,740 | 14,047 | 77,285 |
| [v1.2.4](https://github.com/laurent22/joplin/releases/tag/v1.2.4) (p) | 2020-09-30T07:34:29Z | 816 | 250 | 800 | 1,866 |
| [v1.2.3](https://github.com/laurent22/joplin/releases/tag/v1.2.3) (p) | 2020-09-29T15:13:02Z | 220 | 68 | 82 | 370 |
| [v1.2.2](https://github.com/laurent22/joplin/releases/tag/v1.2.2) (p) | 2020-09-22T20:31:55Z | 961 | 210 | 642 | 1,813 |
| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 27,825 | 13,510 | 7,756 | 49,091 |
| [v1.2.2](https://github.com/laurent22/joplin/releases/tag/v1.2.2) (p) | 2020-09-22T20:31:55Z | 979 | 210 | 642 | 1,831 |
| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 27,839 | 13,510 | 7,756 | 49,105 |
| [v1.1.3](https://github.com/laurent22/joplin/releases/tag/v1.1.3) (p) | 2020-09-17T10:30:37Z | 568 | 155 | 467 | 1,190 |
| [v1.1.2](https://github.com/laurent22/joplin/releases/tag/v1.1.2) (p) | 2020-09-15T12:58:38Z | 380 | 121 | 254 | 755 |
| [v1.1.1](https://github.com/laurent22/joplin/releases/tag/v1.1.1) (p) | 2020-09-11T23:32:47Z | 535 | 202 | 353 | 1,090 |
| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 21,756 | 10,012 | 5,645 | 37,413 |
| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,658 | 6,426 | 3,021 | 22,105 |
| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 25,133 | 5,841 | 5,074 | 36,048 |
| [v1.0.239](https://github.com/laurent22/joplin/releases/tag/v1.0.239) (p) | 2020-09-01T21:56:36Z | 775 | 234 | 407 | 1,416 |
| [v1.1.1](https://github.com/laurent22/joplin/releases/tag/v1.1.1) (p) | 2020-09-11T23:32:47Z | 536 | 202 | 353 | 1,091 |
| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 21,807 | 10,012 | 5,645 | 37,464 |
| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,672 | 6,426 | 3,021 | 22,119 |
| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 25,262 | 5,861 | 5,081 | 36,204 |
| [v1.0.239](https://github.com/laurent22/joplin/releases/tag/v1.0.239) (p) | 2020-09-01T21:56:36Z | 790 | 234 | 407 | 1,431 |
| [v1.0.237](https://github.com/laurent22/joplin/releases/tag/v1.0.237) (p) | 2020-08-29T15:38:04Z | 596 | 932 | 344 | 1,872 |
| [v1.0.236](https://github.com/laurent22/joplin/releases/tag/v1.0.236) (p) | 2020-08-28T09:16:54Z | 321 | 119 | 110 | 550 |
| [v1.0.235](https://github.com/laurent22/joplin/releases/tag/v1.0.235) (p) | 2020-08-18T22:08:01Z | 1,854 | 498 | 928 | 3,280 |
| [v1.0.234](https://github.com/laurent22/joplin/releases/tag/v1.0.234) (p) | 2020-08-17T23:13:02Z | 578 | 133 | 107 | 818 |
| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 44,303 | 18,202 | 12,367 | 74,872 |
| [v1.0.235](https://github.com/laurent22/joplin/releases/tag/v1.0.235) (p) | 2020-08-18T22:08:01Z | 1,869 | 498 | 928 | 3,295 |
| [v1.0.234](https://github.com/laurent22/joplin/releases/tag/v1.0.234) (p) | 2020-08-17T23:13:02Z | 579 | 133 | 107 | 819 |
| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 44,384 | 18,202 | 12,367 | 74,953 |
| [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 | 40,898 | 15,288 | 9,641 | 65,827 |
| [v1.0.227](https://github.com/laurent22/joplin/releases/tag/v1.0.227) | 2020-07-07T20:44:54Z | 40,936 | 15,288 | 9,641 | 65,865 |
| [v1.0.226](https://github.com/laurent22/joplin/releases/tag/v1.0.226) (p) | 2020-07-04T10:21:26Z | 4,922 | 2,261 | 694 | 7,877 |
| [v1.0.224](https://github.com/laurent22/joplin/releases/tag/v1.0.224) | 2020-06-20T22:26:08Z | 24,817 | 11,015 | 6,013 | 41,845 |
| [v1.0.223](https://github.com/laurent22/joplin/releases/tag/v1.0.223) (p) | 2020-06-20T11:51:27Z | 194 | 120 | 84 | 398 |
| [v1.0.221](https://github.com/laurent22/joplin/releases/tag/v1.0.221) (p) | 2020-06-20T01:44:20Z | 862 | 214 | 216 | 1,292 |
| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,175 | 9,929 | 6,421 | 48,525 |
| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,206 | 9,929 | 6,421 | 48,556 |
| [v1.0.218](https://github.com/laurent22/joplin/releases/tag/v1.0.218) | 2020-06-07T10:43:34Z | 14,552 | 6,977 | 2,962 | 24,491 |
| [v1.0.217](https://github.com/laurent22/joplin/releases/tag/v1.0.217) (p) | 2020-06-06T15:17:27Z | 232 | 101 | 60 | 393 |
| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 38,147 | 14,298 | 10,188 | 62,633 |
| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 38,204 | 14,299 | 10,188 | 62,691 |
| [v1.0.214](https://github.com/laurent22/joplin/releases/tag/v1.0.214) (p) | 2020-05-21T17:15:15Z | 6,573 | 3,476 | 768 | 10,817 |
| [v1.0.212](https://github.com/laurent22/joplin/releases/tag/v1.0.212) (p) | 2020-05-21T07:48:39Z | 218 | 76 | 53 | 347 |
| [v1.0.211](https://github.com/laurent22/joplin/releases/tag/v1.0.211) (p) | 2020-05-20T08:59:16Z | 307 | 140 | 93 | 540 |
| [v1.0.209](https://github.com/laurent22/joplin/releases/tag/v1.0.209) (p) | 2020-05-17T18:32:51Z | 1,399 | 860 | 153 | 2,412 |
| [v1.0.207](https://github.com/laurent22/joplin/releases/tag/v1.0.207) (p) | 2020-05-10T16:37:35Z | 1,201 | 271 | 1,022 | 2,494 |
| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,117 | 20,055 | 18,184 | 92,356 |
| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,178 | 20,056 | 18,184 | 92,418 |
| [v1.0.200](https://github.com/laurent22/joplin/releases/tag/v1.0.200) | 2020-04-12T12:17:46Z | 9,566 | 4,897 | 1,908 | 16,371 |
| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,511 | 5,894 | 3,794 | 29,199 |
| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 22,590 | 9,597 | 5,947 | 38,134 |
| [v1.0.195](https://github.com/laurent22/joplin/releases/tag/v1.0.195) | 2020-03-22T19:56:12Z | 19,086 | 7,954 | 4,510 | 31,550 |
| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,517 | 5,894 | 3,794 | 29,205 |
| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 22,630 | 9,608 | 5,979 | 38,217 |
| [v1.0.195](https://github.com/laurent22/joplin/releases/tag/v1.0.195) | 2020-03-22T19:56:12Z | 19,087 | 7,954 | 4,510 | 31,551 |
| [v1.0.194](https://github.com/laurent22/joplin/releases/tag/v1.0.194) (p) | 2020-03-14T00:00:32Z | 1,290 | 1,389 | 522 | 3,201 |
| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,695 | 10,915 | 7,403 | 47,013 |
| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,697 | 10,916 | 7,403 | 47,016 |
| [v1.0.192](https://github.com/laurent22/joplin/releases/tag/v1.0.192) (p) | 2020-03-06T23:27:52Z | 484 | 127 | 93 | 704 |
| [v1.0.190](https://github.com/laurent22/joplin/releases/tag/v1.0.190) (p) | 2020-03-06T01:22:22Z | 383 | 96 | 89 | 568 |
| [v1.0.189](https://github.com/laurent22/joplin/releases/tag/v1.0.189) (p) | 2020-03-04T17:27:15Z | 356 | 101 | 99 | 556 |
| [v1.0.187](https://github.com/laurent22/joplin/releases/tag/v1.0.187) (p) | 2020-03-01T12:31:06Z | 926 | 235 | 272 | 1,433 |
| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,327 | 28,657 | 22,559 | 122,543 |
| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,337 | 28,668 | 22,560 | 122,565 |
| [v1.0.178](https://github.com/laurent22/joplin/releases/tag/v1.0.178) | 2020-01-20T19:06:45Z | 17,595 | 5,968 | 2,592 | 26,155 |
| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 1,953 | 443 | 707 | 3,103 |
| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 1,954 | 443 | 707 | 3,104 |
| [v1.0.176](https://github.com/laurent22/joplin/releases/tag/v1.0.176) (p) | 2019-12-14T10:36:44Z | 3,128 | 2,539 | 471 | 6,138 |
| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,205 | 16,957 | 16,555 | 106,717 |
| [v1.0.174](https://github.com/laurent22/joplin/releases/tag/v1.0.174) | 2019-11-12T18:20:58Z | 30,506 | 11,739 | 8,225 | 50,470 |
| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,312 | 16,963 | 16,558 | 106,833 |
| [v1.0.174](https://github.com/laurent22/joplin/releases/tag/v1.0.174) | 2019-11-12T18:20:58Z | 30,514 | 11,741 | 8,226 | 50,481 |
| [v1.0.173](https://github.com/laurent22/joplin/releases/tag/v1.0.173) | 2019-11-11T08:33:35Z | 5,103 | 2,084 | 750 | 7,937 |
| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,605 | 8,765 | 7,681 | 44,051 |
| [v1.0.169](https://github.com/laurent22/joplin/releases/tag/v1.0.169) | 2019-09-27T18:35:13Z | 17,164 | 5,926 | 3,758 | 26,848 |
| [v1.0.168](https://github.com/laurent22/joplin/releases/tag/v1.0.168) | 2019-09-25T21:21:38Z | 5,336 | 2,278 | 721 | 8,335 |
| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 16,820 | 5,709 | 3,707 | 26,236 |
| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,613 | 8,771 | 7,681 | 44,065 |
| [v1.0.169](https://github.com/laurent22/joplin/releases/tag/v1.0.169) | 2019-09-27T18:35:13Z | 17,167 | 5,926 | 3,758 | 26,851 |
| [v1.0.168](https://github.com/laurent22/joplin/releases/tag/v1.0.168) | 2019-09-25T21:21:38Z | 5,336 | 2,278 | 722 | 8,336 |
| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 16,824 | 5,709 | 3,707 | 26,240 |
| [v1.0.166](https://github.com/laurent22/joplin/releases/tag/v1.0.166) | 2019-09-09T17:35:54Z | 1,961 | 566 | 240 | 2,767 |
| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 18,999 | 6,980 | 5,468 | 31,447 |
| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,304 | 6,357 | 4,140 | 29,801 |
| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,625 | 7,753 | 8,107 | 46,485 |
| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,200 | 2,183 | 1,119 | 8,502 |
| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 19,005 | 6,980 | 5,468 | 31,453 |
| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,306 | 6,357 | 4,140 | 29,803 |
| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,632 | 7,753 | 8,108 | 46,493 |
| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,200 | 2,183 | 1,120 | 8,503 |
| [v1.0.158](https://github.com/laurent22/joplin/releases/tag/v1.0.158) | 2019-05-27T19:01:18Z | 9,820 | 3,548 | 1,940 | 15,308 |
| [v1.0.157](https://github.com/laurent22/joplin/releases/tag/v1.0.157) | 2019-05-26T17:55:53Z | 2,183 | 849 | 295 | 3,327 |
| [v1.0.153](https://github.com/laurent22/joplin/releases/tag/v1.0.153) (p) | 2019-05-15T06:27:29Z | 854 | 107 | 110 | 1,071 |
@ -188,25 +189,25 @@ updated: 2022-03-20T00:37:32Z
| [v1.0.148](https://github.com/laurent22/joplin/releases/tag/v1.0.148) (p) | 2019-05-08T19:12:24Z | 135 | 61 | 99 | 295 |
| [v1.0.145](https://github.com/laurent22/joplin/releases/tag/v1.0.145) | 2019-05-03T09:16:53Z | 7,012 | 2,866 | 1,441 | 11,319 |
| [v1.0.143](https://github.com/laurent22/joplin/releases/tag/v1.0.143) | 2019-04-22T10:51:38Z | 11,923 | 3,555 | 2,784 | 18,262 |
| [v1.0.142](https://github.com/laurent22/joplin/releases/tag/v1.0.142) | 2019-04-02T16:44:51Z | 14,716 | 4,570 | 4,731 | 24,017 |
| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,635 | 4,176 | 3,321 | 21,132 |
| [v1.0.142](https://github.com/laurent22/joplin/releases/tag/v1.0.142) | 2019-04-02T16:44:51Z | 14,722 | 4,570 | 4,731 | 24,023 |
| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,636 | 4,176 | 3,327 | 21,139 |
| [v1.0.139](https://github.com/laurent22/joplin/releases/tag/v1.0.139) (p) | 2019-03-09T10:06:48Z | 128 | 68 | 50 | 246 |
| [v1.0.138](https://github.com/laurent22/joplin/releases/tag/v1.0.138) (p) | 2019-03-03T17:23:00Z | 156 | 94 | 88 | 338 |
| [v1.0.137](https://github.com/laurent22/joplin/releases/tag/v1.0.137) (p) | 2019-03-03T01:12:51Z | 596 | 63 | 87 | 746 |
| [v1.0.135](https://github.com/laurent22/joplin/releases/tag/v1.0.135) | 2019-02-27T23:36:57Z | 12,570 | 3,964 | 4,082 | 20,616 |
| [v1.0.135](https://github.com/laurent22/joplin/releases/tag/v1.0.135) | 2019-02-27T23:36:57Z | 12,577 | 3,964 | 4,082 | 20,623 |
| [v1.0.134](https://github.com/laurent22/joplin/releases/tag/v1.0.134) | 2019-02-27T10:21:44Z | 1,472 | 574 | 223 | 2,269 |
| [v1.0.132](https://github.com/laurent22/joplin/releases/tag/v1.0.132) | 2019-02-26T23:02:05Z | 1,092 | 457 | 100 | 1,649 |
| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 9,835 | 3,177 | 2,934 | 15,946 |
| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 9,841 | 3,177 | 2,934 | 15,952 |
| [v1.0.126](https://github.com/laurent22/joplin/releases/tag/v1.0.126) (p) | 2019-02-09T19:46:16Z | 938 | 79 | 121 | 1,138 |
| [v1.0.125](https://github.com/laurent22/joplin/releases/tag/v1.0.125) | 2019-01-26T18:14:33Z | 10,274 | 3,564 | 1,707 | 15,545 |
| [v1.0.120](https://github.com/laurent22/joplin/releases/tag/v1.0.120) | 2019-01-10T21:42:53Z | 15,610 | 5,208 | 6,522 | 27,340 |
| [v1.0.119](https://github.com/laurent22/joplin/releases/tag/v1.0.119) | 2018-12-18T12:40:22Z | 8,911 | 3,267 | 2,018 | 14,196 |
| [v1.0.118](https://github.com/laurent22/joplin/releases/tag/v1.0.118) | 2019-01-11T08:34:13Z | 722 | 253 | 93 | 1,068 |
| [v1.0.117](https://github.com/laurent22/joplin/releases/tag/v1.0.117) | 2018-11-24T12:05:24Z | 16,266 | 4,902 | 6,385 | 27,553 |
| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 3,504 | 1,128 | 718 | 5,350 |
| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 3,543 | 1,128 | 718 | 5,389 |
| [v1.0.115](https://github.com/laurent22/joplin/releases/tag/v1.0.115) | 2018-11-16T16:52:02Z | 3,662 | 1,308 | 805 | 5,775 |
| [v1.0.114](https://github.com/laurent22/joplin/releases/tag/v1.0.114) | 2018-10-24T20:14:10Z | 11,401 | 3,507 | 3,834 | 18,742 |
| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,093 | 3,316 | 3,686 | 19,095 |
| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,102 | 3,318 | 3,687 | 19,107 |
| [v1.0.110](https://github.com/laurent22/joplin/releases/tag/v1.0.110) | 2018-09-29T12:29:21Z | 966 | 414 | 122 | 1,502 |
| [v1.0.109](https://github.com/laurent22/joplin/releases/tag/v1.0.109) | 2018-09-27T18:01:41Z | 2,107 | 710 | 332 | 3,149 |
| [v1.0.108](https://github.com/laurent22/joplin/releases/tag/v1.0.108) (p) | 2018-09-29T18:49:29Z | 35 | 27 | 18 | 80 |
@ -220,13 +221,13 @@ updated: 2022-03-20T00:37:32Z
| [v1.0.99](https://github.com/laurent22/joplin/releases/tag/v1.0.99) | 2018-06-10T13:18:23Z | 1,260 | 603 | 385 | 2,248 |
| [v1.0.97](https://github.com/laurent22/joplin/releases/tag/v1.0.97) | 2018-06-09T19:23:34Z | 317 | 162 | 65 | 544 |
| [v1.0.96](https://github.com/laurent22/joplin/releases/tag/v1.0.96) | 2018-05-26T16:36:39Z | 2,726 | 1,230 | 1,707 | 5,663 |
| [v1.0.95](https://github.com/laurent22/joplin/releases/tag/v1.0.95) | 2018-05-25T13:04:30Z | 424 | 225 | 128 | 777 |
| [v1.0.95](https://github.com/laurent22/joplin/releases/tag/v1.0.95) | 2018-05-25T13:04:30Z | 424 | 225 | 129 | 778 |
| [v1.0.94](https://github.com/laurent22/joplin/releases/tag/v1.0.94) | 2018-05-21T20:52:59Z | 1,138 | 591 | 404 | 2,133 |
| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,796 | 1,208 | 766 | 3,770 |
| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,796 | 1,214 | 766 | 3,776 |
| [v1.0.91](https://github.com/laurent22/joplin/releases/tag/v1.0.91) | 2018-05-10T14:48:04Z | 832 | 558 | 317 | 1,707 |
| [v1.0.89](https://github.com/laurent22/joplin/releases/tag/v1.0.89) | 2018-05-09T13:05:05Z | 500 | 238 | 118 | 856 |
| [v1.0.85](https://github.com/laurent22/joplin/releases/tag/v1.0.85) | 2018-05-01T21:08:24Z | 1,657 | 957 | 641 | 3,255 |
| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,198 | 2,538 | 2,665 | 10,401 |
| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,230 | 2,538 | 2,665 | 10,433 |
| [v1.0.82](https://github.com/laurent22/joplin/releases/tag/v1.0.82) | 2018-03-31T19:16:31Z | 696 | 412 | 129 | 1,237 |
| [v1.0.81](https://github.com/laurent22/joplin/releases/tag/v1.0.81) | 2018-03-28T08:13:58Z | 1,003 | 603 | 790 | 2,396 |
| [v1.0.79](https://github.com/laurent22/joplin/releases/tag/v1.0.79) | 2018-03-23T18:00:11Z | 934 | 545 | 388 | 1,867 |
@ -249,21 +250,21 @@ updated: 2022-03-20T00:37:32Z
| [v0.10.47](https://github.com/laurent22/joplin/releases/tag/v0.10.47) | 2018-01-16T17:27:17Z | 1,233 | 1,277 | 72 | 2,582 |
| [v0.10.43](https://github.com/laurent22/joplin/releases/tag/v0.10.43) | 2018-01-08T10:12:10Z | 3,445 | 2,362 | 1,213 | 7,020 |
| [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,598 | 1,795 | 344 | 3,737 |
| [v0.10.39](https://github.com/laurent22/joplin/releases/tag/v0.10.39) | 2017-12-11T21:19:44Z | 5,828 | 4,300 | 3,200 | 13,328 |
| [v0.10.38](https://github.com/laurent22/joplin/releases/tag/v0.10.38) | 2017-12-08T10:12:06Z | 1,052 | 1,236 | 311 | 2,599 |
| [v0.10.37](https://github.com/laurent22/joplin/releases/tag/v0.10.37) | 2017-12-07T19:38:05Z | 268 | 851 | 88 | 1,207 |
| [v0.10.36](https://github.com/laurent22/joplin/releases/tag/v0.10.36) | 2017-12-05T09:34:40Z | 1,018 | 1,363 | 444 | 2,825 |
| [v0.10.35](https://github.com/laurent22/joplin/releases/tag/v0.10.35) | 2017-12-02T15:56:08Z | 1,581 | 1,554 | 750 | 3,885 |
| [v0.10.34](https://github.com/laurent22/joplin/releases/tag/v0.10.34) | 2017-12-02T14:50:28Z | 93 | 677 | 65 | 835 |
| [v0.10.33](https://github.com/laurent22/joplin/releases/tag/v0.10.33) | 2017-12-02T13:20:39Z | 65 | 665 | 28 | 758 |
| [v0.10.31](https://github.com/laurent22/joplin/releases/tag/v0.10.31) | 2017-12-01T09:56:44Z | 896 | 1,457 | 412 | 2,765 |
| [v0.10.30](https://github.com/laurent22/joplin/releases/tag/v0.10.30) | 2017-11-30T20:28:16Z | 726 | 1,375 | 425 | 2,526 |
| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,352 | 1,708 | 881 | 3,941 |
| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 191 | 707 | 266 | 1,164 |
| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 152 | 703 | 6,551 | 7,406 |
| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 136 | 654 | 33 | 823 |
| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 89 | 651 | 26 | 766 |
| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 56 | 644 | 19 | 719 |
| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 38 | 655 | 28 | 721 |
| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 28 | 653 | 22 | 703 |
| [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,837 | 4,309 | 3,208 | 13,354 |
| [v0.10.38](https://github.com/laurent22/joplin/releases/tag/v0.10.38) | 2017-12-08T10:12:06Z | 1,054 | 1,240 | 313 | 2,607 |
| [v0.10.37](https://github.com/laurent22/joplin/releases/tag/v0.10.37) | 2017-12-07T19:38:05Z | 270 | 855 | 90 | 1,215 |
| [v0.10.36](https://github.com/laurent22/joplin/releases/tag/v0.10.36) | 2017-12-05T09:34:40Z | 1,020 | 1,367 | 446 | 2,833 |
| [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 | 95 | 681 | 67 | 843 |
| [v0.10.33](https://github.com/laurent22/joplin/releases/tag/v0.10.33) | 2017-12-02T13:20:39Z | 67 | 669 | 30 | 766 |
| [v0.10.31](https://github.com/laurent22/joplin/releases/tag/v0.10.31) | 2017-12-01T09:56:44Z | 898 | 1,461 | 414 | 2,773 |
| [v0.10.30](https://github.com/laurent22/joplin/releases/tag/v0.10.30) | 2017-11-30T20:28:16Z | 728 | 1,379 | 427 | 2,534 |
| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,354 | 1,712 | 883 | 3,949 |
| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 195 | 711 | 268 | 1,174 |
| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 154 | 707 | 6,565 | 7,426 |
| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 142 | 671 | 43 | 856 |
| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 92 | 657 | 29 | 778 |
| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 59 | 650 | 23 | 732 |
| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 42 | 661 | 31 | 734 |
| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 31 | 660 | 28 | 719 |

198
yarn.lock
View File

@ -2978,9 +2978,9 @@ __metadata:
"@codemirror/lang-markdown": ^0.18.4
"@codemirror/state": ^0.18.7
"@codemirror/view": ^0.18.19
"@joplin/lib": ~2.6
"@joplin/renderer": ~2.6
"@joplin/tools": ~2.6
"@joplin/lib": ~2.8
"@joplin/renderer": ~2.8
"@joplin/tools": ~2.8
"@react-native-community/clipboard": ^1.5.0
"@react-native-community/datetimepicker": ^3.0.3
"@react-native-community/geolocation": ^2.0.2
@ -3089,7 +3089,7 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/htmlpack@^2.6.1, @joplin/htmlpack@workspace:packages/htmlpack, @joplin/htmlpack@~2.8":
"@joplin/htmlpack@workspace:packages/htmlpack, @joplin/htmlpack@~2.8":
version: 0.0.0-use.local
resolution: "@joplin/htmlpack@workspace:packages/htmlpack"
dependencies:
@ -3102,7 +3102,7 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/lib@^2.6.3, @joplin/lib@workspace:packages/lib, @joplin/lib@~2.8":
"@joplin/lib@workspace:packages/lib, @joplin/lib@~2.8":
version: 0.0.0-use.local
resolution: "@joplin/lib@workspace:packages/lib"
dependencies:
@ -3183,76 +3183,6 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/lib@npm:~2.6":
version: 2.6.3
resolution: "@joplin/lib@npm:2.6.3"
dependencies:
"@aws-sdk/client-s3": ^3.34.0
"@aws-sdk/s3-request-presigner": ^3.34.0
"@joplin/fork-htmlparser2": ^4.1.39
"@joplin/fork-sax": ^1.2.43
"@joplin/htmlpack": ^2.6.1
"@joplin/renderer": ^2.6.3
"@joplin/turndown": ^4.0.61
"@joplin/turndown-plugin-gfm": ^1.0.43
async-mutex: ^0.1.3
base-64: ^0.1.0
base64-stream: ^1.0.0
builtin-modules: ^3.1.0
chokidar: ^3.4.3
color: 3.1.2
compare-versions: ^3.6.0
css: ^3.0.0
diff-match-patch: ^1.0.4
es6-promise-pool: ^2.5.0
fast-deep-equal: ^3.1.3
follow-redirects: ^1.2.4
form-data: ^2.1.4
fs-extra: ^5.0.0
html-entities: ^1.2.1
html-minifier: ^3.5.15
image-data-uri: ^2.0.0
image-type: ^3.0.0
immer: ^7.0.14
js-yaml: ^4.1.0
levenshtein: ^1.0.5
lodash: ^4.17.20
markdown-it: ^10.0.0
md5: ^2.2.1
md5-file: ^4.0.0
moment: ^2.29.1
multiparty: ^4.2.1
mustache: ^4.0.1
nanoid: ^3.1.12
node-fetch: ^1.7.1
node-notifier: ^8.0.0
node-persist: ^2.1.0
node-rsa: ^1.1.1
promise: ^7.1.1
query-string: 4.3.4
re-reselect: ^4.0.0
read-chunk: ^2.1.0
redux: ^3.7.2
relative: ^3.0.2
reselect: ^4.0.0
server-destroy: ^1.0.1
sprintf-js: ^1.1.2
sqlite3: ^5.0.2
string-padding: ^1.0.2
string-to-stream: ^1.1.0
tar: ^4.4.10
tcp-port-used: ^0.1.2
uglifycss: 0.0.29
url-parse: ^1.4.7
uslug: "git+https://github.com/laurent22/uslug.git#emoji-support"
uuid: ^3.0.1
valid-url: ^1.0.9
word-wrap: ^1.2.3
xml2js: ^0.4.19
checksum: d0208121688057f179fe52a61a3442ce63fe82f3ef65d1fa27a7d21e2912048f7009daa464d44ec00f5fa980d3cf33ae0ec869598357ed96225aed20dcc1dcd0
languageName: node
linkType: hard
"@joplin/plugin-repo-cli@workspace:packages/plugin-repo-cli":
version: 0.0.0-use.local
resolution: "@joplin/plugin-repo-cli@workspace:packages/plugin-repo-cli"
@ -3283,7 +3213,7 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/renderer@^2.6.3, @joplin/renderer@workspace:packages/renderer, @joplin/renderer@~2.8":
"@joplin/renderer@workspace:packages/renderer, @joplin/renderer@~2.8":
version: 0.0.0-use.local
resolution: "@joplin/renderer@workspace:packages/renderer"
dependencies:
@ -3317,37 +3247,6 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/renderer@npm:~2.6":
version: 2.6.3
resolution: "@joplin/renderer@npm:2.6.3"
dependencies:
"@joplin/fork-htmlparser2": ^4.1.39
font-awesome-filetypes: ^2.1.0
fs-extra: ^8.1.0
highlight.js: ^11.2.0
html-entities: ^1.2.1
json-stringify-safe: ^5.0.1
katex: ^0.13.3
markdown-it: ^10.0.0
markdown-it-abbr: ^1.0.4
markdown-it-anchor: ^5.2.5
markdown-it-deflist: ^2.0.3
markdown-it-emoji: ^1.4.0
markdown-it-expand-tabs: ^1.0.13
markdown-it-footnote: ^3.0.2
markdown-it-ins: ^3.0.0
markdown-it-mark: ^3.0.0
markdown-it-multimd-table: ^4.0.1
markdown-it-sub: ^1.0.0
markdown-it-sup: ^1.0.0
markdown-it-toc-done-right: ^4.1.0
md5: ^2.2.1
mermaid: ^8.13.5
uslug: "git+https://github.com/laurent22/uslug.git#emoji-support"
checksum: d72adc250939c425175d61956e7060dc2fca77a00b49cfcd41cc3ff5a44de1c891c0c3be3232ba761efc67129726df155fb8626398feedc5eebdbce9094bc5c5
languageName: node
linkType: hard
"@joplin/server@workspace:packages/server":
version: 0.0.0-use.local
resolution: "@joplin/server@workspace:packages/server"
@ -3409,31 +3308,6 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/tools@npm:~2.6":
version: 2.6.3
resolution: "@joplin/tools@npm:2.6.3"
dependencies:
"@joplin/lib": ^2.6.3
"@joplin/renderer": ^2.6.3
execa: ^4.1.0
fs-extra: ^4.0.3
gettext-parser: ^1.3.0
glob: ^7.1.6
markdown-it: ^8.4.1
md5-file: ^4.0.0
moment: ^2.24.0
mustache: ^2.3.0
node-fetch: ^1.7.3
relative: ^3.0.2
request: ^2.88.0
sharp: ^0.25.2
source-map-support: ^0.5.19
uri-template: ^1.0.1
yargs: ^16.0.3
checksum: d9320b87b43336b8fdc446e144e9dd913fa4de13704c14cdfac0a885f11a8e84824e5e73eb5f20380bdc68e010d738a0cf3203ade445f7765c51d143f2bb4d35
languageName: node
linkType: hard
"@joplin/tools@workspace:packages/tools, @joplin/tools@~2.8":
version: 0.0.0-use.local
resolution: "@joplin/tools@workspace:packages/tools"
@ -16135,13 +16009,6 @@ __metadata:
languageName: node
linkType: hard
"highlight.js@npm:^11.2.0":
version: 11.3.1
resolution: "highlight.js@npm:11.3.1"
checksum: 9adaaa1fe5aaae0ca522f9355bc2a7387f76ab362f88c32c86879b99f606619a9aa33c32ffc94cd893987e71ba5d2de6f3e325ed9e8eac65e5872d251e8cba3a
languageName: node
linkType: hard
"highlight.js@npm:~9.12.0":
version: 9.12.0
resolution: "highlight.js@npm:9.12.0"
@ -20480,15 +20347,6 @@ __metadata:
languageName: node
linkType: hard
"markdown-it-multimd-table@npm:^4.0.1":
version: 4.1.1
resolution: "markdown-it-multimd-table@npm:4.1.1"
dependencies:
markdown-it: ^11.0.0
checksum: a92c9d43f87f26f0f9d1271612ba94bb27affe0408c6bcd34641e9b4c361bf561cf9a1276ce6cf2f8df77fc5a9bb598826dc50e6411905b46aeb481a524e2974
languageName: node
linkType: hard
"markdown-it-multimd-table@npm:^4.1.2":
version: 4.1.2
resolution: "markdown-it-multimd-table@npm:4.1.2"
@ -20534,21 +20392,6 @@ __metadata:
languageName: node
linkType: hard
"markdown-it@npm:^11.0.0":
version: 11.0.1
resolution: "markdown-it@npm:11.0.1"
dependencies:
argparse: ^1.0.7
entities: ~2.0.0
linkify-it: ^3.0.1
mdurl: ^1.0.1
uc.micro: ^1.0.5
bin:
markdown-it: bin/markdown-it.js
checksum: 05e953045479fb0e47ef45fbae1ba544e9539cd26bc4cd56a9662ac08dd716a61f78a1fa64aea618c20eb80805aef8214b02e5aa7280e47cd6c3517d31591dad
languageName: node
linkType: hard
"markdown-it@npm:^12.0.4":
version: 12.2.0
resolution: "markdown-it@npm:12.2.0"
@ -20860,23 +20703,6 @@ __metadata:
languageName: node
linkType: hard
"mermaid@npm:^8.13.5":
version: 8.13.6
resolution: "mermaid@npm:8.13.6"
dependencies:
"@braintree/sanitize-url": ^3.1.0
d3: ^7.0.0
dagre: ^0.8.5
dagre-d3: ^0.6.4
dompurify: 2.3.4
graphlib: ^2.1.8
khroma: ^1.4.1
moment-mini: ^2.24.0
stylis: ^4.0.10
checksum: 70c75c236b04d8e430de58129bd9afa5305f0879c7a60c1d4e44f636181adcc2dcdf6a2fc467ac9e76a008958a8701480dd4cb211729b0be785b650bdd55a408
languageName: node
linkType: hard
"mermaid@npm:^8.13.9":
version: 8.13.9
resolution: "mermaid@npm:8.13.9"
@ -22051,7 +21877,7 @@ __metadata:
languageName: node
linkType: hard
"node-emoji@npm:1.11.0, node-emoji@npm:^1.11.0":
"node-emoji@npm:1.11.0":
version: 1.11.0
resolution: "node-emoji@npm:1.11.0"
dependencies:
@ -30337,16 +30163,6 @@ __metadata:
languageName: node
linkType: hard
"uslug@git+https://github.com/laurent22/uslug.git#emoji-support":
version: 1.0.4
resolution: "uslug@https://github.com/laurent22/uslug.git#commit=8c12bc7678eaefa752e673ea9cfbc0b1a14d7237"
dependencies:
node-emoji: ^1.11.0
unorm: ">= 1.0.0"
checksum: 9ed2a3b18f25090aec7468d60b8fa0b2172029d6bb8e917079971758cb67a68c9a591f2a42aa447124849dae8aaea2fa9873d38c9e792cc2e9c3c05329f59fb3
languageName: node
linkType: hard
"utf8-byte-length@npm:^1.0.1":
version: 1.0.4
resolution: "utf8-byte-length@npm:1.0.4"