1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-30 20:39:46 +02:00

Compare commits

...

14 Commits

Author SHA1 Message Date
Henry Heino
56ed471a2f Chore: Rich Text Editor: Refactor editor dialog to simplify toggling the dialog from external commands (#13082) 2025-08-29 23:28:11 +02:00
Henry Heino
650594ecea Chore: Sync fuzzer: Add action for deleting notes (#13083) 2025-08-29 23:28:00 +02:00
Eric Duarte
78fb07d4c7 All: Translation: Update ca.po (#13065) 2025-08-28 17:50:34 -04:00
Henry Heino
78c5c4d7c3 Android: Accessibility: Fix tag search input loses focus when submitted by pressing "enter" (#13070) 2025-08-28 09:20:10 +03:00
Henry Heino
57093b35ea Android: Fixes #12960: Rich Text Editor: Fix pressing enter does nothing in some cases (#13075) 2025-08-28 09:03:37 +03:00
Henry Heino
bc2832e78f Chore: Desktop: Allow access to more Joplin APIs from the desktop development tools in dev mode (#13052) 2025-08-27 22:05:52 +03:00
Henry Heino
424cc96d36 Chore: Sync fuzzer: Fix incorrect expected state after removing the last user from a share (#13061) 2025-08-27 22:03:17 +03:00
Henry Heino
56fd5d828f Android: Fixes #12952: External keyboard: Fix adding tags by pressing enter on certain Android devices (#13069) 2025-08-27 22:02:48 +03:00
Henry Heino
03843b087a Desktop: Fixes #12816: Accessibility: Fix dismissing the alarm dialog by pressing escape (#13068) 2025-08-27 22:02:34 +03:00
Joplin Bot
f6851314d2 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-27 18:26:06 +00:00
Laurent Cozic
eaec45cb3f Doc: Update sponsors 2025-08-27 18:38:56 +03:00
Laurent Cozic
9be954496c Doc: Update sponsors 2025-08-27 17:45:40 +03:00
Joplin Bot
98ef5e619b Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-27 12:33:13 +00:00
Laurent Cozic
5daa7a1f4c Chore: By default, create new releases as pre-releases when publishing desktop app 2025-08-27 09:54:06 +03:00
21 changed files with 401 additions and 169 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# 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://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://damangameplay.in"><img title="Daman Game" width="256" src="https://joplinapp.org/images/sponsors/DamanGame.png" alt="Daman Game"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></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://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://damangameplay.in"><img title="Daman Game" width="256" src="https://joplinapp.org/images/sponsors/DamanGame.png" alt="Daman Game"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -63,6 +63,8 @@ import { refreshFolders } from '@joplin/lib/folders-screen-utils';
import initializeCommandService from './utils/initializeCommandService';
import OcrDriverBase from '@joplin/lib/services/ocr/OcrDriverBase';
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
import Note from '@joplin/lib/models/Note';
import Resource from '@joplin/lib/models/Resource';
const perfLogger = PerformanceLogger.create();
@@ -683,6 +685,11 @@ class Application extends BaseApplication {
debug: new DebugService(reg.db()),
resourceService: ResourceService.instance(),
searchEngine: SearchEngine.instance(),
shim,
Note,
Folder,
Resource,
Setting,
ocrService: () => this.ocrService_,
};
});

View File

@@ -251,8 +251,6 @@ export default class PromptDialog extends React.Component<Props, any> {
} else {
onClose(true);
}
} else if (event.key === 'Escape') {
onClose(false);
}
};
@@ -309,7 +307,7 @@ export default class PromptDialog extends React.Component<Props, any> {
}
return (
<Dialog className='prompt-dialog' contentStyle={styles.dialog}>
<Dialog className='prompt-dialog' contentStyle={styles.dialog} onCancel={() => onClose(false, 'cancel')}>
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
<div style={{ display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor }}>
{inputComp}

View File

@@ -458,10 +458,11 @@ const useInputEventHandlers = ({
} else if (key === 'ArrowUp') {
selectedIndexControl.onPreviousResult();
event.preventDefault();
} else if (key === 'Enter') {
} else if (key === 'Enter' && Platform.OS === 'web') {
// This case is necessary on web to prevent the
// search input from becoming defocused after
// pressing "enter".
// pressing "enter". Enter key behavior is handled
// elsewhere for other platforms.
event.preventDefault();
onSubmit();
setSearch('');
@@ -584,6 +585,7 @@ const ComboBox: React.FC<Props> = ({
onChangeText={setSearch}
onKeyPress={onKeyPress}
onSubmitEditing={onSubmit}
submitBehavior='submit'
placeholder={placeholder}
aria-activedescendant={showSearchResults ? activeId : undefined}
aria-controls={`menuBox-${baseId}`}

View File

@@ -48,10 +48,7 @@ const createEditorDialog = ({ editorApi, doneLabel, block, onSave, onDismiss }:
block.end,
].join(''));
const submitButton = document.createElement('button');
submitButton.appendChild(createTextNode(doneLabel));
submitButton.classList.add('submit');
submitButton.onclick = () => {
const onClose = () => {
if (dialog.close) {
dialog.close();
} else {
@@ -61,6 +58,11 @@ const createEditorDialog = ({ editorApi, doneLabel, block, onSave, onDismiss }:
}
};
const submitButton = document.createElement('button');
submitButton.appendChild(createTextNode(doneLabel));
submitButton.classList.add('submit');
submitButton.onclick = onClose;
dialog.appendChild(submitButton);
@@ -72,7 +74,9 @@ const createEditorDialog = ({ editorApi, doneLabel, block, onSave, onDismiss }:
focus('createEditorDialog/legacy', editor);
}
return {};
return {
dismiss: onClose,
};
};
export default createEditorDialog;

View File

@@ -2,7 +2,7 @@ import { htmlentities } from '@joplin/utils/html';
import { RenderResult } from '../../../../renderer/types';
import createTestEditor from '../../testing/createTestEditor';
import joplinEditorApiPlugin, { getEditorApi, setEditorApi } from '../joplinEditorApiPlugin';
import joplinEditablePlugin from './joplinEditablePlugin';
import joplinEditablePlugin, { editSourceBlockAt, hideSourceBlockEditor } from './joplinEditablePlugin';
import { Second } from '@joplin/utils/time';
const createEditor = (html: string) => {
@@ -19,7 +19,7 @@ const findEditButton = (ancestor: Element): HTMLButtonElement => {
const findEditorDialog = () => {
const dialog = document.querySelector('dialog.editor-dialog');
if (!dialog) {
throw new Error('Could not find an open editor dialog.');
return null;
}
const editor = dialog.querySelector('textarea');
@@ -117,4 +117,15 @@ describe('joplinEditablePlugin', () => {
hashLinks[1].click();
expect(editor.state.selection.$from.parent.textContent).toBe('Test heading 2');
});
test('should support toggling the editor dialog externally', () => {
const editor = createEditor('<div class="joplin-editable"><pre class="joplin-source">test source</pre>rendered</div>');
editSourceBlockAt(0)(editor.state, editor.dispatch, editor);
const dialog = findEditorDialog();
expect(dialog.editor).toBeTruthy();
hideSourceBlockEditor(editor.state, editor.dispatch, editor);
expect(findEditorDialog()).toBeNull();
});
});

View File

@@ -1,4 +1,4 @@
import { Plugin } from 'prosemirror-state';
import { Command, EditorState, Plugin } from 'prosemirror-state';
import { Node, NodeSpec, TagParseRule } from 'prosemirror-model';
import { EditorView, NodeView } from 'prosemirror-view';
import sanitizeHtml from '../../utils/sanitizeHtml';
@@ -13,6 +13,116 @@ import makeLinksClickableInElement from '../../utils/makeLinksClickableInElement
// writing similar ProseMirror plugins:
// https://prosemirror.net/examples/fold/
type EditRequest = {
nodeStart: number;
showEditor: true;
} | {
nodeStart?: undefined;
showEditor: false;
};
export const editSourceBlockAt = (nodeStart: number): Command => (state, dispatch) => {
const node = state.doc.nodeAt(nodeStart);
if (node.type.name !== 'joplinEditableInline' && node.type.name !== 'joplinEditableBlock') {
return false;
}
if (dispatch) {
const editRequest: EditRequest = {
nodeStart,
showEditor: true,
};
dispatch(state.tr.setMeta(joplinEditablePlugin, editRequest));
}
return true;
};
const isSourceBlockEditorVisible = (state: EditorState) => {
return joplinEditablePlugin.getState(state).editingNodeAt !== null;
};
export const hideSourceBlockEditor: Command = (state, dispatch) => {
const isEditing = isSourceBlockEditorVisible(state);
if (!isEditing) {
return false;
}
if (dispatch) {
const editRequest: EditRequest = {
showEditor: false,
};
dispatch(state.tr.setMeta(joplinEditablePlugin, editRequest));
}
return true;
};
const createDialogForNode = (nodePosition: number, view: EditorView) => {
let saveCounter = 0;
const getNode = () => (
view.state.doc.nodeAt(nodePosition)
);
const { localize: _ } = getEditorApi(view.state);
const { dismiss } = createEditorDialog({
doneLabel: _('Done'),
editorLabel: _('Code:'),
editorApi: getEditorApi(view.state),
block: {
content: getNode().attrs.source,
start: getNode().attrs.openCharacters,
end: getNode().attrs.closeCharacters,
},
onSave: async (block) => {
view.dispatch(
view.state.tr.setNodeAttribute(
nodePosition, 'source', block.content,
).setNodeAttribute(
nodePosition, 'openCharacters', block.start,
).setNodeAttribute(
nodePosition, 'closeCharacters', block.end,
),
);
saveCounter ++;
const initialSaveCounter = saveCounter;
const cancelled = () => saveCounter !== initialSaveCounter;
// Debounce rendering
await msleep(400);
if (cancelled()) return;
const rendered = await getEditorApi(view.state).renderer.renderMarkupToHtml(
`${block.start}${block.content}${block.end}`,
{ forceMarkdown: true, isFullPageRender: false },
);
if (cancelled()) return;
const html = postProcessRenderedHtml(rendered.html, getNode().isInline);
view.dispatch(
view.state.tr.setNodeAttribute(
nodePosition, 'contentHtml', html,
),
);
},
onDismiss: () => {
hideSourceBlockEditor(view.state, view.dispatch, view);
},
});
return {
onPositionChange: (newPosition: number) => {
nodePosition = newPosition;
},
dismiss,
};
};
type DialogHandle = ReturnType<typeof createDialogForNode>;
interface JoplinEditableAttributes {
contentHtml: string;
source: string;
@@ -117,7 +227,6 @@ export const nodeSpecs = {
type GetPosition = ()=> number;
class EditableSourceBlockView implements NodeView {
private editDialogVisible_ = false;
public readonly dom: HTMLElement;
public constructor(private node: Node, inline: boolean, private view: EditorView, private getPosition: GetPosition) {
if ((node.attrs.contentHtml ?? undefined) === undefined) {
@@ -135,58 +244,7 @@ class EditableSourceBlockView implements NodeView {
}
private showEditDialog_() {
if (this.editDialogVisible_) {
return;
}
const { localize: _ } = getEditorApi(this.view.state);
let saveCounter = 0;
createEditorDialog({
doneLabel: _('Done'),
editorLabel: _('Code:'),
editorApi: getEditorApi(this.view.state),
block: {
content: this.node.attrs.source,
start: this.node.attrs.openCharacters,
end: this.node.attrs.closeCharacters,
},
onSave: async (block) => {
this.view.dispatch(
this.view.state.tr.setNodeAttribute(
this.getPosition(), 'source', block.content,
).setNodeAttribute(
this.getPosition(), 'openCharacters', block.start,
).setNodeAttribute(
this.getPosition(), 'closeCharacters', block.end,
),
);
saveCounter ++;
const initialSaveCounter = saveCounter;
const cancelled = () => saveCounter !== initialSaveCounter;
// Debounce rendering
await msleep(400);
if (cancelled()) return;
const rendered = await getEditorApi(this.view.state).renderer.renderMarkupToHtml(
`${block.start}${block.content}${block.end}`,
{ forceMarkdown: true, isFullPageRender: false },
);
if (cancelled()) return;
const html = postProcessRenderedHtml(rendered.html, this.node.isInline);
this.view.dispatch(
this.view.state.tr.setNodeAttribute(
this.getPosition(), 'contentHtml', html,
),
);
},
onDismiss: () => {
this.editDialogVisible_ = false;
},
});
editSourceBlockAt(this.getPosition())(this.view.state, this.view.dispatch, this.view);
}
private updateContent_() {
@@ -236,13 +294,64 @@ class EditableSourceBlockView implements NodeView {
}
}
const joplinEditablePlugin = new Plugin({
interface PluginState {
editingNodeAt: number|null;
}
const joplinEditablePlugin = new Plugin<PluginState>({
state: {
init: () => ({
editingNodeAt: null,
}),
apply: (tr, oldValue) => {
let editingAt = oldValue.editingNodeAt;
const editRequest: EditRequest|null = tr.getMeta(joplinEditablePlugin);
if (editRequest) {
if (editRequest.showEditor) {
editingAt = editRequest.nodeStart;
} else {
editingAt = null;
}
}
if (editingAt) {
editingAt = tr.mapping.map(editingAt, 1);
}
return { editingNodeAt: editingAt };
},
},
props: {
nodeViews: {
joplinEditableInline: (node, view, getPos) => new EditableSourceBlockView(node, true, view, getPos),
joplinEditableBlock: (node, view, getPos) => new EditableSourceBlockView(node, false, view, getPos),
},
},
view: () => {
let dialog: DialogHandle|null = null;
return {
update(view, prevState) {
const oldState = joplinEditablePlugin.getState(prevState);
const newState = joplinEditablePlugin.getState(view.state);
if (newState.editingNodeAt !== null) {
if (oldState.editingNodeAt === null) {
dialog = createDialogForNode(newState.editingNodeAt, view);
}
dialog?.onPositionChange(newState.editingNodeAt);
} else if (dialog) {
const lastDialog = dialog;
// Set dialog to null before dismissing to prevent infinite recursion.
// Dismissing the dialog can cause the editor state to update, which can
// result in this callback being re-run.
dialog = null;
lastDialog.dismiss();
}
},
};
},
});
export default joplinEditablePlugin;

View File

@@ -5,7 +5,7 @@ import { baseKeymap, chainCommands, exitCode, liftEmptyBlock, newlineInCode } fr
import { liftListItem, sinkListItem, splitListItem } from 'prosemirror-schema-list';
import commands from '../commands';
import { EditorCommandType } from '../../types';
import { Command, EditorState, TextSelection } from 'prosemirror-state';
import { Command, EditorState, TextSelection, Plugin } from 'prosemirror-state';
import splitBlockAs from '../vendor/splitBlockAs';
import canReplaceSelectionWith from '../utils/canReplaceSelectionWith';
@@ -60,6 +60,28 @@ const isInEmptyParagraph = (state: EditorState) => {
selectionParent.content.size === 0;
};
// Handle double-hard-break -> paragraph conversion with a Plugin to work around
// a bug on Android. If convertDoubleHardBreakToNewParagraph is handled with the
// main keymap logic (with a keymap() extension), then it's possible for the cursor
// to get stuck in some cases.
// See https://github.com/laurent22/joplin/issues/12960.
const replaceDoubleHardBreaksOnEnter = new Plugin({
props: {
handleDOMEvents: {
keydown: (view, event) => {
if (event.key === 'Enter') {
const commandResult = convertDoubleHardBreakToNewParagraph(view.state, view.dispatch);
if (commandResult) {
event.preventDefault();
return true;
}
}
return false;
},
},
},
});
const insertHardBreak: Command = (state, dispatch) => {
// Avoid adding hard breaks at the beginning of list items
if (isInEmptyListItem(state)) return false;
@@ -88,12 +110,12 @@ const keymapExtension = [
'Mod-[': liftListItem(itemType),
'Mod-]': sinkListItem(itemType),
})),
replaceDoubleHardBreaksOnEnter,
keymap({
'Enter': chainCommands(
newlineInCode,
exitCode,
liftEmptyBlock,
convertDoubleHardBreakToNewParagraph,
insertHardBreak,
),
}),

View File

@@ -200,3 +200,5 @@ LOCALAPPDATA
Daman
vikasmanimc
traspire
Pokies
Pokiesman

View File

@@ -1,12 +1,15 @@
import { strict as assert } from 'assert';
import { ActionableClient, FolderData, FuzzContext, ItemId, NoteData, ShareOptions, TreeItem, assertIsFolder, isFolder } from './types';
import type Client from './Client';
import FolderRecord from './model/FolderRecord';
interface ClientData {
childIds: ItemId[];
}
interface ClientInfo {
email: string;
}
class ActionTracker {
private idToItem_: Map<ItemId, TreeItem> = new Map();
private tree_: Map<string, ClientData> = new Map();
@@ -87,7 +90,7 @@ class ActionTracker {
}
}
public track(client: { email: string }) {
public track(client: ClientInfo) {
const clientId = client.email;
// If the client's remote account already exists, continue using it:
if (!this.tree_.has(clientId)) {
@@ -271,6 +274,23 @@ class ActionTracker {
}
};
const removeFromShare = (id: ItemId, shareWith: ClientInfo) => {
const targetItem = this.idToItem_.get(id);
assertIsFolder(targetItem);
assert.ok(targetItem.isSharedWith(shareWith.email), `Folder ${id} should be shared with ${shareWith.email}`);
const otherSubTree = this.tree_.get(shareWith.email);
this.tree_.set(shareWith.email, {
...otherSubTree,
childIds: otherSubTree.childIds.filter(childId => childId !== id),
});
this.idToItem_.set(id, targetItem.withRemovedFromShare(shareWith.email));
this.checkRep_();
};
const tracker: ActionableClient = {
createNote: (data: NoteData) => {
assertWriteable(data.parentId);
@@ -311,6 +331,7 @@ class ActionTracker {
childIds: getChildIds(data.id),
sharedWith: [],
ownedByEmail: clientId,
isShared: false,
}));
addChild(data.parentId, data.id);
@@ -330,7 +351,20 @@ class ActionTracker {
this.checkRep_();
return Promise.resolve();
},
shareFolder: (id: ItemId, shareWith: Client, options: ShareOptions) => {
deleteNote: (id: ItemId) => {
this.checkRep_();
const item = this.idToItem_.get(id);
if (!item) throw new Error(`Not found ${id}`);
assert.ok(!isFolder(item), 'should be a note');
assertWriteable(item);
removeItemRecursive(id);
this.checkRep_();
return Promise.resolve();
},
shareFolder: (id: ItemId, shareWith: ClientInfo, options: ShareOptions) => {
const itemToShare = this.idToItem_.get(id);
assertIsFolder(itemToShare);
@@ -354,19 +388,16 @@ class ActionTracker {
this.checkRep_();
return Promise.resolve();
},
removeFromShare: (id: ItemId, shareWith: Client) => {
removeFromShare: (id, client) => Promise.resolve(removeFromShare(id, client)),
deleteAssociatedShare: (id: ItemId) => {
const targetItem = this.idToItem_.get(id);
assertIsFolder(targetItem);
assert.ok(targetItem.isSharedWith(shareWith.email), `Folder ${id} should be shared with ${shareWith.label}`);
for (const recipient of targetItem.shareRecipients) {
removeFromShare(id, { email: recipient });
}
const otherSubTree = this.tree_.get(shareWith.email);
this.tree_.set(shareWith.email, {
...otherSubTree,
childIds: otherSubTree.childIds.filter(childId => childId !== id),
});
this.idToItem_.set(id, targetItem.withUnshared(shareWith.email));
this.idToItem_.set(id, targetItem.withUnshared());
this.checkRep_();
return Promise.resolve();

View File

@@ -504,6 +504,13 @@ class Client implements ActionableClient {
await this.assertNoteMatchesState_(note);
}
public async deleteNote(id: ItemId) {
logger.info('Delete note', id, 'in', this.label);
await this.tracker_.deleteNote(id);
await this.execCliCommand_('rmnote', '--permanent', '--force', id);
}
public async deleteFolder(id: string) {
logger.info('Delete folder', id, 'in', this.label);
await this.tracker_.deleteFolder(id);
@@ -567,6 +574,12 @@ class Client implements ActionableClient {
await other.sync();
}
public async deleteAssociatedShare(id: string) {
await this.tracker_.deleteAssociatedShare(id);
logger.info('Unshare', id, '(from', this.label, ')');
await this.execCliCommand_('share', 'delete', '-f', id);
}
public async moveItem(itemId: ItemId, newParentId: ItemId) {
logger.info('Move', itemId, 'to', newParentId);
await this.tracker_.moveItem(itemId, newParentId);

View File

@@ -9,6 +9,7 @@ export type ShareRecord = {
interface InitializationOptions extends FolderData {
childIds: ItemId[];
sharedWith: ShareRecord[];
isShared: boolean;
// Email of the Joplin Server account that controls the item
ownedByEmail: string;
}
@@ -23,6 +24,10 @@ export default class FolderRecord implements FolderData {
public readonly title: string;
public readonly ownedByEmail: string;
public readonly childIds: ItemId[];
// Only valid for root folders. Note that a separate isShared_ is needed
// because Joplin folders can be 'shared' even if there are no share recipients.
private readonly isShared_: boolean;
private readonly sharedWith_: ShareRecord[];
public constructor(options: InitializationOptions) {
@@ -30,6 +35,7 @@ export default class FolderRecord implements FolderData {
this.id = options.id;
this.title = options.title;
this.childIds = options.childIds;
this.isShared_ = options.isShared ?? options.sharedWith.length > 0;
this.ownedByEmail = options.ownedByEmail;
this.sharedWith_ = options.sharedWith;
@@ -51,6 +57,7 @@ export default class FolderRecord implements FolderData {
parentId: this.parentId,
id: this.id,
title: this.title,
isShared: this.isShared_,
ownedByEmail: this.ownedByEmail,
childIds: [...this.childIds],
sharedWith: [...this.sharedWith_],
@@ -58,7 +65,7 @@ export default class FolderRecord implements FolderData {
}
public get isRootSharedItem() {
return this.sharedWith_.length > 0;
return this.isShared_;
}
public isSharedWith(email: string) {
@@ -124,6 +131,7 @@ export default class FolderRecord implements FolderData {
return new FolderRecord({
...this.metadata_,
isShared: true,
sharedWith: [
...this.sharedWith_.filter(record => record.email !== recipientEmail),
{ email: recipientEmail, readOnly },
@@ -131,7 +139,7 @@ export default class FolderRecord implements FolderData {
});
}
public withUnshared(recipientEmail: string) {
public withRemovedFromShare(recipientEmail: string) {
if (!this.isSharedWith(recipientEmail)) {
return this;
}
@@ -141,4 +149,16 @@ export default class FolderRecord implements FolderData {
sharedWith: this.sharedWith_.filter(record => record.email !== recipientEmail),
});
}
public withUnshared() {
if (!this.isShared_) {
return this;
}
return new FolderRecord({
...this.metadata_,
sharedWith: [],
isShared: false,
});
}
}

View File

@@ -144,6 +144,13 @@ const doRandomAction = async (context: FuzzContext, client: Client, clientPool:
return true;
},
deleteNote: async () => {
const target = await client.randomNote({ includeReadOnly: false });
if (!target) return false;
await client.deleteNote(target.id);
return true;
},
shareFolder: async () => {
const other = clientPool.randomClient(c => !c.hasSameAccount(client));
if (!other) return false;
@@ -172,11 +179,15 @@ const doRandomAction = async (context: FuzzContext, client: Client, clientPool:
});
if (!target) return false;
const recipientIndex = context.randInt(0, target.shareRecipients.length);
const recipientEmail = target.shareRecipients[recipientIndex];
const recipient = clientPool.clientsByEmail(recipientEmail)[0];
assert.ok(recipient, `invalid state -- recipient ${recipientEmail} should exist`);
await client.removeFromShare(target.id, recipient);
const recipientIndex = context.randInt(-1, target.shareRecipients.length);
if (recipientIndex === -1) { // Completely remove the share
await client.deleteAssociatedShare(target.id);
} else {
const recipientEmail = target.shareRecipients[recipientIndex];
const recipient = clientPool.clientsByEmail(recipientEmail)[0];
assert.ok(recipient, `invalid state -- recipient ${recipientEmail} should exist`);
await client.removeFromShare(target.id, recipient);
}
return true;
},
deleteFolder: async () => {

View File

@@ -67,7 +67,9 @@ export interface ActionableClient {
createFolder(data: FolderData): Promise<void>;
shareFolder(id: ItemId, shareWith: Client, options: ShareOptions): Promise<void>;
removeFromShare(id: string, shareWith: Client): Promise<void>;
deleteAssociatedShare(id: string): Promise<void>;
deleteFolder(id: ItemId): Promise<void>;
deleteNote(id: ItemId): Promise<void>;
createNote(data: NoteData): Promise<void>;
updateNote(data: NoteData): Promise<void>;
moveItem(itemId: ItemId, newParentId: ItemId): Promise<void>;

View File

@@ -11,6 +11,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Éric Duarte <contacto@ericdq.com>\n"
"Language-Team: contacto@ericdq.com\n"
"Language: ca\n"
@@ -432,9 +434,8 @@ msgid "Add recipient:"
msgstr "Afegeix un destinatari:"
#: packages/app-mobile/components/TagEditor.tsx:265
#, fuzzy
msgid "Add tags:"
msgstr "Etiquetes noves:"
msgstr "Afegir etiquetes:"
#: packages/app-mobile/components/screens/Note/Note.tsx:1726
msgid "Add title"
@@ -449,18 +450,16 @@ msgid "Add to note"
msgstr "Afegeix a la nota"
#: packages/app-mobile/components/ComboBox.tsx:89
#, fuzzy
msgid "Added new: %s"
msgstr "Afegeix-ne un"
msgstr "S'ha afegit una nova: %s"
#: packages/app-mobile/components/TagEditor.tsx:219
#, fuzzy
msgid "Added tag: %s"
msgstr "Afegeix l'etiqueta %s a la nota"
msgstr "Etiqueta afegida: %s"
#: packages/app-mobile/components/TagEditor.tsx:206
msgid "Adds tag"
msgstr ""
msgstr "Afegeix una etiqueta"
#: packages/server/src/services/MustacheService.ts:162
#: packages/server/src/services/MustacheService.ts:286
@@ -647,7 +646,7 @@ msgstr "Aritim fosc"
#: packages/app-mobile/components/TagEditor.tsx:172
msgid "Associated tags:"
msgstr ""
msgstr "Etiquetes associades:"
#: packages/app-mobile/utils/lockToSingleInstance.ts:15
msgid ""
@@ -1172,9 +1171,8 @@ msgid "Code View"
msgstr "Vista de codi"
#: packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.ts:147
#, fuzzy
msgid "Code:"
msgstr "Codi"
msgstr "Codi:"
#: packages/lib/utils/joplinCloud/index.ts:184
msgid "Collaborate on a notebook with others"
@@ -1199,9 +1197,9 @@ msgstr "Alarmes programades"
#: packages/lib/models/settings/builtInMetadata.ts:1550
msgid ""
"Comma-separated list of paths to directories to load the certificates from, "
"or path to individual cert files. For example: /my/cert_dir, /other/custom."
"pem. Note that if you make changes to the TLS settings, you must save your "
"changes before clicking on \"Check synchronisation configuration\"."
"or path to individual cert files. For example: /my/cert_dir, /other/"
"custom.pem. Note that if you make changes to the TLS settings, you must save "
"your changes before clicking on \"Check synchronisation configuration\"."
msgstr ""
"Una llista separada per comes de camins a directoris d'on carregar els "
"certificats, o el camí a fitxers de certificats concrets. Per exemple, "
@@ -1350,14 +1348,12 @@ msgid "Control character"
msgstr "Caràcter de control"
#: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:660
#, fuzzy
msgid "Convert it"
msgstr "Converteix a nota"
msgstr "Converteix-lo"
#: packages/app-desktop/commands/convertNoteToMarkdown.ts:11
#, fuzzy
msgid "Convert note to Markdown"
msgstr "Converteix a llistat de tasques pendents"
msgstr "Converteix la nota a Markdown"
#: packages/app-mobile/components/screens/Note/Note.tsx:1332
msgid "Convert to note"
@@ -1462,9 +1458,8 @@ msgid "Could not connect to plugin repository."
msgstr "No s'ha pogut connectar al repositori d'extensions."
#: packages/app-desktop/commands/convertNoteToMarkdown.ts:45
#, fuzzy
msgid "Could not convert note to Markdown: %s"
msgstr "No s'han pogut exportar les notes: %s"
msgstr "No s'ha pogut convertir la nota a Markdown: %s"
#: packages/app-desktop/InteropServiceHelper.ts:224
msgid "Could not export notes: %s"
@@ -1520,9 +1515,8 @@ msgid "Create a notebook"
msgstr "Crear una llibreta"
#: packages/app-mobile/components/FolderPicker.tsx:112
#, fuzzy
msgid "Create new notebook"
msgstr "Crea una llibreta nova."
msgstr "Crea una llibreta nova"
#: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/addProfile.ts:9
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:88
@@ -1530,9 +1524,8 @@ msgid "Create new profile..."
msgstr "Crea un perfil nou..."
#: packages/app-mobile/components/screens/DocumentScanner/NotePreview.tsx:169
#, fuzzy
msgid "Create note"
msgstr "Crea una llibreta"
msgstr "Crea una nota"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:166
msgid "Create notebook"
@@ -1604,14 +1597,12 @@ msgid "Creating new to-do..."
msgstr "Creant una tasca nova..."
#: packages/app-mobile/components/screens/DocumentScanner/DocumentScanner.tsx:78
#, fuzzy
msgid "Creating note \"%s\"..."
msgstr "Creant una nota nova..."
msgstr "S'està creant la nota \"%s\"..."
#: packages/app-mobile/components/screens/DocumentScanner/DocumentScanner.tsx:130
#, fuzzy
msgid "Creating note."
msgstr "Creant una nota nova..."
msgstr "S'està creant una nota."
#: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.tsx:29
msgid "Creating report..."
@@ -1714,6 +1705,7 @@ msgstr "Per defecte"
#: packages/lib/models/settings/builtInMetadata.ts:1924
msgid "Default title to use for documents created by the scanner."
msgstr ""
"Títol per defecte que s'utilitzarà per als documents creats per l'escàner."
#: packages/app-cli/app/help-utils.js:71
msgid "Default: %s"
@@ -2007,7 +1999,7 @@ msgstr ""
#: packages/lib/models/settings/builtInMetadata.ts:1923
msgid "Document scanner: Title template"
msgstr ""
msgstr "Escàner de documents: plantilla de títol"
#: packages/app-cli/app/command-share.ts:65
msgid ""
@@ -2019,7 +2011,7 @@ msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:662
msgid "Don't show this message again"
msgstr ""
msgstr "No tornis a mostrar aquest missatge"
#: packages/lib/models/Setting.ts:1299
msgid "Donate, website"
@@ -2145,14 +2137,12 @@ msgid "Edit"
msgstr "Edita"
#: packages/app-mobile/components/screens/Note/Note.tsx:1368
#, fuzzy
msgid "Edit as Markdown"
msgstr "Markdown"
msgstr "Edita com a Markdown"
#: packages/app-mobile/components/screens/Note/Note.tsx:1368
#, fuzzy
msgid "Edit as Rich Text"
msgstr "Text enriquit"
msgstr "Edita com a text enriquit"
#: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:138
msgid "Edit link"
@@ -2312,14 +2302,12 @@ msgid "Enable Fountain syntax support"
msgstr "Activa el suport de sintaxi de Fountain"
#: packages/lib/models/settings/builtInMetadata.ts:564
#, fuzzy
msgid "Enable handwritten transcription"
msgstr "Activa el xifratge"
msgstr "Habilita la transcripció manuscrita"
#: packages/lib/models/settings/builtInMetadata.ts:741
#, fuzzy
msgid "Enable HTML-to-Markdown conversion banner"
msgstr "Activa la barra d'eines Markdown"
msgstr "Activar el bàner de conversió d'HTML a Markdown"
#: packages/lib/models/settings/builtInMetadata.ts:1063
msgid "Enable Linkify"
@@ -2940,9 +2928,8 @@ msgid "Hide password"
msgstr "Amaga la contrasenya"
#: packages/app-mobile/components/NoteEditor/WarningBanner.tsx:24
#, fuzzy
msgid "Hides warning"
msgstr "Avís"
msgstr "Amaga l'avís"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14
msgid "Highlight"
@@ -2995,6 +2982,8 @@ msgid ""
"If an image attachment is on its own line and followed by a blank line, it "
"will be rendered just below its Markdown source."
msgstr ""
"Si un adjunt d'imatge està en la seva pròpia línia i seguit d'una línia en "
"blanc, es mostrarà just a sota de la seva font Markdown."
#: packages/lib/services/joplinCloudUtils.ts:37
msgid ""
@@ -3695,13 +3684,12 @@ msgid "Markdown editor"
msgstr "Editor Markdown"
#: packages/lib/models/settings/builtInMetadata.ts:1473
#, fuzzy
msgid "Markdown editor: Render images"
msgstr "Editor Markdown"
msgstr "Editor Markdown: Renderització d'imatges"
#: packages/lib/models/settings/builtInMetadata.ts:1463
msgid "Markdown editor: Render markup in editor"
msgstr ""
msgstr "Editor de Markdown: Renderitza el marcatge a l'editor"
#: packages/app-cli/app/command-done.ts:15
msgid "Marks a to-do as done."
@@ -3925,9 +3913,8 @@ msgid ""
msgstr "Es crearà una nova llibreta \"%s\" i s'hi importarà el fitxer \"%s\""
#: packages/app-mobile/components/FolderPicker.tsx:71
#, fuzzy
msgid "New notebook title"
msgstr "Títol del bloc de notes:"
msgstr "Nou títol de llibreta"
#: packages/app-mobile/setupQuickActions.ts:32
msgid "New photo"
@@ -4043,9 +4030,8 @@ msgid "No tab selected"
msgstr "No hi ha cap pestanya seleccionada"
#: packages/app-mobile/components/TagEditor.tsx:167
#, fuzzy
msgid "No tags"
msgstr "Etiquetes noves:"
msgstr "Sense etiquetes"
#: packages/app-cli/app/command-edit.ts:31
msgid ""
@@ -4164,9 +4150,8 @@ msgid "Note list style"
msgstr "Estil de llista de notes"
#: packages/app-mobile/components/screens/DocumentScanner/DocumentScanner.tsx:128
#, fuzzy
msgid "Note preview"
msgstr "Visor de notes"
msgstr "Vista prèvia de la nota"
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:390
#: packages/app-desktop/gui/NotePropertiesDialog.tsx:500
@@ -4258,9 +4243,8 @@ msgid "OCR: Language data URL or path"
msgstr "OCR: URL o camí de dades de l'idioma"
#: packages/lib/models/settings/builtInMetadata.ts:604
#, fuzzy
msgid "OCR: Search in extracted content"
msgstr "Cerca a totes les notes"
msgstr "OCR: Cerca en el contingut extret"
#: packages/app-cli/app/utils/shimInitCli.ts:12
#: packages/app-desktop/bridge.ts:392 packages/app-desktop/bridge.ts:405
@@ -4530,7 +4514,7 @@ msgstr "Cal permís"
#: packages/app-mobile/components/screens/DocumentScanner/DocumentScanner.tsx:93
msgid "Photo %d"
msgstr ""
msgstr "Foto %d"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:261
msgid ""
@@ -4878,9 +4862,8 @@ msgid "Re-upload local data to sync target"
msgstr "Torna a pujar les dades locals per a sincronitzar la destinació"
#: packages/app-mobile/components/NoteEditor/WarningBanner.tsx:19
#, fuzzy
msgid "Read more"
msgstr "Més informació"
msgstr "Llegeix més"
#: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:40
msgid "Read more about it"
@@ -4891,9 +4874,10 @@ msgid "Read time: %s min"
msgstr "Temps de lectura: %s min"
#: packages/app-cli/app/command-batch.ts:45
#, fuzzy
msgid "Reading commands from standard input is only available in CLI mode."
msgstr "L'ordre «%s» només és disponible en mode IGU"
msgstr ""
"La lectura d'ordres des de l'entrada estàndard només està disponible en mode "
"CLI."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:314
msgid "Recipient has accepted the invitation"
@@ -4912,9 +4896,8 @@ msgid "Recipients:"
msgstr "Destinataris:"
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:142
#, fuzzy
msgid "Recognize handwritten image"
msgstr "Canvia la mida de les imatges grans:"
msgstr "Reconeix la imatge manuscrita"
#: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:72
msgid "Recommended"
@@ -4968,9 +4951,8 @@ msgid "Remove"
msgstr "Elimina"
#: packages/app-mobile/components/TagEditor.tsx:129
#, fuzzy
msgid "Remove %s"
msgstr "Elimina"
msgstr "Elimina %s"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:331
msgid "Remove %s from share"
@@ -4989,9 +4971,8 @@ msgid "Removed %s from share."
msgstr "S'ha eliminat %s de la compartició."
#: packages/app-mobile/components/TagEditor.tsx:231
#, fuzzy
msgid "Removed tag: %s"
msgstr "Canvia el nom de l'etiqueta:"
msgstr "Etiqueta eliminada: %s"
#: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/renameFolder.ts:8
#: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/renameTag.ts:8
@@ -5012,7 +4993,7 @@ msgstr "Canvia el nom de la nota o de la llibreta indicat de <item> a <name>."
#: packages/lib/models/settings/builtInMetadata.ts:1464
msgid "Renders markup on all lines that don't include the cursor."
msgstr ""
msgstr "Representa el marcatge en totes les línies que no inclouen el cursor."
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:222
#: packages/app-desktop/gui/ClipperConfigScreen.tsx:162
@@ -5266,9 +5247,8 @@ msgid "Save geo-location with notes"
msgstr "Desa la geolocalització a les notes"
#: packages/app-mobile/components/screens/Notes/NewNoteButton.tsx:81
#, fuzzy
msgid "Scan notebook"
msgstr "Selecciona una llibreta"
msgstr "Escaneja la llibreta"
#: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:90
msgid "Scanned code"
@@ -5317,9 +5297,8 @@ msgid "Search shown"
msgstr "Cerca mostrada"
#: packages/app-mobile/components/TagEditor.tsx:273
#, fuzzy
msgid "Search tags"
msgstr "Resultats de la recerca"
msgstr "Cercar etiquetes"
#: packages/app-cli/app/gui/FolderListWidget.ts:70
msgid "Search:"
@@ -5369,18 +5348,16 @@ msgid "Select file..."
msgstr "Selecciona un fitxer..."
#: packages/app-mobile/components/screens/DocumentScanner/NotePreview.tsx:147
#, fuzzy
msgid "Select notebook"
msgstr "Selecciona una llibreta"
msgstr "Selecciona la llibreta"
#: packages/app-mobile/components/screens/folder.js:109
msgid "Select parent notebook"
msgstr "Selecciona la llibreta principal"
#: packages/app-mobile/components/ComboBox.tsx:367
#, fuzzy
msgid "Selected: %s"
msgstr "Creació: %s"
msgstr "Seleccionat: %s"
#: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:9
msgid "Selection deleted"
@@ -6221,11 +6198,12 @@ msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
msgstr "La nota \"%s\" s'ha restaurat correctament a la llibreta \"%s\"."
#: packages/app-desktop/gui/ConversionNotification/ConversionNotification.tsx:22
#, fuzzy
msgid ""
"The note has been converted to Markdown and the original note has been moved "
"to the trash"
msgstr "La llibreta i el seu contingut s'han mogut amb èxit a la paperera."
msgstr ""
"La nota s'ha convertit a Markdown i la nota original s'ha traslladat a la "
"paperera"
#: packages/app-desktop/gui/TrashNotification/TrashNotification.tsx:45
msgid "The note was successfully moved to the trash."
@@ -6465,14 +6443,17 @@ msgid ""
"This feature is disabled by default, you need to manually enable it by "
"turning on the option to 'Enable handwritten transcription'."
msgstr ""
"Aquesta funció està desactivada per defecte, cal activar-la manualment "
"activant l'opció \"Activa la transcripció manuscrita\"."
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:146
msgid "This feature is only available on Joplin Cloud and Joplin Server."
msgstr ""
msgstr "Aquesta funció només està disponible a Joplin Cloud i Joplin Server."
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:158
msgid "This image type is not supported by the recognition system."
msgstr ""
"Aquest tipus d'imatge no és compatible amb el sistema de reconeixement."
#: packages/app-desktop/gui/ResourceScreen.tsx:298
msgid ""
@@ -6528,6 +6509,8 @@ msgstr "Aquesta nota no té historial"
msgid ""
"This note is in HTML format. Convert it to Markdown to edit it more easily."
msgstr ""
"Aquesta nota està en format HTML. Converteix-la a Markdown per editar-la més "
"fàcilment."
#: packages/lib/services/plugins/PluginService.ts:535
msgid "This plugin doesn't support %s."
@@ -6635,7 +6618,7 @@ msgstr "Per a continuar, introduïu la contrasenya mestra."
#: packages/app-mobile/components/ComboBox.tsx:569
msgid "To create a new tag, type the name and press enter."
msgstr ""
msgstr "Per crear una etiqueta nova, escriviu el nom i premeu Retorn."
#: packages/app-cli/app/app-gui.js:465
msgid "To delete a tag, untag the associated notes."
@@ -7019,9 +7002,8 @@ msgid "Upgrade the sync target to the latest version."
msgstr "Actualitza la destinació de sincronització a la versió més recent."
#: packages/app-mobile/components/CameraView/CameraView.web.tsx:45
#, fuzzy
msgid "Upload photo"
msgstr "Fes una foto"
msgstr "Pujar la foto"
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:33
#: packages/app-desktop/gui/NotePropertiesDialog.tsx:86

View File

@@ -172,6 +172,8 @@
"v3.4.5": true,
"v3.4.4": true,
"v3.4.6": true,
"v3.4.7": true
"v3.4.7": true,
"android-v3.4.5": true,
"ios-v13.4.2": true
}
}

View File

@@ -6,8 +6,6 @@ const appDir = `${rootDir}/packages/app-desktop`;
async function main() {
await gitPullTry(false);
const argv = require('yargs').argv;
process.chdir(appDir);
console.info(`Running from: ${process.cwd()}`);
@@ -23,7 +21,7 @@ async function main() {
await execCommand('git push');
await execCommand('git push --tags');
const releaseOptions = { isDraft: true, isPreRelease: !!argv.beta };
const releaseOptions = { isDraft: true, isPreRelease: true };
console.info('Release options: ', releaseOptions);

View File

@@ -129,6 +129,24 @@
"title": "EssayShark - essay writers for hire",
"imageName": "EssayShark.png",
"alt": "EssayShark - essay writers for hire"
},
{
"url": "https://pokieslab1.com/real-money-pokies/",
"title": "Australian Real Money Pokies",
"imageName": "PokiesLab.png",
"alt": "Australian Real Money Pokies"
},
{
"url": "https://pokiesman1.net/real-money-pokies/",
"title": "Australian Real Money Pokies",
"imageName": "Pokiesman.png",
"alt": "Australian Real Money Pokies"
},
{
"url": "https://domyessay.com",
"title": "Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements",
"imageName": "DoMyEssay.png",
"alt": "DoMyEssay"
}
]
}