You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-30 20:39:46 +02:00
Compare commits
14 Commits
cli-v3.4.1
...
dev
Author | SHA1 | Date | |
---|---|---|---|
|
56ed471a2f | ||
|
650594ecea | ||
|
78fb07d4c7 | ||
|
78c5c4d7c3 | ||
|
57093b35ea | ||
|
bc2832e78f | ||
|
424cc96d36 | ||
|
56fd5d828f | ||
|
03843b087a | ||
|
f6851314d2 | ||
|
eaec45cb3f | ||
|
9be954496c | ||
|
98ef5e619b | ||
|
5daa7a1f4c |
BIN
Assets/WebsiteAssets/images/sponsors/DoMyEssay.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/DoMyEssay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/PokiesLab.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/PokiesLab.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 378 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/Pokiesman.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/Pokiesman.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 295 KiB |
@@ -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 -->
|
||||
|
||||
* * *
|
||||
|
@@ -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_,
|
||||
};
|
||||
});
|
||||
|
@@ -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}
|
||||
|
@@ -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}`}
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
),
|
||||
}),
|
||||
|
@@ -200,3 +200,5 @@ LOCALAPPDATA
|
||||
Daman
|
||||
vikasmanimc
|
||||
traspire
|
||||
Pokies
|
||||
Pokiesman
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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 () => {
|
||||
|
@@ -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>;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user