1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00

Merge branch 'master' of github.com:laurent22/joplin

This commit is contained in:
Laurent Cozic 2020-02-06 11:55:35 +00:00
commit 10cf80d6ca
53 changed files with 1690 additions and 630 deletions

View File

@ -68,6 +68,8 @@ module.exports = {
"linebreak-style": ["error", "unix"],
"prefer-template": ["error"],
"template-curly-spacing": ["error", "never"],
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": ["error", "never"],
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true,

View File

@ -25,7 +25,7 @@ class Command extends BaseCommand {
info: stdoutFn,
warn: stdoutFn,
error: stdoutFn,
}});
} });
ClipperServer.instance().setDispatch(() => {});
ClipperServer.instance().setLogger(clipperLogger);

View File

@ -7,13 +7,13 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Michael Sonntag <ms@editorei.de>\n"
"Last-Translator: Fabian <fab4x@mailbox.org>\n"
"Language-Team: \n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.4\n"
"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
@ -1518,7 +1518,7 @@ msgstr "Link-Adresse kopieren"
#: /mnt/c/Users/laurent/src/joplin/Tools/../ElectronClient/app/gui/NoteText.min.js:829
msgid "There was an error downloading this attachment:"
msgstr ""
msgstr "Es gab einen Fehler beim Herunterladen des Anhangs: "
#: /mnt/c/Users/laurent/src/joplin/Tools/../ElectronClient/app/gui/NoteText.min.js:831
msgid "This attachment is not downloaded or not decrypted yet"
@ -1836,6 +1836,11 @@ msgid ""
"\n"
"%s"
msgstr ""
"Konnte keine Verbindung zu der Joplin Nextcloud Applikation herstellen. "
"Bitte überprüfe die Konfiguration in der Ausgabe der Synchronisation. "
"Vollständige Fehlermeldung:\n"
"\n"
"%s"
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/SyncTargetDropbox.js:25
msgid "Dropbox"
@ -2238,6 +2243,8 @@ msgstr "Sortiere Notizen nach"
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:293
msgid "Auto-pair braces, parenthesis, quotations, etc."
msgstr ""
"Automatisches hinzufügen von Geschweiften Klammern, runden Klammern, "
"Anführungszeichen usw."
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:295
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:313
@ -2375,7 +2382,7 @@ msgstr ""
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:463
msgid "Custom stylesheet for Joplin-wide app styles"
msgstr ""
msgstr "Benutzerdefiniertes Stylesheet für Programmweiten Stil"
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/models/Setting.js:467
msgid "Automatically update the application"
@ -2951,6 +2958,7 @@ msgstr "Exportiere Profil"
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/components/screens/config.js:438
msgid "For debugging purpose only: export your profile to an external SD card."
msgstr ""
"Nur für Debugging-Zwecke: Exportiere dein Profil auf eine externe SD-Karte."
#: /mnt/c/Users/laurent/src/joplin/Tools/../ReactNativeClient/lib/components/screens/config.js:453
msgid "More information"

View File

@ -90,13 +90,13 @@ describe('models_Note', function() {
}));
it('should serialize and unserialize without modifying data', asyncTest(async () => {
let folder1 = await Folder.save({ title: 'folder1'});
let folder1 = await Folder.save({ title: 'folder1' });
const testCases = [
[ {title: '', body: 'Body and no title\nSecond line\nThird Line', parent_id: folder1.id},
[{ title: '', body: 'Body and no title\nSecond line\nThird Line', parent_id: folder1.id },
'', 'Body and no title\nSecond line\nThird Line'],
[ {title: 'Note title', body: 'Body and title', parent_id: folder1.id},
[{ title: 'Note title', body: 'Body and title', parent_id: folder1.id },
'Note title', 'Body and title'],
[ {title: 'Title and no body', body: '', parent_id: folder1.id},
[{ title: 'Title and no body', body: '', parent_id: folder1.id },
'Title and no body', ''],
];
@ -116,4 +116,17 @@ describe('models_Note', function() {
}
}));
it('should reset fields for a duplicate', asyncTest(async () => {
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'note', parent_id: folder1.id });
let duplicatedNote = await Note.duplicate(note1.id);
expect(duplicatedNote !== note1).toBe(true);
expect(duplicatedNote.created_time !== note1.created_time).toBe(true);
expect(duplicatedNote.updated_time !== note1.updated_time).toBe(true);
expect(duplicatedNote.user_created_time !== note1.user_created_time).toBe(true);
expect(duplicatedNote.user_updated_time !== note1.user_updated_time).toBe(true);
}));
});

View File

@ -86,7 +86,7 @@ describe('models_Tag', function() {
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
let note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id });
let tag = await Tag.save({ title: 'mytag'});
let tag = await Tag.save({ title: 'mytag' });
await Tag.addNote(tag.id, note1.id);
let tagWithCount = await Tag.loadWithCount(tag.id);
@ -97,4 +97,57 @@ describe('models_Tag', function() {
expect(tagWithCount.note_count).toBe(2);
}));
it('should get common tags for set of notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: 'folder1' });
let taga = await Tag.save({ title: 'mytaga' });
let tagb = await Tag.save({ title: 'mytagb' });
let tagc = await Tag.save({ title: 'mytagc' });
let tagd = await Tag.save({ title: 'mytagd' });
let note0 = await Note.save({ title: 'ma note 0', parent_id: folder1.id });
let note1 = await Note.save({ title: 'ma note 1', parent_id: folder1.id });
let note2 = await Note.save({ title: 'ma note 2', parent_id: folder1.id });
let note3 = await Note.save({ title: 'ma note 3', parent_id: folder1.id });
await Tag.addNote(taga.id, note1.id);
await Tag.addNote(taga.id, note2.id);
await Tag.addNote(tagb.id, note2.id);
await Tag.addNote(taga.id, note3.id);
await Tag.addNote(tagb.id, note3.id);
await Tag.addNote(tagc.id, note3.id);
let commonTags = await Tag.commonTagsByNoteIds(null);
expect(commonTags.length).toBe(0);
commonTags = await Tag.commonTagsByNoteIds(undefined);
expect(commonTags.length).toBe(0);
commonTags = await Tag.commonTagsByNoteIds([]);
expect(commonTags.length).toBe(0);
commonTags = await Tag.commonTagsByNoteIds([note0.id, note1.id, note2.id, note3.id]);
let commonTagIds = commonTags.map(t => t.id);
expect(commonTagIds.length).toBe(0);
commonTags = await Tag.commonTagsByNoteIds([note1.id, note2.id, note3.id]);
commonTagIds = commonTags.map(t => t.id);
expect(commonTagIds.length).toBe(1);
expect(commonTagIds.includes(taga.id)).toBe(true);
commonTags = await Tag.commonTagsByNoteIds([note2.id, note3.id]);
commonTagIds = commonTags.map(t => t.id);
expect(commonTagIds.length).toBe(2);
expect(commonTagIds.includes(taga.id)).toBe(true);
expect(commonTagIds.includes(tagb.id)).toBe(true);
commonTags = await Tag.commonTagsByNoteIds([note3.id]);
commonTagIds = commonTags.map(t => t.id);
expect(commonTags.length).toBe(3);
expect(commonTagIds.includes(taga.id)).toBe(true);
expect(commonTagIds.includes(tagb.id)).toBe(true);
expect(commonTagIds.includes(tagc.id)).toBe(true);
}));
});

View File

@ -1,11 +1,11 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const {setupDatabaseAndSynchronizer, switchClient, asyncTest } = require('test-utils.js');
const { setupDatabaseAndSynchronizer, switchClient, asyncTest } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { reducer, defaultState, stateUtils} = require('lib/reducer.js');
const { reducer, defaultState, stateUtils } = require('lib/reducer.js');
async function createNTestFolders(n) {
let folders = [];
@ -19,7 +19,7 @@ async function createNTestFolders(n) {
async function createNTestNotes(n, folder) {
let notes = [];
for (let i = 0; i < n; i++) {
let note = await Note.save({ title: 'note', parent_id: folder.id });
let note = await Note.save({ title: 'note', parent_id: folder.id, is_conflict: 0 });
notes.push(note);
}
return notes;
@ -36,12 +36,13 @@ async function createNTestTags(n) {
function initTestState(folders, selectedFolderIndex, notes, selectedIndexes, tags=null, selectedTagIndex=null) {
let state = defaultState;
if (folders != null) {
state = reducer(state, { type: 'FOLDER_UPDATE_ALL', items: folders });
}
if (selectedFolderIndex != null) {
state = reducer(state, { type: 'FOLDER_SELECT', id: folders[selectedFolderIndex].id });
}
if (folders != null) {
state = reducer(state, { type: 'FOLDER_UPDATE_ALL', items: folders });
}
if (notes != null) {
state = reducer(state, { type: 'NOTE_UPDATE_ALL', notes: notes, noteSource: 'test' });
}
@ -63,7 +64,7 @@ function initTestState(folders, selectedFolderIndex, notes, selectedIndexes, tag
}
function createExpectedState(items, keepIndexes, selectedIndexes) {
let expected = { items: [], selectedIds: []};
let expected = { items: [], selectedIds: [] };
for (let i = 0; i < selectedIndexes.length; i++) {
expected.selectedIds.push(items[selectedIndexes[i]].id);
@ -110,7 +111,7 @@ describe('Reducer', function() {
// test action
// delete the third note
state = reducer(state, {type: 'NOTE_DELETE', id: notes[2].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id });
// expect that the third note is missing, and the 4th note is now selected
let expected = createExpectedState(notes, [0,1,3,4], [3]);
@ -127,7 +128,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [1]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[0].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[0].id });
let expected = createExpectedState(notes, [1,2,3,4], [1]);
@ -141,7 +142,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [0]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[0].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[0].id });
let expected = createExpectedState(notes, [], []);
@ -155,7 +156,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [4]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[4].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[4].id });
let expected = createExpectedState(notes, [0,1,2,3], [3]);
@ -169,7 +170,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [3]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[1].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[1].id });
let expected = createExpectedState(notes, [0,2,3,4], [3]);
@ -183,7 +184,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [1]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[3].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[3].id });
let expected = createExpectedState(notes, [0,1,2,4], [1]);
@ -197,8 +198,8 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [1,2]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[1].id});
state = reducer(state, {type: 'NOTE_DELETE', id: notes[2].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[1].id });
state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id });
let expected = createExpectedState(notes, [0,3,4], [3]);
@ -212,7 +213,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [3,4]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[1].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[1].id });
let expected = createExpectedState(notes, [0,2,3,4], [3,4]);
@ -226,7 +227,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [1,2]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[3].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[3].id });
let expected = createExpectedState(notes, [0,1,2,4], [1,2]);
@ -240,8 +241,8 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [3,4]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[3].id});
state = reducer(state, {type: 'NOTE_DELETE', id: notes[4].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[3].id });
state = reducer(state, { type: 'NOTE_DELETE', id: notes[4].id });
let expected = createExpectedState(notes, [0,1,2], [2]);
@ -255,9 +256,9 @@ describe('Reducer', function() {
let state = initTestState(folders, 0, notes, [0,2,4]);
// test action
state = reducer(state, {type: 'NOTE_DELETE', id: notes[0].id});
state = reducer(state, {type: 'NOTE_DELETE', id: notes[2].id});
state = reducer(state, {type: 'NOTE_DELETE', id: notes[4].id});
state = reducer(state, { type: 'NOTE_DELETE', id: notes[0].id });
state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id });
state = reducer(state, { type: 'NOTE_DELETE', id: notes[4].id });
let expected = createExpectedState(notes, [1,3], [1]);
@ -272,7 +273,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 2, notes, [2]);
// test action
state = reducer(state, {type: 'FOLDER_DELETE', id: folders[2].id});
state = reducer(state, { type: 'FOLDER_DELETE', id: folders[2].id });
let expected = createExpectedState(folders, [0,1,3,4], [3]);
@ -286,7 +287,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 1, notes, [2]);
// test action
state = reducer(state, {type: 'FOLDER_DELETE', id: folders[2].id});
state = reducer(state, { type: 'FOLDER_DELETE', id: folders[2].id });
let expected = createExpectedState(folders, [0,1,3,4], [1]);
@ -300,7 +301,7 @@ describe('Reducer', function() {
let state = initTestState(folders, 4, notes, [2]);
// test action
state = reducer(state, {type: 'FOLDER_DELETE', id: folders[2].id});
state = reducer(state, { type: 'FOLDER_DELETE', id: folders[2].id });
let expected = createExpectedState(folders, [0,1,3,4], [4]);
@ -314,7 +315,7 @@ describe('Reducer', function() {
let state = initTestState(null, null, null, null, tags, [2]);
// test action
state = reducer(state, {type: 'TAG_DELETE', id: tags[2].id});
state = reducer(state, { type: 'TAG_DELETE', id: tags[2].id });
let expected = createExpectedState(tags, [0,1,3,4], [3]);
@ -327,7 +328,7 @@ describe('Reducer', function() {
let state = initTestState(null, null, null, null, tags, [2]);
// test action
state = reducer(state, {type: 'TAG_DELETE', id: tags[4].id});
state = reducer(state, { type: 'TAG_DELETE', id: tags[4].id });
let expected = createExpectedState(tags, [0,1,2,3], [2]);
@ -340,11 +341,35 @@ describe('Reducer', function() {
let state = initTestState(null, null, null, null, tags, [2]);
// test action
state = reducer(state, {type: 'TAG_DELETE', id: tags[0].id});
state = reducer(state, { type: 'TAG_DELETE', id: tags[0].id });
let expected = createExpectedState(tags, [1,2,3,4], [2]);
expect(getIds(state.tags)).toEqual(getIds(expected.items));
expect(state.selectedTagId).toEqual(expected.selectedIds[0]);
}));
it('should select all notes', asyncTest(async () => {
let folders = await createNTestFolders(2);
let notes = [];
for (let i = 0; i < folders.length; i++) {
notes.push(...await createNTestNotes(3, folders[i]));
}
let state = initTestState(folders, 0, notes.slice(0,3), [0]);
let expected = createExpectedState(notes, [0,1,2], [0]);
expect(state.notes.length).toEqual(expected.items.length);
expect(getIds(state.notes.slice(0,4))).toEqual(getIds(expected.items));
expect(state.selectedNoteIds).toEqual(expected.selectedIds);
// test action
state = reducer(state, { type: 'NOTE_SELECT_ALL' });
expected = createExpectedState(notes.slice(0,3), [0,1,2], [0,1,2]);
expect(getIds(state.notes)).toEqual(getIds(expected.items));
expect(state.selectedNoteIds).toEqual(expected.selectedIds);
}));
});

View File

@ -279,7 +279,7 @@ describe('services_rest_Api', function() {
const response = await api.route('GET', 'notes', { token: 'mytoken' });
expect(response.length).toBe(0);
hasThrown = await checkThrowAsync(async () => await api.route('POST', 'notes', null, JSON.stringify({title: 'testing'})));
hasThrown = await checkThrowAsync(async () => await api.route('POST', 'notes', null, JSON.stringify({ title: 'testing' })));
expect(hasThrown).toBe(true);
}));

View File

@ -189,10 +189,10 @@ class AppComponent extends Component {
}
async loadContentScripts() {
await bridge().tabsExecuteScript({file: '/content_scripts/JSDOMParser.js'});
await bridge().tabsExecuteScript({file: '/content_scripts/Readability.js'});
await bridge().tabsExecuteScript({file: '/content_scripts/Readability-readerable.js'});
await bridge().tabsExecuteScript({file: '/content_scripts/index.js'});
await bridge().tabsExecuteScript({ file: '/content_scripts/JSDOMParser.js' });
await bridge().tabsExecuteScript({ file: '/content_scripts/Readability.js' });
await bridge().tabsExecuteScript({ file: '/content_scripts/Readability-readerable.js' });
await bridge().tabsExecuteScript({ file: '/content_scripts/index.js' });
}
async componentDidMount() {
@ -248,7 +248,7 @@ class AppComponent extends Component {
if (!this.state.contentScriptLoaded) {
let msg = 'Loading...';
if (this.state.contentScriptError) msg = `The Joplin extension is not available on this tab due to: ${this.state.contentScriptError}`;
return <div style={{padding: 10, fontSize: 12, maxWidth: 200}}>{msg}</div>;
return <div style={{ padding: 10, fontSize: 12, maxWidth: 200 }}>{msg}</div>;
}
const warningComponent = !this.props.warning ? null : <div className="Warning">{ this.props.warning }</div>;

View File

@ -124,6 +124,13 @@ class ElectronAppWrapper {
// automatically (the listeners will be removed when the window is closed)
// and restore the maximized or full screen state
windowState.manage(this.win_);
// HACK: Ensure the window is hidden, as `windowState.manage` may make the window
// visible with isMaximized set to true in window-state-${this.env_}.json.
// https://github.com/laurent22/joplin/issues/2365
if (!windowOptions.show) {
this.win_.hide();
}
}
async waitForElectronAppReady() {

View File

@ -61,12 +61,12 @@ class InteropServiceHelper {
cleanup();
}
} else {
win.webContents.print(options, (success) => {
win.webContents.print(options, (success, reason) => {
// TODO: This is correct but broken in Electron 4. Need to upgrade to 5+
// It calls the callback right away with "false" even if the document hasn't be print yet.
cleanup();
if (!success) reject(new Error('Could not print'));
if (!success && reason !== 'cancelled') reject(new Error(`Could not print: ${reason}`));
resolve();
});
}
@ -99,7 +99,7 @@ class InteropServiceHelper {
if (module.target === 'file') {
path = bridge().showSaveDialog({
filters: [{ name: module.description, extensions: module.fileExtensions}],
filters: [{ name: module.description, extensions: module.fileExtensions }],
});
} else {
path = bridge().showOpenDialog({

View File

@ -408,7 +408,7 @@ class Application extends BaseApplication {
if (moduleSource === 'file') {
path = bridge().showOpenDialog({
filters: [{ name: module.description, extensions: module.fileExtensions}],
filters: [{ name: module.description, extensions: module.fileExtensions }],
});
} else {
path = bridge().showOpenDialog({
@ -874,12 +874,10 @@ class Application extends BaseApplication {
accelerator: 'CommandOrControl+Alt+T',
click: () => {
const selectedNoteIds = this.store().getState().selectedNoteIds;
if (selectedNoteIds.length !== 1) return;
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteId: selectedNoteIds[0],
noteIds: selectedNoteIds,
});
},
}, {
@ -1132,7 +1130,7 @@ class Application extends BaseApplication {
const selectedNoteIds = state.selectedNoteIds;
const note = selectedNoteIds.length === 1 ? await Note.load(selectedNoteIds[0]) : null;
for (const itemId of ['copy', 'paste', 'cut', 'selectAll', 'bold', 'italic', 'link', 'code', 'insertDateTime', 'commandStartExternalEditing', 'setTags', 'showLocalSearch']) {
for (const itemId of ['copy', 'paste', 'cut', 'selectAll', 'bold', 'italic', 'link', 'code', 'insertDateTime', 'commandStartExternalEditing', 'showLocalSearch']) {
const menuItem = Menu.getApplicationMenu().getMenuItemById(`edit:${itemId}`);
if (!menuItem) continue;
menuItem.enabled = !!note && note.markup_language === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;

View File

@ -56,7 +56,7 @@ class Bridge {
}
showSaveDialog(options) {
const {dialog} = require('electron');
const { dialog } = require('electron');
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_;
const filePath = dialog.showSaveDialogSync(this.window(), options);
@ -67,7 +67,7 @@ class Bridge {
}
showOpenDialog(options) {
const {dialog} = require('electron');
const { dialog } = require('electron');
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_;
if (!('createDirectory' in options)) options.createDirectory = true;
@ -80,7 +80,7 @@ class Bridge {
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
showMessageBox_(window, options) {
const {dialog} = require('electron');
const { dialog } = require('electron');
if (!window) window = this.window();
return dialog.showMessageBoxSync(window, options);
}
@ -89,6 +89,7 @@ class Bridge {
return this.showMessageBox_(this.window(), {
type: 'error',
message: message,
buttons: [_('OK')],
});
}

View File

@ -21,8 +21,8 @@ let branch;
let hash;
try {
// Use stdio: 'pipe' so that execSync doesn't print error directly to stdout
branch = execSync('git rev-parse --abbrev-ref HEAD', {stdio: 'pipe' }).toString().trim();
hash = execSync('git log --pretty="%h" -1', {stdio: 'pipe' }).toString().trim();
branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
hash = execSync('git log --pretty="%h" -1', { stdio: 'pipe' }).toString().trim();
} catch (err) {
// Don't display error object as it's a "fatal" error, but
// not for us, since is it not critical information

View File

@ -121,9 +121,9 @@ class ClipperConfigScreenComponent extends React.Component {
<div style={stepBoxStyle}>
<p style={theme.h1Style}>{_('Step 2: Install the extension')}</p>
<p style={theme.textStyle}>{_('Download and install the relevant extension for your browser:')}</p>
<div style={{display: 'flex', flexDirection: 'row'}}>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<ExtensionBadge theme={this.props.theme} type="firefox" url="https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/"/>
<ExtensionBadge style={{marginLeft: 10}} theme={this.props.theme} type="chrome" url="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek"/>
<ExtensionBadge style={{ marginLeft: 10 }} theme={this.props.theme} type="chrome" url="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek"/>
</div>
</div>

View File

@ -212,7 +212,7 @@ class ConfigScreenComponent extends React.Component {
if (advancedSettingComps.length) {
const iconName = this.state.showAdvancedSettings ? 'fa fa-toggle-up' : 'fa fa-toggle-down';
const advancedSettingsButtonStyle = Object.assign({}, theme.buttonStyle, { marginBottom: 10 });
advancedSettingsButton = <button onClick={() => shared.advancedSettingsButton_click(this)} style={advancedSettingsButtonStyle}><i style={{fontSize: 14}} className={iconName}></i> {_('Show Advanced Settings')}</button>;
advancedSettingsButton = <button onClick={() => shared.advancedSettingsButton_click(this)} style={advancedSettingsButtonStyle}><i style={{ fontSize: 14 }} className={iconName}></i> {_('Show Advanced Settings')}</button>;
advancedSettingsSectionStyle.display = this.state.showAdvancedSettings ? 'block' : 'none';
}
@ -575,7 +575,7 @@ class ConfigScreenComponent extends React.Component {
borderTopColor: theme.dividerColor,
};
const screenComp = this.state.screenName ? <div style={{overflow: 'scroll', flex: 1}}>{this.screenFromName(this.state.screenName)}</div> : null;
const screenComp = this.state.screenName ? <div style={{ overflow: 'scroll', flex: 1 }}>{this.screenFromName(this.state.screenName)}</div> : null;
if (screenComp) containerStyle.display = 'none';

View File

@ -144,33 +144,53 @@ class MainScreenComponent extends React.Component {
},
});
} else if (command.name === 'setTags') {
const tags = await Tag.tagsByNoteId(command.noteId);
const noteTags = tags
const tags = await Tag.commonTagsByNoteIds(command.noteIds);
const startTags = tags
.map(a => {
return { value: a.id, label: a.title };
})
.sort((a, b) => {
// sensitivity accent will treat accented characters as differemt
// but treats caps as equal
return a.label.localeCompare(b.label, undefined, {sensitivity: 'accent'});
return a.label.localeCompare(b.label, undefined, { sensitivity: 'accent' });
});
const allTags = await Tag.allWithNotes();
const tagSuggestions = allTags.map(a => {
return { value: a.id, label: a.title };
});
})
.sort((a, b) => {
// sensitivity accent will treat accented characters as differemt
// but treats caps as equal
return a.label.localeCompare(b.label, undefined, { sensitivity: 'accent' });
});
this.setState({
promptOptions: {
label: _('Add or remove tags:'),
inputType: 'tags',
value: noteTags,
value: startTags,
autocomplete: tagSuggestions,
onClose: async answer => {
if (answer !== null) {
const tagTitles = answer.map(a => {
const endTagTitles = answer.map(a => {
return a.label.trim();
});
await Tag.setNoteTagsByTitles(command.noteId, tagTitles);
if (command.noteIds.length === 1) {
await Tag.setNoteTagsByTitles(command.noteIds[0], endTagTitles);
} else {
const startTagTitles = startTags.map(a => { return a.label.trim(); });
const addTags = endTagTitles.filter(value => !startTagTitles.includes(value));
const delTags = startTagTitles.filter(value => !endTagTitles.includes(value));
// apply the tag additions and deletions to each selected note
for (let i = 0; i < command.noteIds.length; i++) {
const tags = await Tag.tagsByNoteId(command.noteIds[i]);
let tagTitles = tags.map(a => { return a.title; });
tagTitles = tagTitles.concat(addTags);
tagTitles = tagTitles.filter(value => !delTags.includes(value));
await Tag.setNoteTagsByTitles(command.noteIds[i], tagTitles);
}
}
}
this.setState({ promptOptions: null });
},

View File

@ -295,20 +295,49 @@ class NoteListComponent extends React.Component {
}
}
scrollNoteIndex_(keyCode, ctrlKey, metaKey, noteIndex) {
if (keyCode === 33) {
// Page Up
noteIndex -= (this.itemListRef.current.visibleItemCount() - 1);
} else if (keyCode === 34) {
// Page Down
noteIndex += (this.itemListRef.current.visibleItemCount() - 1);
} else if ((keyCode === 35 && ctrlKey) || (keyCode === 40 && metaKey)) {
// CTRL+End, CMD+Down
noteIndex = this.props.notes.length - 1;
} else if ((keyCode === 36 && ctrlKey) || (keyCode === 38 && metaKey)) {
// CTRL+Home, CMD+Up
noteIndex = 0;
} else if (keyCode === 38 && !metaKey) {
// Up
noteIndex -= 1;
} else if (keyCode === 40 && !metaKey) {
// Down
noteIndex += 1;
}
if (noteIndex < 0) noteIndex = 0;
if (noteIndex > this.props.notes.length - 1) noteIndex = this.props.notes.length - 1;
return noteIndex;
}
async onKeyDown(event) {
const keyCode = event.keyCode;
const noteIds = this.props.selectedNoteIds;
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38)) {
// DOWN / UP
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode == 36)) {
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
const inc = keyCode === 38 ? -1 : +1;
noteIndex += inc;
if (noteIndex < 0) noteIndex = 0;
if (noteIndex > this.props.notes.length - 1) noteIndex = this.props.notes.length - 1;
noteIndex = this.scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
const newSelectedNote = this.props.notes[noteIndex];
@ -364,6 +393,15 @@ class NoteListComponent extends React.Component {
});
}
}
if (event.keyCode === 65 && (event.ctrlKey || event.metaKey)) {
// Ctrl+A key
event.preventDefault();
this.props.dispatch({
type: 'NOTE_SELECT_ALL',
});
}
}
focusNoteId_(noteId) {

View File

@ -118,6 +118,9 @@ class NoteSearchBarComponent extends React.Component {
render() {
const query = this.props.query ? this.props.query : '';
// backgroundColor needs to cached to a local variable to prevent the
// colour from blinking.
// For more info: https://github.com/laurent22/joplin/pull/2329#issuecomment-578376835
const theme = themeStyle(this.props.theme);
if (!this.props.searching) {
if (this.props.resultCount === 0 && query.length > 0) {

View File

@ -1121,7 +1121,7 @@ class NoteTextComponent extends React.Component {
if (command.name === 'exportPdf') {
fn = this.commandSavePdf;
args = {noteId: command.noteId};
args = { noteId: command.noteId };
} else if (command.name === 'print') {
fn = this.commandPrint;
}
@ -1342,7 +1342,7 @@ class NoteTextComponent extends React.Component {
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteId: this.state.note.id,
noteIds: [this.state.note.id],
});
}
@ -1406,7 +1406,7 @@ class NoteTextComponent extends React.Component {
}
editorPasteText() {
this.wrapSelectionWithStrings('', '', '', clipboard.readText());
this.wrapSelectionWithStrings(clipboard.readText(), '', '', '');
}
selectionRangePreviousLine() {
@ -1425,7 +1425,7 @@ class NoteTextComponent extends React.Component {
return this.selectionRange_ ? this.rangeToTextOffsets(this.selectionRange_, this.state.note.body) : null;
}
wrapSelectionWithStrings(string1, string2 = '', defaultText = '', replacementText = '') {
wrapSelectionWithStrings(string1, string2 = '', defaultText = '', replacementText = null, byLine = false) {
if (!this.rawEditor() || !this.state.note) return;
const selection = this.textOffsetSelection();
@ -1433,10 +1433,14 @@ class NoteTextComponent extends React.Component {
let newBody = this.state.note.body;
if (selection && selection.start !== selection.end) {
const s1 = this.state.note.body.substr(0, selection.start);
const s2 = replacementText ? replacementText : this.state.note.body.substr(selection.start, selection.end - selection.start);
const s3 = this.state.note.body.substr(selection.end);
newBody = s1 + string1 + s2 + string2 + s3;
const selectedLines = replacementText !== null ? replacementText : this.state.note.body.substr(selection.start, selection.end - selection.start);
let selectedStrings = byLine ? selectedLines.split(/\r?\n/) : [selectedLines];
newBody = this.state.note.body.substr(0, selection.start);
for (let i = 0; i < selectedStrings.length; i++) {
newBody += string1 + selectedStrings[i] + string2;
}
newBody += this.state.note.body.substr(selection.end);
const r = this.selectionRange_;
@ -1452,7 +1456,7 @@ class NoteTextComponent extends React.Component {
column: r.end.column + str1Split[str1Split.length - 1].length },
};
if (replacementText) {
if (replacementText !== null) {
const diff = replacementText.length - (selection.end - selection.start);
newRange.end.column += diff;
}
@ -1468,7 +1472,7 @@ class NoteTextComponent extends React.Component {
editor.focus();
});
} else {
let middleText = replacementText ? replacementText : defaultText;
let middleText = replacementText !== null ? replacementText : defaultText;
const textOffset = this.currentTextOffset();
const s1 = this.state.note.body.substr(0, textOffset);
const s2 = this.state.note.body.substr(textOffset);
@ -1540,26 +1544,30 @@ class NoteTextComponent extends React.Component {
this.wrapSelectionWithStrings(TemplateUtils.render(value));
}
addListItem(string1, string2 = '', defaultText = '') {
const currentLine = this.selectionRangeCurrentLine();
addListItem(string1, string2 = '', defaultText = '', byLine=false) {
let newLine = '\n';
if (!currentLine) newLine = '';
this.wrapSelectionWithStrings(newLine + string1, string2, defaultText);
const range = this.selectionRange_;
if (!range || (range.start.row === range.end.row && !this.selectionRangeCurrentLine())) {
newLine = '';
}
this.wrapSelectionWithStrings(newLine + string1, string2, defaultText, null, byLine);
}
commandTextCheckbox() {
this.addListItem('- [ ] ', '', _('List item'));
this.addListItem('- [ ] ', '', _('List item'), true);
}
commandTextListUl() {
this.addListItem('- ', '', _('List item'));
this.addListItem('- ', '', _('List item'), true);
}
// Converting multiple lines to a numbered list will use the same number on each line
// Not ideal, but the rendered text will still be correct.
commandTextListOl() {
let bulletNumber = markdownUtils.olLineNumber(this.selectionRangeCurrentLine());
if (!bulletNumber) bulletNumber = markdownUtils.olLineNumber(this.selectionRangePreviousLine());
if (!bulletNumber) bulletNumber = 0;
this.addListItem(`${bulletNumber + 1}. `, '', _('List item'));
this.addListItem(`${bulletNumber + 1}. `, '', _('List item'), true);
}
commandTextHeading() {
@ -1938,14 +1946,17 @@ class NoteTextComponent extends React.Component {
paddingLeft: 8,
paddingRight: 8,
marginRight: rootStyle.paddingLeft,
color: theme.color,
color: theme.textStyle.color,
fontSize: theme.textStyle.fontSize * 1.25 *1.5,
backgroundColor: theme.backgroundColor,
border: '1px solid',
borderColor: theme.dividerColor,
fontSize: theme.fontSize,
};
const toolbarStyle = {};
const toolbarStyle = {
marginTop: 3,
marginBottom: 0,
};
const tagStyle = {
marginBottom: 10,
@ -1956,10 +1967,10 @@ class NoteTextComponent extends React.Component {
let bottomRowHeight = 0;
if (this.canDisplayTagBar()) {
bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - tagStyle.height - tagStyle.marginBottom;
bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - toolbarStyle.marginTop - toolbarStyle.marginBottom - tagStyle.height - tagStyle.marginBottom;
} else {
toolbarStyle.marginBottom = tagStyle.marginBottom,
bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - toolbarStyle.marginBottom;
bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - toolbarStyle.marginTop - toolbarStyle.marginBottom;
}
bottomRowHeight -= searchBarHeight;

View File

@ -70,7 +70,7 @@ class OneDriveLoginScreenComponent extends React.Component {
return (
<div>
<Header style={headerStyle}/>
<div style={{padding: 10}}>
<div style={{ padding: 10 }}>
{logComps}
</div>
</div>

View File

@ -680,6 +680,11 @@ class SideBarComponent extends React.Component {
id: selectedItem.id,
});
}
if (keyCode === 65 && (event.ctrlKey || event.metaKey)) {
// Ctrl+A key
event.preventDefault();
}
}
onHeaderClick_(key, event) {

View File

@ -25,12 +25,11 @@ class NoteListUtils {
menu.append(
new MenuItem({
label: _('Add or remove tags'),
enabled: noteIds.length === 1,
click: async () => {
props.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteId: noteIds[0],
noteIds: noteIds,
});
},
})

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.179",
"version": "1.0.181",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@ -75,7 +75,7 @@
"babel-cli": "^6.26.0",
"babel-preset-react": "^6.24.1",
"electron": "^7.1.9",
"electron-builder": "^21.2.0",
"electron-builder": "20.15.0",
"electron-rebuild": "^1.8.8"
},
"optionalDependencies": {

View File

@ -62,9 +62,9 @@ class Dialog extends React.PureComponent {
this.styles_[this.props.theme] = {
dialogBox: Object.assign({}, theme.dialogBox, { minWidth: '50%', maxWidth: '50%' }),
input: Object.assign({}, theme.inputStyle, { flex: 1 }),
row: {overflow: 'hidden', height: itemHeight, display: 'flex', justifyContent: 'center', flexDirection: 'column', paddingLeft: 10, paddingRight: 10},
row: { overflow: 'hidden', height: itemHeight, display: 'flex', justifyContent: 'center', flexDirection: 'column', paddingLeft: 10, paddingRight: 10 },
help: Object.assign({}, theme.textStyle, { marginBottom: 10 }),
inputHelpWrapper: {display: 'flex', flexDirection: 'row', alignItems: 'center'},
inputHelpWrapper: { display: 'flex', flexDirection: 'row', alignItems: 'center' },
};
const rowTextStyle = {
@ -254,7 +254,7 @@ class Dialog extends React.PureComponent {
return (
<div key={item.id} style={rowStyle} onClick={this.listItem_onClick} data-id={item.id} data-parent-id={item.parent_id}>
<div style={style.rowTitle} dangerouslySetInnerHTML={{__html: titleHtml}}></div>
<div style={style.rowTitle} dangerouslySetInnerHTML={{ __html: titleHtml }}></div>
{pathComp}
</div>
);

View File

@ -176,6 +176,14 @@ class BaseApplication {
continue;
}
if (arg === '--no-sandbox') {
// Electron-specific flag for running the app without chrome-sandbox
// Allows users to use it as a workaround for the electron+AppImage issue
// https://github.com/laurent22/joplin/issues/2246
argv.splice(0, 1);
continue;
}
if (arg.length && arg[0] == '-') {
throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
} else {

View File

@ -24,4 +24,4 @@ const injectCustomStyles = async cssFilePath => {
document.head.appendChild(styleTag);
};
module.exports = {loadCustomCss, injectCustomStyles};
module.exports = { loadCustomCss, injectCustomStyles };

View File

@ -3,7 +3,7 @@ const { shim } = require('lib/shim.js');
const JoplinError = require('lib/JoplinError');
const { rtrimSlashes } = require('lib/path-utils.js');
const base64 = require('base-64');
const {_ } = require('lib/locale');
const { _ } = require('lib/locale');
interface JoplinServerApiOptions {
username: Function,

View File

@ -45,7 +45,7 @@ TemplateUtils.loadTemplates = async function(filePath) {
// Make sure templates are always in the same order
// sensitivity ensures that the sort will ignore case
files.sort((a, b) => { return a.path.localeCompare(b.path, undefined, {sensitivity: 'accent'}); });
files.sort((a, b) => { return a.path.localeCompare(b.path, undefined, { sensitivity: 'accent' }); });
files.forEach(async file => {
if (file.path.endsWith('.md')) {

View File

@ -99,7 +99,7 @@ class CameraView extends Component {
return (
<TouchableOpacity onPress={onPress} style={Object.assign({}, style)}>
<View style={{borderRadius: 32, width: 60, height: 60, borderColor: '#00000040', borderWidth: 1, borderStyle: 'solid', backgroundColor: '#ffffff77', justifyContent: 'center', alignItems: 'center', alignSelf: 'baseline' }}>
<View style={{ borderRadius: 32, width: 60, height: 60, borderColor: '#00000040', borderWidth: 1, borderStyle: 'solid', backgroundColor: '#ffffff77', justifyContent: 'center', alignItems: 'center', alignSelf: 'baseline' }}>
{ icon }
</View>
</TouchableOpacity>
@ -152,7 +152,7 @@ class CameraView extends Component {
const displayRatios = shim.mobilePlatform() === 'android' && this.state.ratios.length > 1;
const reverseCameraButton = this.renderButton(this.reverse_onPress, 'md-reverse-camera', { flex: 1, flexDirection: 'row', justifyContent: 'flex-start', marginLeft: 20 });
const ratioButton = !displayRatios ? <View style={{ flex: 1 }}/> : this.renderButton(this.ratio_onPress, <Text style={{fontWeight: 'bold', fontSize: 20}}>{Setting.value('camera.ratio')}</Text>, { flex: 1, flexDirection: 'row', justifyContent: 'flex-end', marginRight: 20 });
const ratioButton = !displayRatios ? <View style={{ flex: 1 }}/> : this.renderButton(this.ratio_onPress, <Text style={{ fontWeight: 'bold', fontSize: 20 }}>{Setting.value('camera.ratio')}</Text>, { flex: 1, flexDirection: 'row', justifyContent: 'flex-end', marginRight: 20 });
let cameraRatio = '4:3';
const cameraProps = {};

View File

@ -170,7 +170,7 @@ class ScreenHeaderComponent extends React.PureComponent {
// Duplicate all selected notes. ensureUniqueTitle is set to true to use the
// original note's name as a root for the new unique identifier.
await Note.duplicateMultipleNotes(noteIds, {ensureUniqueTitle: true});
await Note.duplicateMultipleNotes(noteIds, { ensureUniqueTitle: true });
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
}

View File

@ -443,7 +443,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
const profileExportPrompt = (
<View style={this.styles().settingContainer}>
<Text style={this.styles().settingText}>Path:</Text>
<TextInput style={{marginRight: 20}} onChange={(event) => this.setState({profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard"></TextInput>
<TextInput style={{ marginRight: 20 }} onChange={(event) => this.setState({ profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard"></TextInput>
<Button title="OK" onPress={this.exportProfileButtonPress2_}></Button>
</View>
);

View File

@ -81,7 +81,7 @@ class SelectDateTimeDialog extends React.PureComponent {
width={0.9}
height={350}
>
<View style={{flex: 1, margin: 20, alignItems: 'center'}}>
<View style={{ flex: 1, margin: 20, alignItems: 'center' }}>
<DatePicker
date={this.state.date}
mode="datetime"
@ -90,7 +90,7 @@ class SelectDateTimeDialog extends React.PureComponent {
confirmBtnText={_('Confirm')}
cancelBtnText={_('Cancel')}
onDateChange={(date) => { this.setState({ date: this.stringToDate(date) }); }}
style={{width: 300}}
style={{ width: 300 }}
customStyles={{
btnConfirm: {
paddingVertical: 0,

View File

@ -11,7 +11,7 @@ function addResourceTag(lines, resource, attributes) {
const src = `:/${resource.id}`;
if (resourceUtils.isImageMimeType(resource.mime)) {
lines.push(resourceUtils.imgElement({src, attributes}));
lines.push(resourceUtils.imgElement({ src, attributes }));
} else if (resource.mime === 'audio/x-m4a') {
/**
* TODO: once https://github.com/laurent22/joplin/issues/1794 is resolved,
@ -162,9 +162,9 @@ async function enexXmlToHtml(xmlString, resources, options = {}) {
const beautifyHtml = (html) => {
return new Promise((resolve) => {
const options = {wrap: 0};
const options = { wrap: 0 };
cleanHtml.clean(html, options, (...cleanedHtml) => resolve(cleanedHtml));
});
};
module.exports = {enexXmlToHtml};
module.exports = { enexXmlToHtml };

View File

@ -28,6 +28,7 @@ const plugins = {
insert: { module: require('markdown-it-ins') },
multitable: { module: require('markdown-it-multimd-table'), options: { multiline: true, rowspan: true, headerless: true } },
toc: { module: require('markdown-it-toc-done-right'), options: { listType: 'ul', slugify: uslugify } },
expand_tabs: { module: require('markdown-it-expand-tabs'), options: { tabWidth: 4 } },
};
const defaultNoteStyle = require('./defaultNoteStyle');

View File

@ -1,5 +1,5 @@
const fs = require('fs-extra');
const {dirname } = require('../pathUtils');
const { dirname } = require('../pathUtils');
const rootDir = dirname(__dirname);
const assetsDir = `${rootDir}/assets`;

View File

@ -14,6 +14,7 @@ module.exports = function(style, options) {
body {
font-size: ${style.htmlFontSize};
color: ${style.htmlColor};
word-wrap: break-word;
line-height: ${style.htmlLineHeight};
background-color: ${style.htmlBackgroundColor};
font-family: ${fontFamily};

View File

@ -26,6 +26,7 @@
"markdown-it-anchor": "^5.2.5",
"markdown-it-deflist": "^2.0.3",
"markdown-it-emoji": "^1.4.0",
"markdown-it-expand-tabs": "^1.0.13",
"markdown-it-footnote": "^3.0.2",
"markdown-it-ins": "^3.0.0",
"markdown-it-mark": "^3.0.0",

View File

@ -511,7 +511,11 @@ class Note extends BaseItem {
if (!originalNote) throw new Error(`Unknown note: ${noteId}`);
let newNote = Object.assign({}, originalNote);
delete newNote.id;
const fieldsToReset = ['id', 'created_time', 'updated_time', 'user_created_time', 'user_updated_time'];
for (let field of fieldsToReset) {
delete newNote[field];
}
for (let n in changes) {
if (!changes.hasOwnProperty(n)) continue;

View File

@ -345,8 +345,8 @@ class Setting extends BaseModel {
},
// Deprecated - use markdown.plugin.*
'markdown.softbreaks': { value: false, type: Setting.TYPE_BOOL, public: false, appTypes: ['mobile', 'desktop']},
'markdown.typographer': { value: false, type: Setting.TYPE_BOOL, public: false, appTypes: ['mobile', 'desktop']},
'markdown.softbreaks': { value: false, type: Setting.TYPE_BOOL, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.typographer': { value: false, type: Setting.TYPE_BOOL, public: false, appTypes: ['mobile', 'desktop'] },
// Deprecated
'markdown.plugin.softbreaks': { value: false, type: Setting.TYPE_BOOL, section: 'plugins', public: true, appTypes: ['mobile', 'desktop'], label: () => _('Enable soft breaks') },
@ -502,13 +502,13 @@ class Setting extends BaseModel {
'Tabloid': _('Tabloid'),
'Legal': _('Legal'),
};
}},
} },
'export.pdfPageOrientation': { value: 'portrait', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('Page orientation for PDF export'), options: () => {
return {
'portrait': _('Portrait'),
'landscape': _('Landscape'),
};
}},
} },
'net.customCertificates': {

View File

@ -112,6 +112,21 @@ class Tag extends BaseItem {
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
}
static async commonTagsByNoteIds(noteIds) {
if (!noteIds || noteIds.length === 0) {
return [];
}
let commonTagIds = await NoteTag.tagIdsByNoteId(noteIds[0]);
for (let i = 1; i < noteIds.length; i++) {
const tagIds = await NoteTag.tagIdsByNoteId(noteIds[i]);
commonTagIds = commonTagIds.filter(value => tagIds.includes(value));
if (commonTagIds.length === 0) {
break;
}
}
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${commonTagIds.join('","')}")`);
}
static async loadByTitle(title) {
return this.loadByField('title', title, { caseInsensitive: true });
}

View File

@ -394,6 +394,11 @@ const reducer = (state = defaultState, action) => {
}
break;
case 'NOTE_SELECT_ALL':
newState = Object.assign({}, state);
newState.selectedNoteIds = newState.notes.map(n => n.id);
break;
case 'FOLDER_SELECT':
newState = changeSelectedFolder(state, action, { clearSelectedNoteIds: true });
break;

View File

@ -45,17 +45,17 @@ const attributesToStr = (attributes) =>
.map(([key, value]) => ` ${key}="${escapeQuotes(value)}"`)
.join('');
const attachmentElement = ({src, attributes, id}) =>
const attachmentElement = ({ src, attributes, id }) =>
[
`<a href='joplin://${id}' ${attributesToStr(attributes)}>`,
` ${attributes.alt || src}`,
'</a>',
].join('');
const imgElement = ({src, attributes}) =>
const imgElement = ({ src, attributes }) =>
`<img src="${src}" ${attributesToStr(attributes)} />`;
const audioElement = ({src, alt, id}) =>
const audioElement = ({ src, alt, id }) =>
[
'<audio controls preload="none" style="width:480px;">',
` <source src="${src}" type="audio/mp4" />`,

View File

@ -196,7 +196,7 @@ class InteropService {
const ModuleClass = require(modulePath);
const output = new ModuleClass();
const moduleMetadata = this.findModuleByFormat_(type, options.format, options.target);
output.setMetadata({options, ...moduleMetadata}); // TODO: Check that this metadata is equivalent to module above
output.setMetadata({ options, ...moduleMetadata }); // TODO: Check that this metadata is equivalent to module above
return output;
}

View File

@ -13,7 +13,7 @@ class InteropService_Importer_EnexToHtml extends InteropService_Importer_Base {
folder = await Folder.save({ title: folderTitle });
}
await importEnex(folder.id, this.sourcePath_, {...this.options_, outputFormat: 'html'});
await importEnex(folder.id, this.sourcePath_, { ...this.options_, outputFormat: 'html' });
return result;
}

View File

@ -4,8 +4,8 @@ const Note = require('lib/models/Note.js');
const { basename, filename, rtrimSlashes, fileExtension, dirname } = require('lib/path-utils.js');
const { shim } = require('lib/shim');
const { _ } = require('lib/locale');
const {extractImageUrls} = require('lib/markdownUtils');
const {unique} = require('lib/ArrayUtils');
const { extractImageUrls } = require('lib/markdownUtils');
const { unique } = require('lib/ArrayUtils');
const { pregQuote } = require('lib/string-utils-common');
class InteropService_Importer_Md extends InteropService_Importer_Base {

View File

@ -3,7 +3,7 @@ module.exports = {
{
"id": "8a1556e382704160808e9a7bef7135d3",
"title": "1. Welcome to Joplin! 🗒️",
"body": "# Welcome to Joplin! 🗒️\n\nJoplin is a free, open source note taking and to-do application, which helps you write and organise your notes, and synchronise them between your devices. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](https://joplinapp.org/#markdown). Joplin is available as a **💻 desktop**, **📱 mobile** and **🔡 terminal** application.\n\nThe notes in this notebook give an overview of what Joplin can do and how to use it. In general, the three applications share roughly the same functionalities; any differences will be clearly indicated.\n\n![](./AllClients.png)\n\n## Joplin is divided into three parts\n\nJoplin has three main columns:\n\n- **Sidebar** contains the list of your notebooks and tags, as well as the synchronisation status.\n- **Note List** contains the current list of notes - either the notes in the currently selected notebook, the notes in the currently selected tag, or search results.\n- **Note Editor** is the place where you write your notes in Markdown, with a viewer showing what the note will look like. You may also use an [external editor](https://joplinapp.org/#external-text-editor) to edit notes. For example, if you like WYSIWYG editors, you can use something like Typora as an external editor and it will display the note as well as any embedded images.\n\n## Writing notes in Markdown\n\nMarkdown is a lightweight markup language with plain text formatting syntax. Joplin supports a [Github-flavoured Markdown syntax](https://joplinapp.org/markdown/) with a few variations and additions.\n\nIn general, while Markdown is a markup language, it is meant to be human readable, even without being rendered. This is a simple example (you can see how it looks in the viewer panel):\n\n* * *\n\n# Heading\n\n## Sub-heading\n\nParagraphs are separated by a blank line. Text attributes _italic_, **bold** and `monospace` are supported. You can create bullet lists:\n\n* apples\n* oranges\n* pears\n\nOr numbered lists:\n\n1. wash\n2. rinse\n3. repeat\n\nThis is a [link](https://joplinapp.org) and, finally, below is a horizontal rule:\n\n* * *\n\nA lot more is possible including adding code samples, math formulae or checkbox lists - see the [Markdown documentation](https://joplinapp.org/#markdown) for more information.\n\n## Organising your notes\n\n### With notebooks 📔\n\nJoplin notes are organised into a tree of notebooks and sub-notebooks.\n\n- On **desktop**, you can create a notebook by clicking on New Notebook, then you can drag and drop them into other notebooks to organise them as you wish.\n- On **mobile**, press the \"+\" icon and select \"New notebook\".\n- On **terminal**, press `:mn`\n\n![](./SubNotebooks.png)\n\n### With tags 🏷️\n\nThe second way to organise your notes is using tags:\n\n- On **desktop**, right-click on any note in the Note List, and select \"Edit tags\". You can then add the tags, separating them by commas.\n- On **mobile**, open the note and press the \"⋮\" button and select \"Tags\".\n- On **terminal**, type `:help tag` for the available commands.\n",
"body": "Joplin is a free, open source note taking and to-do application, which helps you write and organise your notes, and synchronise them between your devices. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](https://joplinapp.org/#markdown). Joplin is available as a **💻 desktop**, **📱 mobile** and **🔡 terminal** application.\n\nThe notes in this notebook give an overview of what Joplin can do and how to use it. In general, the three applications share roughly the same functionalities; any differences will be clearly indicated.\n\n![](./AllClients.png)\n\n## Joplin is divided into three parts\n\nJoplin has three main columns:\n\n- **Sidebar** contains the list of your notebooks and tags, as well as the synchronisation status.\n- **Note List** contains the current list of notes - either the notes in the currently selected notebook, the notes in the currently selected tag, or search results.\n- **Note Editor** is the place where you write your notes in Markdown, with a viewer showing what the note will look like. You may also use an [external editor](https://joplinapp.org/#external-text-editor) to edit notes. For example, if you like WYSIWYG editors, you can use something like Typora as an external editor and it will display the note as well as any embedded images.\n\n## Writing notes in Markdown\n\nMarkdown is a lightweight markup language with plain text formatting syntax. Joplin supports a [Github-flavoured Markdown syntax](https://joplinapp.org/markdown/) with a few variations and additions.\n\nIn general, while Markdown is a markup language, it is meant to be human readable, even without being rendered. This is a simple example (you can see how it looks in the viewer panel):\n\n* * *\n\n# Heading\n\n## Sub-heading\n\nParagraphs are separated by a blank line. Text attributes _italic_, **bold** and `monospace` are supported. You can create bullet lists:\n\n* apples\n* oranges\n* pears\n\nOr numbered lists:\n\n1. wash\n2. rinse\n3. repeat\n\nThis is a [link](https://joplinapp.org) and, finally, below is a horizontal rule:\n\n* * *\n\nA lot more is possible including adding code samples, math formulae or checkbox lists - see the [Markdown documentation](https://joplinapp.org/#markdown) for more information.\n\n## Organising your notes\n\n### With notebooks 📔\n\nJoplin notes are organised into a tree of notebooks and sub-notebooks.\n\n- On **desktop**, you can create a notebook by clicking on New Notebook, then you can drag and drop them into other notebooks to organise them as you wish.\n- On **mobile**, press the \"+\" icon and select \"New notebook\".\n- On **terminal**, press `:mn`\n\n![](./SubNotebooks.png)\n\n### With tags 🏷️\n\nThe second way to organise your notes is using tags:\n\n- On **desktop**, right-click on any note in the Note List, and select \"Edit tags\". You can then add the tags, separating them by commas.\n- On **mobile**, open the note and press the \"⋮\" button and select \"Tags\".\n- On **terminal**, type `:help tag` for the available commands.\n",
"tags": [],
"resources": {
"./AllClients.png": {
@ -20,7 +20,7 @@ module.exports = {
{
"id": "b863cbc514cb4cafbae8dd6a4fcad919",
"title": "2. Importing and exporting notes ↔️",
"body": "# Importing and exporting notes ↔️\n\n## Importing from Evernote\n\nJoplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, images, attached files and note metadata (such as author, geo-location, etc.) via ENEX files.\n\nTo import Evernote data, first export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). Then, on **desktop**, do the following: Open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc. Read [more about Evernote import](https://joplinapp.org/#importing-from-evernote).\n\n# Importing from other apps\n\nJoplin can also import notes from [many other apps](https://github.com/laurent22/joplin#importing-from-other-applications) as well as [from Markdown or text files](https://github.com/laurent22/joplin#importing-from-markdown-files).\n\n# Exporting notes\n\nJoplin can export to the JEX format (Joplin Export file), which is an archive that can contain multiple notes, notebooks, etc. This is a format mostly designed for backup purposes. You may also export to other formats such as plain Markdown files, to JSON or to PDF. Find out [more about exporting notes](https://github.com/laurent22/joplin#exporting) on the official website.",
"body": "## Importing from Evernote\n\nJoplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, images, attached files and note metadata (such as author, geo-location, etc.) via ENEX files.\n\nTo import Evernote data, first export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). Then, on **desktop**, do the following: Open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc. Read [more about Evernote import](https://joplinapp.org/#importing-from-evernote).\n\n# Importing from other apps\n\nJoplin can also import notes from [many other apps](https://github.com/laurent22/joplin#importing-from-other-applications) as well as [from Markdown or text files](https://github.com/laurent22/joplin#importing-from-markdown-files).\n\n# Exporting notes\n\nJoplin can export to the JEX format (Joplin Export file), which is an archive that can contain multiple notes, notebooks, etc. This is a format mostly designed for backup purposes. You may also export to other formats such as plain Markdown files, to JSON or to PDF. Find out [more about exporting notes](https://github.com/laurent22/joplin#exporting) on the official website.",
"tags": [],
"resources": {},
"parent_id": "9bb5d498aba74cc6a047cfdc841e82a1"
@ -28,7 +28,7 @@ module.exports = {
{
"id": "25b656aac0564d1a91ab98295aa3cc58",
"title": "3. Synchronising your notes 🔄",
"body": "# Synchronising your notes 🔄\n\nOne of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. You basically choose the service you prefer among those supported, setup the configuration, and the app will be able to sync between your computers or mobile devices.\n\nThe supported cloud services are the following:\n\n## Setting up Dropbox synchronisation\n\nSelect \"Dropbox\" as the synchronisation target in the config screen (it is selected by default). Then, to initiate the synchronisation process, click on the \"Synchronise\" button in the sidebar and follow the instructions.\n\n## Setting up Nextcloud synchronisation\n\nNextcloud is a self-hosted, private cloud solution. It can store documents, images and videos but also calendars, passwords and countless other things and can sync them to your laptop or phone. As you can host your own Nextcloud server, you own both the data on your device and infrastructure used for synchronisation. As such it is a good fit for Joplin.\n\nTo set it up, go to the config screen and select Nextcloud as the synchronisation target. Then input the WebDAV URL (to get it, go to your Nextcloud page, click on Settings in the bottom left corner of the page and copy the URL). Note that it has to be the **full URL**, so for example if you want the notes to be under `/Joplin`, the URL would be something like `https://example.com/remote.php/webdav/Joplin` (note that \"/Joplin\" part). And **make sure to create the \"/Joplin\" directory in Nextcloud**. Finally set the username and password. If it does not work, please [see this explanation](https://github.com/laurent22/joplin/issues/61#issuecomment-373282608) for more details.\n\n## Setting up OneDrive or WebDAV synchronisation\n\nOneDrive and WebDAV are also supported as synchronisation services. Please see [the export documentation](https://github.com/laurent22/joplin#exporting) for more information.\n\n## Using End-To-End Encryption\n\nJoplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the data can read it. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](https://joplinapp.org/e2ee/) for more information about this feature and how to enable it.",
"body": "One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. You basically choose the service you prefer among those supported, setup the configuration, and the app will be able to sync between your computers or mobile devices.\n\nThe supported cloud services are the following:\n\n## Setting up Dropbox synchronisation\n\nSelect \"Dropbox\" as the synchronisation target in the config screen (it is selected by default). Then, to initiate the synchronisation process, click on the \"Synchronise\" button in the sidebar and follow the instructions.\n\n## Setting up Nextcloud synchronisation\n\nNextcloud is a self-hosted, private cloud solution. It can store documents, images and videos but also calendars, passwords and countless other things and can sync them to your laptop or phone. As you can host your own Nextcloud server, you own both the data on your device and infrastructure used for synchronisation. As such it is a good fit for Joplin.\n\nTo set it up, go to the config screen and select Nextcloud as the synchronisation target. Then input the WebDAV URL (to get it, go to your Nextcloud page, click on Settings in the bottom left corner of the page and copy the URL). Note that it has to be the **full URL**, so for example if you want the notes to be under `/Joplin`, the URL would be something like `https://example.com/remote.php/webdav/Joplin` (note that \"/Joplin\" part). And **make sure to create the \"/Joplin\" directory in Nextcloud**. Finally set the username and password. If it does not work, please [see this explanation](https://github.com/laurent22/joplin/issues/61#issuecomment-373282608) for more details.\n\n## Setting up OneDrive or WebDAV synchronisation\n\nOneDrive and WebDAV are also supported as synchronisation services. Please see [the export documentation](https://github.com/laurent22/joplin#exporting) for more information.\n\n## Using End-To-End Encryption\n\nJoplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the data can read it. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](https://joplinapp.org/e2ee/) for more information about this feature and how to enable it.",
"tags": [],
"resources": {},
"parent_id": "9bb5d498aba74cc6a047cfdc841e82a1"
@ -36,7 +36,7 @@ module.exports = {
{
"id": "2ee48f80889447429a3cccb04a466072",
"title": "4. Tips 💡",
"body": "# Tips 💡\n\nThe first few notes should have given you an overview of the main functionalities of Joplin, but there's more it can do. See below for some of these features and how to get more help using the app:\n\n## Web clipper\n\n![](./WebClipper.png)\n\nThe Web Clipper is a browser extension that allows you to save web pages and screenshots from your browser. To start using it, open the Joplin desktop application, go to the Web Clipper Options and follow the instructions.\n\nMore info on the official website: https://joplinapp.org/clipper/\n\n## Attachments\n\nAny kind of file can be attached to a note. In Markdown, links to these files are represented as an ID. In the note viewer, these files, if they are images, will be displayed or, if they are other files (PDF, text files, etc.) they will be displayed as links. Clicking on this link will open the file in the default application.\n\nImages can be attached either by clicking on \"Attach file\" or by pasting (with `Ctrl+V` or `Cmd+V`) an image directly in the editor, or by drag and dropping an image.\n\nMore info about attachments: https://joplinapp.org#attachments--resources\n\n## Search\n\nJoplin supports advanced search queries, which are fully documented on the official website: https://joplinapp.org#searching\n\n## Alarms\n\nAn alarm can be associated with any to-do. It will be triggered at the given time by displaying a notification. To use this feature, see the documentation: https://joplinapp.org#notifications\n\n## Markdown advanced tips\n\nJoplin uses and renders [Github-flavoured Markdown](https://joplinapp.org/markdown/) with a few variations and additions.\n\nFor example, tables are supported:\n\n| Tables | Are | Cool |\n| ------------- |:-------------:| -----:|\n| col 3 is | right-aligned | $1600 |\n| col 2 is | centered | $12 |\n| zebra stripes | are neat | $1 |\n\nYou can also create lists of checkboxes. These checkboxes can be ticked directly in the viewer, or by adding an \"x\" inside:\n\n- [ ] Milk\n- [ ] Eggs\n- [x] Beer\n\nMath expressions can be added using the [KaTeX notation](https://khan.github.io/KaTeX/):\n\n$$\nf(x) = \\int_{-\\infty}^\\infty\n \\hat f(\\xi)\\,e^{2 \\pi i \\xi x}\n \\,d\\xi\n$$\n\nVarious other tricks are possible, such as using HTML, or customising the CSS. See the Markdown documentation for more info - https://joplinapp.org#markdown\n\n## Community and further help\n\n- For general discussion about Joplin, user support, software development questions, and to discuss new features, go to the [Joplin Forum](https://discourse.joplinapp.org/). It is possible to login with your GitHub account.\n- The latest news are posted [on the Patreon page](https://www.patreon.com/joplin).\n- For bug reports and feature requests, go to the [GitHub Issue Tracker](https://github.com/laurent22/joplin/issues).\n\n## Donations\n\nDonations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.\n\nPlease see the [donation page](https://joplinapp.org/donate/) for information on how to support the development of Joplin.",
"body": "The first few notes should have given you an overview of the main functionalities of Joplin, but there's more it can do. See below for some of these features and how to get more help using the app:\n\n## Web clipper\n\n![](./WebClipper.png)\n\nThe Web Clipper is a browser extension that allows you to save web pages and screenshots from your browser. To start using it, open the Joplin desktop application, go to the Web Clipper Options and follow the instructions.\n\nMore info on the official website: https://joplinapp.org/clipper/\n\n## Attachments\n\nAny kind of file can be attached to a note. In Markdown, links to these files are represented as an ID. In the note viewer, these files, if they are images, will be displayed or, if they are other files (PDF, text files, etc.) they will be displayed as links. Clicking on this link will open the file in the default application.\n\nImages can be attached either by clicking on \"Attach file\" or by pasting (with `Ctrl+V` or `Cmd+V`) an image directly in the editor, or by drag and dropping an image.\n\nMore info about attachments: https://joplinapp.org#attachments--resources\n\n## Search\n\nJoplin supports advanced search queries, which are fully documented on the official website: https://joplinapp.org#searching\n\n## Alarms\n\nAn alarm can be associated with any to-do. It will be triggered at the given time by displaying a notification. To use this feature, see the documentation: https://joplinapp.org#notifications\n\n## Markdown advanced tips\n\nJoplin uses and renders [Github-flavoured Markdown](https://joplinapp.org/markdown/) with a few variations and additions.\n\nFor example, tables are supported:\n\n| Tables | Are | Cool |\n| ------------- |:-------------:| -----:|\n| col 3 is | right-aligned | $1600 |\n| col 2 is | centered | $12 |\n| zebra stripes | are neat | $1 |\n\nYou can also create lists of checkboxes. These checkboxes can be ticked directly in the viewer, or by adding an \"x\" inside:\n\n- [ ] Milk\n- [ ] Eggs\n- [x] Beer\n\nMath expressions can be added using the [KaTeX notation](https://khan.github.io/KaTeX/):\n\n$$\nf(x) = \\int_{-\\infty}^\\infty\n \\hat f(\\xi)\\,e^{2 \\pi i \\xi x}\n \\,d\\xi\n$$\n\nVarious other tricks are possible, such as using HTML, or customising the CSS. See the Markdown documentation for more info - https://joplinapp.org#markdown\n\n## Community and further help\n\n- For general discussion about Joplin, user support, software development questions, and to discuss new features, go to the [Joplin Forum](https://discourse.joplinapp.org/). It is possible to login with your GitHub account.\n- The latest news are posted [on the Patreon page](https://www.patreon.com/joplin).\n- For bug reports and feature requests, go to the [GitHub Issue Tracker](https://github.com/laurent22/joplin/issues).\n\n## Donations\n\nDonations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.\n\nPlease see the [donation page](https://joplinapp.org/donate/) for information on how to support the development of Joplin.",
"tags": [],
"resources": {
"./WebClipper.png": {
@ -55,4 +55,4 @@ module.exports = {
],
"tags": [],
"timestamp": 1529668800000
}
}

View File

@ -8,7 +8,7 @@
// console.disableYellowBox = true
import {YellowBox} from 'react-native';
import { YellowBox } from 'react-native';
YellowBox.ignoreWarnings([
'Require cycle: node_modules/react-native-',
'Require cycle: node_modules/rn-fetch-blob',

View File

@ -717,10 +717,10 @@ class AppComponent extends React.Component {
let menuPosition = 'left';
if (this.props.routeName === 'Note') {
sideMenuContent = <SafeAreaView style={{flex: 1, backgroundColor: theme.backgroundColor}}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
menuPosition = 'right';
} else {
sideMenuContent = <SafeAreaView style={{flex: 1, backgroundColor: theme.backgroundColor}}><SideMenuContent/></SafeAreaView>;
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContent/></SafeAreaView>;
}
const appNavInit = {
@ -750,12 +750,12 @@ class AppComponent extends React.Component {
}}
>
<MenuContext style={{ flex: 1 }}>
<SafeAreaView style={{flex: 0, backgroundColor: theme.raisedBackgroundColor}} />
<SafeAreaView style={{flex: 1, backgroundColor: theme.backgroundColor}}>
<SafeAreaView style={{ flex: 0, backgroundColor: theme.raisedBackgroundColor }} />
<SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<AppNav screens={appNavInit} />
</SafeAreaView>
<DropdownAlert ref={ref => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
<Animated.View pointerEvents='none' style={{position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '100%'}}/>
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '100%' }}/>
</MenuContext>
</SideMenu>
);

View File

@ -566,25 +566,25 @@ async function main() {
renderMdToHtml(makeHomePageMd(), `${rootDir}/docs/index.html`, { sourceMarkdownFile: 'README.md' });
const sources = [
[ 'readme/changelog.md', 'docs/changelog/index.html', { title: 'Changelog (Desktop App)' } ],
[ 'readme/changelog_cli.md', 'docs/changelog_cli/index.html', { title: 'Changelog (CLI App)' } ],
[ 'readme/clipper.md', 'docs/clipper/index.html', { title: 'Web Clipper' } ],
[ 'readme/debugging.md', 'docs/debugging/index.html', { title: 'Debugging' } ],
[ 'readme/desktop.md', 'docs/desktop/index.html', { title: 'Desktop Application' } ],
[ 'readme/donate.md', 'docs/donate/index.html', { title: 'Donate' } ],
[ 'readme/e2ee.md', 'docs/e2ee/index.html', { title: 'End-To-End Encryption' } ],
[ 'readme/faq.md', 'docs/faq/index.html', { title: 'FAQ' } ],
[ 'readme/mobile.md', 'docs/mobile/index.html', { title: 'Mobile Application' } ],
[ 'readme/spec.md', 'docs/spec/index.html', { title: 'Specifications' } ],
[ 'readme/stats.md', 'docs/stats/index.html', { title: 'Statistics' } ],
[ 'readme/terminal.md', 'docs/terminal/index.html', { title: 'Terminal Application' } ],
[ 'readme/api.md', 'docs/api/index.html', { title: 'REST API' } ],
[ 'readme/prereleases.md', 'docs/prereleases/index.html', { title: 'Pre-releases' } ],
[ 'readme/markdown.md', 'docs/markdown/index.html', { title: 'Markdown Guide' } ],
[ 'readme/nextcloud_app.md', 'docs/nextcloud_app/index.html', { title: 'Joplin Web API for Nextcloud' } ],
['readme/changelog.md', 'docs/changelog/index.html', { title: 'Changelog (Desktop App)' }],
['readme/changelog_cli.md', 'docs/changelog_cli/index.html', { title: 'Changelog (CLI App)' }],
['readme/clipper.md', 'docs/clipper/index.html', { title: 'Web Clipper' }],
['readme/debugging.md', 'docs/debugging/index.html', { title: 'Debugging' }],
['readme/desktop.md', 'docs/desktop/index.html', { title: 'Desktop Application' }],
['readme/donate.md', 'docs/donate/index.html', { title: 'Donate' }],
['readme/e2ee.md', 'docs/e2ee/index.html', { title: 'End-To-End Encryption' }],
['readme/faq.md', 'docs/faq/index.html', { title: 'FAQ' }],
['readme/mobile.md', 'docs/mobile/index.html', { title: 'Mobile Application' }],
['readme/spec.md', 'docs/spec/index.html', { title: 'Specifications' }],
['readme/stats.md', 'docs/stats/index.html', { title: 'Statistics' }],
['readme/terminal.md', 'docs/terminal/index.html', { title: 'Terminal Application' }],
['readme/api.md', 'docs/api/index.html', { title: 'REST API' }],
['readme/prereleases.md', 'docs/prereleases/index.html', { title: 'Pre-releases' }],
['readme/markdown.md', 'docs/markdown/index.html', { title: 'Markdown Guide' }],
['readme/nextcloud_app.md', 'docs/nextcloud_app/index.html', { title: 'Joplin Web API for Nextcloud' }],
[ 'readme/gsoc2020/index.md', 'docs/gsoc2020/index.html', { title: 'Google Summer of Code' } ],
[ 'readme/gsoc2020/ideas.md', 'docs/gsoc2020/ideas.html', { title: 'GSoC: Project Ideas' } ],
['readme/gsoc2020/index.md', 'docs/gsoc2020/index.html', { title: 'Google Summer of Code' }],
['readme/gsoc2020/ideas.md', 'docs/gsoc2020/ideas.html', { title: 'GSoC: Project Ideas' }],
];
const path = require('path');

View File

@ -25,7 +25,7 @@ toolUtils.execCommandWithPipes = function(executable, args) {
var spawn = require('child_process').spawn;
return new Promise((resolve, reject) => {
const child = spawn(executable, args, { stdio: 'inherit'});
const child = spawn(executable, args, { stdio: 'inherit' });
child.on('error', (error) => {
reject(error);

View File

@ -12,7 +12,7 @@ async function gitHubContributors(page) {
request.get({
url: `https://api.github.com/repos/laurent22/joplin/contributors${page ? `?page=${page}` : ''}`,
json: true,
headers: {'User-Agent': 'Joplin Readme Updater'},
headers: { 'User-Agent': 'Joplin Readme Updater' },
}, (error, response, data) => {
if (error) {
reject(error);

View File

@ -22,7 +22,7 @@ async function gitHubLatestRelease() {
request.get({
url: url,
json: true,
headers: {'User-Agent': 'Joplin Readme Updater'},
headers: { 'User-Agent': 'Joplin Readme Updater' },
}, (error, response, data) => {
if (error) {
reject(error);