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

Compare commits

...

24 Commits

Author SHA1 Message Date
Laurent Cozic
8c65a7cc31 Electron release v1.0.78 2018-03-17 15:13:00 +00:00
Laurent Cozic
aabb9be7de Mobile: Resolves #285: Create, edit and remove tags from notes 2018-03-16 20:17:52 +00:00
Laurent Cozic
544f93bf22 All: Handle deletion of resources that are not linked to any note 2018-03-16 17:39:44 +00:00
Laurent Cozic
f81dbf4a4c CLI v1.0.103 2018-03-16 14:36:09 +00:00
Laurent Cozic
fbec8263a3 Electron release v1.0.77 2018-03-16 14:34:10 +00:00
Laurent Cozic
68d77a69e6 Updated translations 2018-03-16 14:33:53 +00:00
Laurent Cozic
f2ef2446c6 Merge branch 'master' of github.com:laurent22/joplin 2018-03-16 14:33:16 +00:00
Laurent Cozic
875cb5387a Merge pull request #306 from tobias-grasse/tobias-grasse-patch-1
Update de_DE.po
2018-03-16 14:33:02 +00:00
Laurent Cozic
ae9ecdad40 All: Fix database upgrade 2018-03-16 14:32:47 +00:00
Laurent Cozic
86a0e34975 Update faq.md 2018-03-16 14:19:24 +00:00
Laurent Cozic
1141074745 Update faq.md 2018-03-16 14:19:10 +00:00
Tobias Grasse
efc46d9989 Update de_DE.po
Add missing translations, minor corrections for existing translations.
2018-03-16 12:13:42 +01:00
Laurent Cozic
2b45f745b6 Electron release v1.0.76 2018-03-16 10:12:27 +00:00
Laurent Cozic
37fb81e9b2 Trying to build only master branch 2018-03-16 10:12:23 +00:00
Laurent Cozic
255a4fac93 Electron release v1.0.75 2018-03-16 10:03:47 +00:00
Laurent Cozic
3e3fb88de8 Merge branch 'master' into resource_cleanup 2018-03-16 10:03:05 +00:00
Laurent Cozic
e4cf03ae46 Only build master branch 2018-03-16 10:02:58 +00:00
Laurent Cozic
554a3eb10d Electron release v1.0.74 2018-03-16 09:11:25 +00:00
Laurent Cozic
61881b528a Electron: Trying to fix signed executable issue 2018-03-16 09:11:10 +00:00
Laurent Cozic
c1bb51c12b All: Finished service to clean up resources 2018-03-15 18:08:46 +00:00
Laurent Cozic
945018b698 All: Allow deleting and syncing deleted resources 2018-03-15 17:46:54 +00:00
Laurent Cozic
df7b981e5e Merge branch 'master' into resource_cleanup 2018-03-15 18:19:06 +00:00
Laurent Cozic
c9e130a771 Merge branch 'master' into resource_cleanup 2018-03-14 17:41:06 +00:00
Laurent Cozic
f595be07d4 Adding service to keep track of note resources associations 2018-03-12 23:40:43 +00:00
69 changed files with 1053 additions and 492 deletions

View File

@@ -3,6 +3,14 @@ if: tag IS present
rvm: 2.3.3
# It's important to only build production branches otherwise Electron Builder
# might take assets from dev branches and overwrite those of production.
# https://docs.travis-ci.com/user/customizing-the-build/#Building-Specific-Branches
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
matrix:
include:
- os: osx

View File

@@ -5,6 +5,7 @@ const { JoplinDatabase } = require('lib/joplin-database.js');
const { Database } = require('lib/database.js');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const ResourceService = require('lib/services/ResourceService');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
@@ -412,6 +413,8 @@ class Application extends BaseApplication {
const tags = await Tag.allWithNotes();
ResourceService.runInBackground();
this.dispatch({
type: 'TAG_UPDATE_ALL',
items: tags,

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Tobias Strobel <git@strobeltobias.de>\n"
"Last-Translator: Tobias Grasse <mail@tobias-grasse.net>\n"
"Language-Team: \n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
@@ -38,7 +38,7 @@ msgid "No notebook selected."
msgstr "Kein Notizbuch ausgewählt."
msgid "No notebook has been specified."
msgstr "Kein Notizbuch wurde ausgewählt."
msgstr "Es wurde kein Notizbuch festgelegt."
msgid "Y"
msgstr "J"
@@ -586,9 +586,8 @@ msgstr "Exportiere „%s“ ins „%s“ Format. Bitte warten..."
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr "Importiere „%s“ ins „%s“ Format. Bitte warten…"
#, fuzzy
msgid "PDF File"
msgstr "Datei"
msgstr "PDF-Datei"
msgid "File"
msgstr "Datei"
@@ -609,7 +608,7 @@ msgid "Export"
msgstr "Exportieren"
msgid "Print"
msgstr ""
msgstr "Drucken"
#, javascript-format
msgid "Hide %s"
@@ -657,9 +656,8 @@ msgstr "Hilfe"
msgid "Website and documentation"
msgstr "Webseite und Dokumentation"
#, fuzzy
msgid "Make a donation"
msgstr "Webseite und Dokumentation"
msgstr "Spenden"
msgid "Check for updates..."
msgstr "Überprüfe auf Updates..."
@@ -1016,8 +1014,8 @@ msgstr "Remote Objekte gelöscht: %d."
msgid "Fetched items: %d/%d."
msgstr "Geladene Objekte: %d/%d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Status: \"%s\"."
msgid "Cancelling..."
@@ -1027,15 +1025,15 @@ msgstr "Abbrechen..."
msgid "Completed: %s"
msgstr "Abgeschlossen: %s"
#, fuzzy, javascript-format
#, javascript-format
msgid "Last error: %s"
msgstr "Schwerwiegender Fehler:"
msgstr "Letzte Fehlermeldung: %s"
msgid "Idle"
msgstr ""
msgstr "wartend"
msgid "In progress"
msgstr ""
msgstr "In Bearbeitung"
#, javascript-format
msgid "Synchronisation is already in progress. State: %s"
@@ -1333,7 +1331,7 @@ msgid "Cancel synchronisation"
msgstr "Synchronisation abbrechen"
msgid "Joplin website"
msgstr ""
msgstr "Website von Joplin"
#, javascript-format
msgid "Master Key %s"

View File

@@ -897,7 +897,7 @@ msgid "Fetched items: %d/%d."
msgstr ""
#, javascript-format
msgid "State: \"%s\"."
msgid "State: %s."
msgstr ""
msgid "Cancelling..."

View File

@@ -993,8 +993,8 @@ msgstr "Elementos remotos borrados: %d."
msgid "Fetched items: %d/%d."
msgstr "Elementos obtenidos: %d/%d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Estado: «%s»."
msgid "Cancelling..."

View File

@@ -1000,8 +1000,8 @@ msgstr "Urruneko itemak ezabatuta: %d."
msgid "Fetched items: %d/%d."
msgstr "Itemak eskuratuta: %d%d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Egoera: \"%s\"."
msgid "Cancelling..."

View File

@@ -14,6 +14,8 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
msgid "To delete a tag, untag the associated notes."
msgstr "Pour supprimer une vignette, enlever là des notes associées."
@@ -1000,8 +1002,8 @@ msgid "Fetched items: %d/%d."
msgstr "Téléchargés : %d/%d."
#, javascript-format
msgid "State: \"%s\"."
msgstr "État : \"%s\"."
msgid "State: %s."
msgstr "État : %s."
msgid "Cancelling..."
msgstr "Annulation..."

View File

@@ -987,8 +987,8 @@ msgstr "Obrisane udaljene stavke: %d."
msgid "Fetched items: %d/%d."
msgstr "Stvorene lokalne stavke: %d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Stanje: \"%s\"."
msgid "Cancelling..."

View File

@@ -973,8 +973,8 @@ msgstr "Elementi remoti eliminati: %d."
msgid "Fetched items: %d/%d."
msgstr "Elementi locali creati: %d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Stato: \"%s\"."
msgid "Cancelling..."

View File

@@ -975,8 +975,8 @@ msgstr "リモートアイテムの削除: %d."
msgid "Fetched items: %d/%d."
msgstr "ローカルアイテムの作成: %d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "状態: \"%s\"。"
msgid "Cancelling..."

View File

@@ -897,7 +897,7 @@ msgid "Fetched items: %d/%d."
msgstr ""
#, javascript-format
msgid "State: \"%s\"."
msgid "State: %s."
msgstr ""
msgid "Cancelling..."

View File

@@ -1002,8 +1002,8 @@ msgstr "Verwijderde remote items: %d."
msgid "Fetched items: %d/%d."
msgstr "Opgehaalde items: %d/%d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Status: \"%s\""
msgid "Cancelling..."

View File

@@ -993,8 +993,8 @@ msgstr "Itens remotos excluídos: %d."
msgid "Fetched items: %d/%d."
msgstr "Itens pesquisados: %d/%d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Estado: \"%s\"."
msgid "Cancelling..."

View File

@@ -993,8 +993,8 @@ msgstr "Удалено удалённых элементов: %d."
msgid "Fetched items: %d/%d."
msgstr "Получено элементов: %d/%d."
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "Статус: «%s»."
msgid "Cancelling..."

View File

@@ -941,8 +941,8 @@ msgstr "已删除远程项目: %d。"
msgid "Fetched items: %d/%d."
msgstr "已新建本地项目: %d。"
#, javascript-format
msgid "State: \"%s\"."
#, fuzzy, javascript-format
msgid "State: %s."
msgstr "状态:\"%s\"。"
msgid "Cancelling..."

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.0.101",
"version": "1.0.103",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -19,7 +19,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.0.101",
"version": "1.0.103",
"bin": {
"joplin": "./main.js"
},

View File

@@ -0,0 +1,99 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const InteropService = require('lib/services/InteropService.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const Resource = require('lib/models/Resource.js');
const NoteResource = require('lib/models/NoteResource.js');
const ResourceService = require('lib/services/ResourceService.js');
const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
function exportDir() {
return __dirname + '/export';
}
function fieldsEqual(model1, model2, fieldNames) {
for (let i = 0; i < fieldNames.length; i++) {
const f = fieldNames[i];
expect(model1[f]).toBe(model2[f], 'For key ' + f);
}
}
describe('services_ResourceService', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should delete orphaned resources', asyncTest(async () => {
const service = new ResourceService();
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
const resourcePath = Resource.fullPath(resource1);
await service.indexNoteResources();
await service.deleteOrphanResources(0);
expect(!!(await Resource.load(resource1.id))).toBe(true);
await Note.delete(note1.id);
await service.deleteOrphanResources(0);
expect(!!(await Resource.load(resource1.id))).toBe(true);
await service.indexNoteResources();
await service.deleteOrphanResources(1000 * 60);
expect(!!(await Resource.load(resource1.id))).toBe(true);
await service.deleteOrphanResources(0);
expect(!!(await Resource.load(resource1.id))).toBe(false);
expect(await shim.fsDriver().exists(resourcePath)).toBe(false);
expect(!(await NoteResource.all()).length).toBe(true);
}));
it('should not delete resource if still associated with at least one note', asyncTest(async () => {
const service = new ResourceService();
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 deuxième note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
const resourcePath = Resource.fullPath(resource1);
await service.indexNoteResources();
await Note.delete(note1.id);
await service.indexNoteResources();
await Note.save({ id: note2.id, body: Resource.markdownTag(resource1) });
await service.indexNoteResources();
await service.deleteOrphanResources(0);
expect(!!(await Resource.load(resource1.id))).toBe(true);
}));
});

View File

@@ -883,6 +883,37 @@ describe('Synchronizer', function() {
expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true);
}));
it('should delete resources', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
let resourcePath1 = Resource.fullPath(resource1);
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
let allResources = await Resource.all();
expect(allResources.length).toBe(1);
let all = await fileApi().list();
expect(all.items.length).toBe(3);
await Resource.delete(resource1.id);
await synchronizer().start();
all = await fileApi().list();
expect(all.items.length).toBe(2);
await switchClient(1);
expect(await shim.fsDriver().exists(resourcePath1)).toBe(true);
await synchronizer().start();
allResources = await Resource.all();
expect(allResources.length).toBe(0);
expect(await shim.fsDriver().exists(resourcePath1)).toBe(false);
}));
it('should encryt resources', asyncTest(async () => {
Setting.setValue('encryption.enabled', true);
const masterKey = await loadEncryptionMasterKey();

View File

@@ -16,6 +16,7 @@ const { FileApi } = require('lib/file-api.js');
const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav.js');
const BaseService = require('lib/services/BaseService.js');
const { FsDriverNode } = require('lib/fs-driver-node.js');
const { time } = require('lib/time-utils.js');
const { shimInit } = require('lib/shim-init-node.js');
@@ -63,7 +64,7 @@ console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTarg
const logger = new Logger();
logger.addTarget('console');
logger.addTarget('file', { path: logDir + '/log.txt' });
logger.setLevel(Logger.LEVEL_WARN); // Set to INFO to display sync process in console
logger.setLevel(Logger.LEVEL_WARN); // Set to DEBUG to display sync process in console
BaseItem.loadClass('Note', Note);
BaseItem.loadClass('Folder', Folder);
@@ -75,6 +76,8 @@ BaseItem.loadClass('MasterKey', MasterKey);
Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appType', 'cli');
BaseService.logger_ = logger;
Setting.autoSaveEnabled = false;
function syncTargetId() {
@@ -118,8 +121,9 @@ async function clearDatabase(id = null) {
'DELETE FROM tags',
'DELETE FROM note_tags',
'DELETE FROM master_keys',
'DELETE FROM settings',
'DELETE FROM item_changes',
'DELETE FROM note_resources',
'DELETE FROM settings',
'DELETE FROM deleted_items',
'DELETE FROM sync_items',
];

View File

@@ -22,6 +22,7 @@ const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('./InteropServiceHelper.js');
const ResourceService = require('lib/services/ResourceService');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
@@ -608,6 +609,8 @@ class Application extends BaseApplication {
AlarmService.garbageCollect();
}, 1000 * 60 * 60);
ResourceService.runInBackground();
if (Setting.value('env') === 'dev') {
AlarmService.updateAllNotifications();
} else {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.73",
"version": "1.0.78",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -643,7 +643,7 @@
"requires": {
"ansi-align": "2.0.0",
"camelcase": "4.1.0",
"chalk": "2.3.1",
"chalk": "2.3.2",
"cli-boxes": "1.0.0",
"string-width": "2.1.1",
"term-size": "1.2.0",
@@ -657,9 +657,9 @@
"dev": true
},
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
@@ -672,14 +672,14 @@
"dev": true
},
"chalk": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
"supports-color": "5.3.0"
}
},
"is-fullwidth-code-point": {
@@ -708,9 +708,9 @@
}
},
"supports-color": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
@@ -748,45 +748,77 @@
}
},
"builder-util": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-5.6.0.tgz",
"integrity": "sha512-5Enhnnm9gCHjzOUnVqqGjuMlx6pPA36VImQ9wgpRIIyfqLPXLVyWHOYbd0CThm/+GFMWx9xwAUyU6uL93+vwMg==",
"version": "5.6.5",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-5.6.5.tgz",
"integrity": "sha512-J03MEuyUf8li8KjTpml3r0K2Q9jZl21bHTEuGiZZX9vSMY81mXZe5AmEutsquyDrcsQFjCrDkbEDaqSc7vj5sw==",
"dev": true,
"requires": {
"7zip-bin": "3.1.0",
"app-builder-bin": "1.5.0",
"app-builder-bin": "1.7.2",
"bluebird-lst": "1.0.5",
"builder-util-runtime": "4.0.5",
"chalk": "2.3.1",
"chalk": "2.3.2",
"debug": "3.1.0",
"fs-extra-p": "4.5.2",
"is-ci": "1.1.0",
"js-yaml": "3.10.0",
"js-yaml": "3.11.0",
"lazy-val": "1.0.3",
"semver": "5.5.0",
"source-map-support": "0.5.3",
"source-map-support": "0.5.4",
"stat-mode": "0.2.2",
"temp-file": "3.1.1"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"app-builder-bin": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-1.7.2.tgz",
"integrity": "sha512-2uJICLdVnkDqizLZa4HclhBsAWiSf1sEPeKS5+GhuxGaDdWnabXZ4ed9hYQ5u81P3hW3lB+xvxDw2TTinDB9Tw==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"app-builder-bin-linux": "1.7.2",
"app-builder-bin-mac": "1.7.2",
"app-builder-bin-win": "1.7.2"
}
},
"app-builder-bin-linux": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin-linux/-/app-builder-bin-linux-1.7.2.tgz",
"integrity": "sha512-spoW8f6sqo5aKpoZx+scIPMonSTrh8JtKWM3MuDqBJiXiUCtpVIPez5c4AycGwQnmh167KFjK4pn129o3k+aHQ==",
"dev": true,
"optional": true
},
"app-builder-bin-mac": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin-mac/-/app-builder-bin-mac-1.7.2.tgz",
"integrity": "sha512-GLrQ9r17Hnc8dap2rKJ1N7ZukLBbTN88BSG4EC3xmNeafoWbekuxq3IdJYkZAT/eS1Ig4Q6nRcLI9TfnafwZEQ==",
"dev": true,
"optional": true
},
"app-builder-bin-win": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz",
"integrity": "sha512-/7tvJZas9T5TBM3QUV0xQkRQAyUlsXdtUsqtOg48mgp1ogPqDjs4W2Jr31YhhiUHDdNgamZc655PzWqAEnbZfQ==",
"dev": true,
"optional": true
},
"chalk": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
"supports-color": "5.3.0"
}
},
"debug": {
@@ -798,22 +830,6 @@
"ms": "2.0.0"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"dev": true
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@@ -827,18 +843,18 @@
"dev": true
},
"source-map-support": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz",
"integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==",
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz",
"integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==",
"dev": true,
"requires": {
"source-map": "0.6.1"
}
},
"supports-color": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
@@ -866,16 +882,6 @@
"requires": {
"ms": "2.0.0"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
}
}
},
@@ -971,9 +977,9 @@
"dev": true
},
"ci-info": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz",
"integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz",
"integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==",
"dev": true
},
"cli-boxes": {
@@ -1173,7 +1179,7 @@
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"dev": true,
"requires": {
"lru-cache": "4.1.1",
"lru-cache": "4.1.2",
"shebang-command": "1.2.0",
"which": "1.3.0"
}
@@ -1282,31 +1288,19 @@
}
},
"dmg-builder": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-4.1.1.tgz",
"integrity": "sha512-AhRa1J1coSVIUE2KpmievfIA8WI3G1Rxhf7qJYkiR5XWkJYp+6W1Z7vaCabRwEtEYyeJ0M8EdZnFT2BCYxEYVA==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-4.1.2.tgz",
"integrity": "sha512-nH7PYrRG9er8sVEOEV9lhngRyZDznViBFVTV/E5p4ZDFy5YZLDHNFWI3m7RKiWnRli5UpOwqLVn0Nxn1vJqsGg==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"builder-util": "5.6.0",
"electron-builder-lib": "20.2.0",
"builder-util": "5.6.5",
"electron-builder-lib": "20.5.1",
"fs-extra-p": "4.5.2",
"iconv-lite": "0.4.19",
"js-yaml": "3.10.0",
"js-yaml": "3.11.0",
"parse-color": "1.0.0",
"sanitize-filename": "1.6.1"
},
"dependencies": {
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
}
}
},
"dot-prop": {
@@ -1363,17 +1357,17 @@
}
},
"electron-builder": {
"version": "20.2.0",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-20.2.0.tgz",
"integrity": "sha512-gl+veD9FqunV5oGwBVhoHlHeHbVeXYiLoMw8/Cv3b91gC7XuXHoZ3oGbgaVGgycjm7suW8O6QXYaFujE8osnfw==",
"version": "20.5.1",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-20.5.1.tgz",
"integrity": "sha512-TBBWDXnqrRjTKAqnBJU/fxSxcwHjnXepTBVUEG02TZPUp7jQEdZjdvZDRvK0jM2xsGrL7q/UUS4jFqe4tRIF6g==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"builder-util": "5.6.0",
"builder-util": "5.6.5",
"builder-util-runtime": "4.0.5",
"chalk": "2.3.1",
"dmg-builder": "4.1.1",
"electron-builder-lib": "20.2.0",
"chalk": "2.3.2",
"dmg-builder": "4.1.2",
"electron-builder-lib": "20.5.1",
"electron-download-tf": "4.3.4",
"fs-extra-p": "4.5.2",
"is-ci": "1.1.0",
@@ -1385,23 +1379,23 @@
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
"supports-color": "5.3.0"
}
},
"debug": {
@@ -1441,35 +1435,6 @@
"universalify": "0.1.1"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
},
"dependencies": {
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
}
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"dev": true
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
@@ -1497,9 +1462,9 @@
}
},
"supports-color": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
@@ -1508,27 +1473,27 @@
}
},
"electron-builder-lib": {
"version": "20.2.0",
"resolved": "https://registry.npmjs.org/electron-builder-lib/-/electron-builder-lib-20.2.0.tgz",
"integrity": "sha512-bHESbb/OjO0F+tyUAj2wFXVDpuXweB5YR94/f7CKqdpd7k2LeYJvy+cYtgtVXt4CJyg5Vs4Kmak2VvDfWxbO/A==",
"version": "20.5.1",
"resolved": "https://registry.npmjs.org/electron-builder-lib/-/electron-builder-lib-20.5.1.tgz",
"integrity": "sha512-YAUu4KHZQNFPdHqnvwOHmYWmqnwiExKuB4fnETZl5jmN3ZUgxCQFqWdwIGQWoAIdxtkQxTervJMt+uJ/wJWbZA==",
"dev": true,
"requires": {
"7zip-bin": "3.1.0",
"app-builder-bin": "1.5.0",
"app-builder-bin": "1.7.2",
"async-exit-hook": "2.0.1",
"bluebird-lst": "1.0.5",
"builder-util": "5.6.0",
"builder-util": "5.6.5",
"builder-util-runtime": "4.0.5",
"chromium-pickle-js": "0.2.0",
"debug": "3.1.0",
"ejs": "2.5.7",
"electron-osx-sign": "0.4.8",
"electron-publish": "20.2.0",
"electron-osx-sign": "0.4.10",
"electron-publish": "20.5.0",
"fs-extra-p": "4.5.2",
"hosted-git-info": "2.5.0",
"hosted-git-info": "2.6.0",
"is-ci": "1.1.0",
"isbinaryfile": "3.0.2",
"js-yaml": "3.10.0",
"js-yaml": "3.11.0",
"lazy-val": "1.0.3",
"minimatch": "3.0.4",
"normalize-package-data": "2.4.0",
@@ -1539,6 +1504,38 @@
"temp-file": "3.1.1"
},
"dependencies": {
"app-builder-bin": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-1.7.2.tgz",
"integrity": "sha512-2uJICLdVnkDqizLZa4HclhBsAWiSf1sEPeKS5+GhuxGaDdWnabXZ4ed9hYQ5u81P3hW3lB+xvxDw2TTinDB9Tw==",
"dev": true,
"requires": {
"app-builder-bin-linux": "1.7.2",
"app-builder-bin-mac": "1.7.2",
"app-builder-bin-win": "1.7.2"
}
},
"app-builder-bin-linux": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin-linux/-/app-builder-bin-linux-1.7.2.tgz",
"integrity": "sha512-spoW8f6sqo5aKpoZx+scIPMonSTrh8JtKWM3MuDqBJiXiUCtpVIPez5c4AycGwQnmh167KFjK4pn129o3k+aHQ==",
"dev": true,
"optional": true
},
"app-builder-bin-mac": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin-mac/-/app-builder-bin-mac-1.7.2.tgz",
"integrity": "sha512-GLrQ9r17Hnc8dap2rKJ1N7ZukLBbTN88BSG4EC3xmNeafoWbekuxq3IdJYkZAT/eS1Ig4Q6nRcLI9TfnafwZEQ==",
"dev": true,
"optional": true
},
"app-builder-bin-win": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz",
"integrity": "sha512-/7tvJZas9T5TBM3QUV0xQkRQAyUlsXdtUsqtOg48mgp1ogPqDjs4W2Jr31YhhiUHDdNgamZc655PzWqAEnbZfQ==",
"dev": true,
"optional": true
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@@ -1548,20 +1545,10 @@
"ms": "2.0.0"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"hosted-git-info": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz",
"integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==",
"dev": true
},
"semver": {
@@ -1653,9 +1640,9 @@
"integrity": "sha1-ihBD4ys6HaHD9VPc4oznZCRhZ+M="
},
"electron-osx-sign": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.8.tgz",
"integrity": "sha1-8Ln63e2eHlTsNfqJh3tcbDTHvEA=",
"version": "0.4.10",
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz",
"integrity": "sha1-vk87ibKnWh3F8eckkIGrKSnKOiY=",
"dev": true,
"requires": {
"bluebird": "3.5.1",
@@ -1675,56 +1662,40 @@
}
},
"electron-publish": {
"version": "20.2.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-20.2.0.tgz",
"integrity": "sha512-n8MEDVSYXi8ZC8sHJMoSzTrOrV6X+6cWmyQP4M6nh0RZaLcyPa/txWuHDeRNysvhOKJvgQJrf09Fuc+CMSY6zg==",
"version": "20.5.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-20.5.0.tgz",
"integrity": "sha512-BejALjAMG0QbjJNjN66pruwhWt07Iy86VBDxHWXO9IoupYykCEyFdy20jjl5rNTpfnojvynqZyj8QpRkNjJBZg==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"builder-util": "5.6.0",
"builder-util": "5.6.5",
"builder-util-runtime": "4.0.5",
"chalk": "2.3.1",
"chalk": "2.3.2",
"fs-extra-p": "4.5.2",
"lazy-val": "1.0.3",
"mime": "2.2.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
"supports-color": "5.3.0"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"dev": true
},
"mime": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz",
@@ -1732,9 +1703,9 @@
"dev": true
},
"supports-color": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
@@ -2056,6 +2027,16 @@
"universalify": "0.1.1"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"fs-minipass": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
@@ -3329,7 +3310,7 @@
"integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==",
"dev": true,
"requires": {
"ci-info": "1.1.2"
"ci-info": "1.1.3"
}
},
"is-dotfile": {
@@ -3545,9 +3526,9 @@
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz",
"integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==",
"dev": true,
"requires": {
"argparse": "1.0.9",
@@ -3651,6 +3632,12 @@
"package-json": "4.0.1"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"dev": true
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
@@ -3748,9 +3735,9 @@
"integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY="
},
"lru-cache": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
"integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz",
"integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==",
"dev": true,
"requires": {
"pseudomap": "1.0.2",
@@ -4553,38 +4540,9 @@
"dotenv": "5.0.1",
"dotenv-expand": "4.2.0",
"fs-extra-p": "4.5.2",
"js-yaml": "3.10.0",
"js-yaml": "3.11.0",
"json5": "0.5.1",
"lazy-val": "1.0.3"
},
"dependencies": {
"ajv": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.0.tgz",
"integrity": "sha1-r6wpW7qgFSRJ5SJ0LkVHwa6TKNI=",
"dev": true,
"requires": {
"fast-deep-equal": "1.0.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"dev": true
}
}
},
"read-pkg": {
@@ -5859,24 +5817,6 @@
"bluebird-lst": "1.0.5",
"fs-extra-p": "4.5.2",
"lazy-val": "1.0.3"
},
"dependencies": {
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"dev": true,
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
"dev": true
}
}
},
"term-size": {
@@ -6032,7 +5972,7 @@
"dev": true,
"requires": {
"boxen": "1.3.0",
"chalk": "2.3.1",
"chalk": "2.3.2",
"configstore": "3.1.1",
"import-lazy": "2.1.0",
"is-installed-globally": "0.1.0",
@@ -6043,29 +5983,29 @@
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
"supports-color": "5.3.0"
}
},
"supports-color": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"dev": true,
"requires": {
"has-flag": "3.0.0"

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.73",
"version": "1.0.78",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -48,7 +48,7 @@
"babel-cli": "^6.26.0",
"babel-preset-react": "^6.24.1",
"electron": "^1.7.11",
"electron-builder": "^20.2.0"
"electron-builder": "^20.5.1"
},
"optionalDependencies": {
"7zip-bin-mac": "^1.0.1",

View File

@@ -4,7 +4,7 @@ Joplin is a free, open source note taking and to-do application, which can handl
Notes exported from Evernote via .enex files [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be [synchronised](#synchronisation) with various targets including [Nextcloud](https://nextcloud.com/), the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
The notes can be [synchronised](#synchronisation) with various cloud services including [Nextcloud](https://nextcloud.com/), the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
The UI of the terminal client is built on top of the great [terminal-kit](https://github.com/cronvel/terminal-kit) library, the desktop client using [Electron](https://electronjs.org/), and the Android client front end is done using [React Native](https://facebook.github.io/react-native/).
@@ -108,7 +108,7 @@ Currently, synchronisation is possible with Nextcloud and OneDrive (by default)
## Nextcloud synchronisation
On the **desktop application** or **mobile application**, go to the config screen and select Nextcloud as the synchronisation target. Then input [the WebDAV URL](https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html), this is normally `https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin` (make sure to create the "Joplin" directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.
On the **desktop application** or **mobile application**, go to the config screen and select Nextcloud as the synchronisation target. Then input [the WebDAV URL](https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html), this is normally `https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin` (**make sure to create the "Joplin" directory in Nextcloud** and to replace USERNAME by your Nextcloud username), and 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.
On the **terminal application**, you will need to set the `sync.target` config variable and all the `sync.5.path`, `sync.5.username` and `sync.5.password` config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:
@@ -228,18 +228,18 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 80%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png) | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 65%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Strobel <git@strobeltobias.de> | 97%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 79%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png) | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 64%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Grasse <mail@tobias-grasse.net> | 99%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png) | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png) | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 99%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png) | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 99%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png) | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 66%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png) | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 80%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos <rnbastos@gmail.com> | 98%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 99%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH <RCJacH@outlook.com> | 66%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 65%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 64%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Known bugs

View File

@@ -31,6 +31,7 @@ const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js');
const EncryptionService = require('lib/services/EncryptionService');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetOneDrive);
@@ -426,6 +427,7 @@ class BaseApplication {
setLocale(Setting.value('locale'));
}
BaseService.logger_ = this.logger_;
EncryptionService.instance().setLogger(this.logger_);
BaseItem.encryptionService_ = EncryptionService.instance();
DecryptionWorker.instance().setLogger(this.logger_);

View File

@@ -121,15 +121,6 @@ class BaseModel {
return id.substr(0, 5);
}
// static minimalPartialId(id) {
// let length = 2;
// while (true) {
// const partialId = id.substr(0, length);
// const r = await this.db().selectOne('SELECT count(*) as total FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
// if (r['total'] <= 1) return partialId;
// }
// }
static loadByPartialId(partialId) {
return this.modelSelectAll('SELECT * FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
}
@@ -221,20 +212,6 @@ class BaseModel {
}
if ('type_' in newModel) output.type_ = newModel.type_;
return output;
// let output = {};
// let type = null;
// for (let n in newModel) {
// if (!newModel.hasOwnProperty(n)) continue;
// if (n == 'type_') {
// type = newModel[n];
// continue;
// }
// if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
// output[n] = newModel[n];
// }
// }
// if (type !== null) output.type_ = type;
// return output;
}
static diffObjectsFields(oldModel, newModel) {
@@ -421,11 +398,10 @@ class BaseModel {
}
output = this.filter(o);
} catch (error) {
this.logger().error('Cannot save model', error);
} finally {
this.releaseSaveMutex(o, mutexRelease);
}
this.releaseSaveMutex(o, mutexRelease);
return output;
}
@@ -504,6 +480,8 @@ BaseModel.typeEnum_ = [
['TYPE_SEARCH', 7],
['TYPE_ALARM', 8],
['TYPE_MASTER_KEY', 9],
['TYPE_ITEM_CHANGE', 10],
['TYPE_NOTE_RESOURCE', 11],
];
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
@@ -511,16 +489,6 @@ for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
BaseModel[e[0]] = e[1];
}
// BaseModel.TYPE_NOTE = 1;
// BaseModel.TYPE_FOLDER = 2;
// BaseModel.TYPE_SETTING = 3;
// BaseModel.TYPE_RESOURCE = 4;
// BaseModel.TYPE_TAG = 5;
// BaseModel.TYPE_NOTE_TAG = 6;
// BaseModel.TYPE_SEARCH = 7;
// BaseModel.TYPE_ALARM = 8;
// BaseModel.TYPE_MASTER_KEY = 9;
BaseModel.db_ = null;
BaseModel.dispatch = function(o) {};
BaseModel.saveMutexes_ = {};

View File

@@ -46,6 +46,13 @@ globalStyle.lineInput = {
backgroundColor: globalStyle.backgroundColor,
};
globalStyle.buttonRow = {
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: globalStyle.dividerColor,
paddingTop: 10,
};
let themeCache_ = {};
function themeStyle(theme) {

View File

@@ -128,6 +128,8 @@ class ScreenHeaderComponent extends Component {
color: theme.raisedHighlightedColor,
fontWeight: 'bold',
fontSize: theme.fontSize,
paddingTop: 15,
paddingBottom: 15,
},
warningBox: {
backgroundColor: "#ff9900",
@@ -428,15 +430,19 @@ class ScreenHeaderComponent extends Component {
</TouchableOpacity>
) : null;
const showSideMenuButton = this.props.showSideMenuButton !== false && !this.props.noteSelectionEnabled;
const showSearchButton = this.props.showSearchButton !== false && !this.props.noteSelectionEnabled;
const showContextMenuButton = this.props.showContextMenuButton !== false;
const titleComp = createTitleComponent();
const sideMenuComp = this.props.noteSelectionEnabled ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
const backButtonComp = backButton(this.styles(), () => this.backButton_press(), !this.props.historyCanGoBack);
const searchButtonComp = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press());
const searchButtonComp = !showSearchButton ? null : searchButton(this.styles(), () => this.searchButton_press());
const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null;
const sortButtonComp = this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
const windowHeight = Dimensions.get('window').height - 50;
const menuComp = (
const menuComp = !showContextMenuButton ? null : (
<Menu onSelect={(value) => this.menu_select(value)} style={this.styles().contextMenu}>
<MenuTrigger style={{ paddingTop: PADDING_V, paddingBottom: PADDING_V }}>
<Text style={this.styles().contextMenuTrigger}> &#8942;</Text>

View File

@@ -0,0 +1,201 @@
const React = require('react'); const Component = React.Component;
const { ListView, StyleSheet, View, Text, Button, FlatList, TouchableOpacity, TextInput } = require('react-native');
const Setting = require('lib/models/Setting.js');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { time } = require('lib/time-utils');
const { Logger } = require('lib/logger.js');
const BaseItem = require('lib/models/BaseItem.js');
const Tag = require('lib/models/Tag.js');
const { Database } = require('lib/database.js');
const Folder = require('lib/models/Folder.js');
const { ReportService } = require('lib/services/report.js');
const { _ } = require('lib/locale.js');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { globalStyle, themeStyle } = require('lib/components/global-style.js');
const Icon = require('react-native-vector-icons/Ionicons').default;
const styles = StyleSheet.create({
body: {
flex: 1,
margin: globalStyle.margin,
},
});
class NoteTagsScreenComponent extends BaseScreenComponent {
constructor() {
super();
this.styles_ = {};
this.state = {
noteTagIds: [],
noteId: null,
tagListData: [],
newTags: '',
savingTags: false,
};
const noteHasTag = (tagId) => {
for (let i = 0; i < this.state.tagListData.length; i++) {
if (this.state.tagListData[i].id === tagId) return this.state.tagListData[i].selected;
}
return false;
}
const newTagTitles = () => {
return this.state.newTags
.split(',')
.map(t => t.trim().toLowerCase())
.filter(t => !!t);
}
this.tag_press = (tagId) => {
const newData = this.state.tagListData.slice();
for (let i = 0; i < newData.length; i++) {
const t = newData[i];
if (t.id === tagId) {
const newTag = Object.assign({}, t);
newTag.selected = !newTag.selected;
newData[i] = newTag;
break;
}
}
this.setState({ tagListData: newData });
}
this.renderTag = (data) => {
const tag = data.item;
const iconName = noteHasTag(tag.id) ? 'md-checkbox-outline' : 'md-square-outline';
return (
<TouchableOpacity key={tag.id} onPress={() => this.tag_press(tag.id)} style={this.styles().tag}>
<View style={this.styles().tagIconText}>
<Icon name={iconName} style={this.styles().tagCheckbox}/><Text>{tag.title}</Text>
</View>
</TouchableOpacity>
);
}
this.tagKeyExtractor = (tag, index) => tag.id;
this.okButton_press = async () => {
this.setState({ savingTags: true });
try {
const tagIds = this.state.tagListData.filter(t => t.selected).map(t => t.id);
await Tag.setNoteTagsByIds(this.state.noteId, tagIds);
const extraTitles = newTagTitles();
for (let i = 0; i < extraTitles.length; i++) {
await Tag.addNoteTagByTitle(this.state.noteId, extraTitles[i]);
}
} finally {
this.setState({ savingTags: false });
}
this.props.dispatch({
type: 'NAV_BACK',
});
}
this.cancelButton_press = () => {
this.props.dispatch({
type: 'NAV_BACK',
});
}
}
componentWillMount() {
const noteId = this.props.noteId;
this.setState({ noteId: noteId });
this.loadNoteTags(noteId);
}
async loadNoteTags(noteId) {
const tags = await Tag.tagsByNoteId(noteId);
const tagIds = tags.map(t => t.id);
const tagListData = this.props.tags.map(tag => { return {
id: tag.id,
title: tag.title,
selected: tagIds.indexOf(tag.id) >= 0,
}});
this.setState({ tagListData: tagListData });
}
styles() {
const themeId = this.props.theme;
const theme = themeStyle(themeId);
if (this.styles_[themeId]) return this.styles_[themeId];
this.styles_ = {};
let styles = {
tag: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: theme.dividerColor,
},
tagIconText: {
flexDirection: 'row',
alignItems: 'center',
},
tagCheckbox: {
marginRight: 5,
fontSize: 20,
},
newTagBox: {
flexDirection:'row',
alignItems: 'center',
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
borderTopWidth: 1,
borderTopColor: theme.dividerColor
},
};
this.styles_[themeId] = StyleSheet.create(styles);
return this.styles_[themeId];
}
render() {
const theme = themeStyle(this.props.theme);
return (
<View style={this.rootStyle(this.props.theme).root}>
<ScreenHeader title={_('Note tags')} showSideMenuButton={false} showSearchButton={false} showContextMenuButton={false}/>
<FlatList
data={this.state.tagListData}
renderItem={this.renderTag}
keyExtractor={this.tagKeyExtractor}
/>
<View style={this.styles().newTagBox}>
<Text>{_('Or type tags:')}</Text><TextInput value={this.state.newTags} onChangeText={value => { this.setState({ newTags: value }) }} style={{flex:1}}/>
</View>
<View style={theme.buttonRow}>
<View style={{flex:1}}>
<Button disabled={this.state.savingTags} title={_('OK')} onPress={this.okButton_press}></Button>
</View>
<View style={{flex:1, marginLeft: 5}}>
<Button disabled={this.state.savingTags} title={_('Cancel')} onPress={this.cancelButton_press}></Button>
</View>
</View>
</View>
);
}
}
const NoteTagsScreen = connect(
(state) => {
return {
theme: state.settings.theme,
tags: state.tags,
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
};
}
)(NoteTagsScreenComponent)
module.exports = { NoteTagsScreen };

View File

@@ -357,6 +357,16 @@ class NoteScreenComponent extends BaseScreenComponent {
shared.toggleIsTodo_onPress(this);
}
tags_onPress() {
if (!this.state.note || !this.state.note.id) return;
this.props.dispatch({
type: 'NAV_GO',
routeName: 'NoteTags',
noteId: this.state.note.id,
});
}
setAlarm_onPress() {
this.setState({ alarmDialogShown: true });
}
@@ -393,6 +403,7 @@ class NoteScreenComponent extends BaseScreenComponent {
menuOptions() {
const note = this.state.note;
const isTodo = note && !!note.is_todo;
const isSaved = note && note.id;
let output = [];
@@ -410,6 +421,7 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({ title: _('Set alarm'), onPress: () => { this.setState({ alarmDialogShown: true }) }});;
}
if (isSaved) output.push({ title: _('Tags'), onPress: () => { this.tags_onPress(); } });
output.push({ title: isTodo ? _('Convert to note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } });
output.push({ isDivider: true });
if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } });

View File

@@ -3,16 +3,16 @@ const { promiseChain } = require('lib/promise-utils.js');
const { Logger } = require('lib/logger.js');
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const Mutex = require('async-mutex').Mutex;
class Database {
constructor(driver) {
this.debugMode_ = false;
this.driver_ = driver;
this.inTransaction_ = false;
this.logger_ = new Logger();
this.logExcludedQueryTypes_ = [];
this.batchTransactionMutex_ = new Mutex();
}
setLogExcludedQueryTypes(v) {
@@ -113,92 +113,24 @@ class Database {
return;
}
// There can be only one transaction running at a time so queue
// any new transaction here.
if (this.inTransaction_) {
while (true) {
await time.msleep(100);
if (!this.inTransaction_) {
this.inTransaction_ = true;
break;
}
// There can be only one transaction running at a time so use a mutex
const release = await this.batchTransactionMutex_.acquire();
try {
await this.exec('BEGIN TRANSACTION');
for (let i = 0; i < queries.length; i++) {
let query = this.wrapQuery(queries[i]);
await this.exec(query.sql, query.params);
}
// return new Promise((resolve, reject) => {
// let iid = setInterval(() => {
// if (!this.inTransaction_) {
// clearInterval(iid);
// this.transactionExecBatch(queries).then(() => {
// resolve();
// }).catch((error) => {
// reject(error);
// });
// }
// }, 100);
// });
await this.exec('COMMIT');
} catch (error) {
await this.exec('ROLLBACK');
throw error;
} finally {
release();
}
this.inTransaction_ = true;
queries.splice(0, 0, 'BEGIN TRANSACTION');
queries.push('COMMIT'); // Note: ROLLBACK is currently not supported
for (let i = 0; i < queries.length; i++) {
let query = this.wrapQuery(queries[i]);
await this.exec(query.sql, query.params);
}
this.inTransaction_ = false;
// return promiseChain(chain).then(() => {
// this.inTransaction_ = false;
// });
// if (queries.length <= 0) return Promise.resolve();
// if (queries.length == 1) {
// let q = this.wrapQuery(queries[0]);
// return this.exec(q.sql, q.params);
// }
// // There can be only one transaction running at a time so queue
// // any new transaction here.
// if (this.inTransaction_) {
// return new Promise((resolve, reject) => {
// let iid = setInterval(() => {
// if (!this.inTransaction_) {
// clearInterval(iid);
// this.transactionExecBatch(queries).then(() => {
// resolve();
// }).catch((error) => {
// reject(error);
// });
// }
// }, 100);
// });
// }
// this.inTransaction_ = true;
// queries.splice(0, 0, 'BEGIN TRANSACTION');
// queries.push('COMMIT'); // Note: ROLLBACK is currently not supported
// let chain = [];
// for (let i = 0; i < queries.length; i++) {
// let query = this.wrapQuery(queries[i]);
// chain.push(() => {
// return this.exec(query.sql, query.params);
// });
// }
// return promiseChain(chain).then(() => {
// this.inTransaction_ = false;
// });
}
static enumId(type, s) {

View File

@@ -243,7 +243,9 @@ async function basicDelta(path, getDirStatFn, options) {
newContext.statsCache.sort(function(a, b) {
return a.updated_time - b.updated_time;
});
newContext.statIdsCache = newContext.statsCache.map((item) => BaseItem.pathToId(item.path));
newContext.statIdsCache = newContext.statsCache
.filter(item => BaseItem.isSystemPath(item.path))
.map(item => BaseItem.pathToId(item.path));
newContext.statIdsCache.sort(); // Items must be sorted to use binary search below
}

View File

@@ -202,7 +202,7 @@ class JoplinDatabase extends Database {
// default value and thus might cause problems. In that case, the default value
// must be set in the synchronizer too.
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
@@ -298,6 +298,52 @@ class JoplinDatabase extends Database {
queries.push('ALTER TABLE resources ADD COLUMN encryption_blob_encrypted INT NOT NULL DEFAULT 0');
}
const upgradeVersion10 = () => {
const itemChangesTable = `
CREATE TABLE item_changes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_type INT NOT NULL,
item_id TEXT NOT NULL,
type INT NOT NULL,
created_time INT NOT NULL
);
`;
const noteResourcesTable = `
CREATE TABLE note_resources (
id INTEGER PRIMARY KEY,
note_id TEXT NOT NULL,
resource_id TEXT NOT NULL,
is_associated INT NOT NULL,
last_seen_time INT NOT NULL
);
`;
queries.push(this.sqlStringToLines(itemChangesTable)[0]);
queries.push('CREATE INDEX item_changes_item_id ON item_changes (item_id)');
queries.push('CREATE INDEX item_changes_created_time ON item_changes (created_time)');
queries.push('CREATE INDEX item_changes_item_type ON item_changes (item_type)');
queries.push(this.sqlStringToLines(noteResourcesTable)[0]);
queries.push('CREATE INDEX note_resources_note_id ON note_resources (note_id)');
queries.push('CREATE INDEX note_resources_resource_id ON note_resources (resource_id)');
queries.push({ sql: 'INSERT INTO item_changes (item_type, item_id, type, created_time) SELECT 1, id, 1, ? FROM notes', params: [Date.now()] });
}
if (targetVersion == 10) {
upgradeVersion10();
}
if (targetVersion == 11) {
// This trick was needed because Electron Builder incorrectly released a dev branch containing v10 as it was
// still being developed, and the db schema was not final at that time. So this v11 was created to
// make sure any invalid db schema that was accidentally created was deleted and recreated.
queries.push('DROP TABLE item_changes');
queries.push('DROP TABLE note_resources');
upgradeVersion10();
}
queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] });
await this.transactionExecBatch(queries);

View File

@@ -0,0 +1,54 @@
const BaseModel = require('lib/BaseModel.js');
const Mutex = require('async-mutex').Mutex;
class ItemChange extends BaseModel {
static tableName() {
return 'item_changes';
}
static modelType() {
return BaseModel.TYPE_ITEM_CHANGE;
}
static async add(itemType, itemId, type) {
ItemChange.saveCalls_.push(true);
// Using a mutex so that records can be added to the database in the
// background, without making the UI wait.
const release = await ItemChange.addChangeMutex_.acquire();
try {
await this.db().transactionExecBatch([
{ sql: 'DELETE FROM item_changes WHERE item_id = ?', params: [itemId] },
{ sql: 'INSERT INTO item_changes (item_type, item_id, type, created_time) VALUES (?, ?, ?, ?)', params: [itemType, itemId, type, Date.now()] },
]);
} finally {
release();
ItemChange.saveCalls_.pop();
}
}
// Because item changes are recorded in the background, this function
// can be used for synchronous code, in particular when unit testing.
static async waitForAllSaved() {
return new Promise((resolve, reject) => {
const iid = setInterval(() => {
if (!ItemChange.saveCalls_.length) {
clearInterval(iid);
resolve();
}
}, 100);
});
}
}
ItemChange.addChangeMutex_ = new Mutex();
ItemChange.saveCalls_ = [];
ItemChange.TYPE_CREATE = 1;
ItemChange.TYPE_UPDATE = 2;
ItemChange.TYPE_DELETE = 3;
module.exports = ItemChange;

View File

@@ -1,6 +1,7 @@
const BaseModel = require('lib/BaseModel.js');
const { sprintf } = require('sprintf-js');
const BaseItem = require('lib/models/BaseItem.js');
const ItemChange = require('lib/models/ItemChange.js');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim.js');
const { time } = require('lib/time-utils.js');
@@ -407,6 +408,8 @@ class Note extends BaseItem {
const note = await super.save(o, options);
ItemChange.add(BaseModel.TYPE_NOTE, note.id, isNew ? ItemChange.TYPE_CREATE : ItemChange.TYPE_UPDATE);
this.dispatch({
type: 'NOTE_UPDATE_ONE',
note: note,
@@ -422,18 +425,22 @@ class Note extends BaseItem {
return note;
}
static async delete(id, options = null) {
let r = await super.delete(id, options);
// Not used?
this.dispatch({
type: 'NOTE_DELETE',
id: id,
});
}
// static async delete(id, options = null) {
// let r = await super.delete(id, options);
static batchDelete(ids, options = null) {
const result = super.batchDelete(ids, options);
// this.dispatch({
// type: 'NOTE_DELETE',
// id: id,
// });
// }
static async batchDelete(ids, options = null) {
const result = await super.batchDelete(ids, options);
for (let i = 0; i < ids.length; i++) {
ItemChange.add(BaseModel.TYPE_NOTE, ids[i], ItemChange.TYPE_DELETE);
this.dispatch({
type: 'NOTE_DELETE',
id: ids[i],

View File

@@ -0,0 +1,70 @@
const BaseModel = require('lib/BaseModel.js');
class NoteResource extends BaseModel {
static tableName() {
return 'note_resources';
}
static modelType() {
return BaseModel.TYPE_NOTE_RESOURCE;
}
static async setAssociatedResources(noteId, resourceIds) {
const existingRows = await this.modelSelectAll('SELECT * FROM note_resources WHERE note_id = ?', [noteId]);
const notProcessedResourceIds = resourceIds.slice();
const queries = [];
for (let i = 0; i < existingRows.length; i++) {
const row = existingRows[i];
const resourceIndex = resourceIds.indexOf(row.resource_id);
if (resourceIndex >= 0) {
queries.push({ sql: 'UPDATE note_resources SET last_seen_time = ?, is_associated = 1 WHERE id = ?', params: [Date.now(), row.id] });
notProcessedResourceIds.splice(notProcessedResourceIds.indexOf(row.resource_id), 1);
} else {
queries.push({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE id = ?', params: [row.id] });
}
}
for (let i = 0; i < notProcessedResourceIds.length; i++) {
queries.push({ sql: 'INSERT INTO note_resources (note_id, resource_id, is_associated, last_seen_time) VALUES (?, ?, ?, ?)', params: [noteId, notProcessedResourceIds[i], 1, Date.now()] });
}
await this.db().transactionExecBatch(queries);
}
static async addOrphanedResources() {
const missingResources = await this.db().selectAll('SELECT id FROM resources WHERE id NOT IN (SELECT DISTINCT resource_id FROM note_resources)');
const queries = [];
for (let i = 0; i < missingResources.length; i++) {
const id = missingResources[i].id;
queries.push({ sql: 'INSERT INTO note_resources (note_id, resource_id, is_associated, last_seen_time) VALUES (?, ?, ?, ?)', params: ["", id, 0, Date.now()] });
}
await this.db().transactionExecBatch(queries);
}
static async remove(noteId) {
await this.db().exec({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE note_id = ?', params: [noteId] });
}
static async orphanResources(expiryDelay = null) {
if (expiryDelay === null) expiryDelay = 1000 * 60 * 60 * 24;
const cutOffTime = Date.now() - expiryDelay;
const output = await this.modelSelectAll(`
SELECT resource_id, sum(is_associated)
FROM note_resources
GROUP BY resource_id
HAVING sum(is_associated) <= 0
AND last_seen_time < ?
`, [cutOffTime]);
return output.map(r => r.resource_id);
}
static async deleteByResource(resourceId) {
await this.db().exec('DELETE FROM note_resources WHERE resource_id = ?', [resourceId]);
}
}
module.exports = NoteResource;

View File

@@ -1,5 +1,6 @@
const BaseModel = require('lib/BaseModel.js');
const BaseItem = require('lib/models/BaseItem.js');
const NoteResource = require('lib/models/NoteResource.js');
const Setting = require('lib/models/Setting.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const pathUtils = require('lib/path-utils.js');
@@ -143,6 +144,20 @@ class Resource extends BaseItem {
return url.substr(2);
}
static async batchDelete(ids, options = null) {
// For resources, there's not really batch deleting since there's the file data to delete
// too, so each is processed one by one with the item being deleted last (since the db
// call is the less likely to fail).
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
const resource = await Resource.load(id);
const path = Resource.fullPath(resource);
await this.fsDriver().remove(path);
await super.batchDelete([id], options);
await NoteResource.deleteByResource(id); // Clean up note/resource relationships
}
}
}
Resource.IMAGE_MAX_DIMENSION = 1920;

View File

@@ -106,6 +106,12 @@ class Tag extends BaseItem {
return this.loadByField('title', title, { caseInsensitive: true });
}
static async addNoteTagByTitle(noteId, tagTitle) {
let tag = await this.loadByTitle(tagTitle);
if (!tag) tag = await Tag.save({ title: tagTitle }, { userSideValidation: true });
return await this.addNote(tag.id, noteId);
}
static async setNoteTagsByTitles(noteId, tagTitles) {
const previousTags = await this.tagsByNoteId(noteId);
const addedTitles = [];
@@ -126,6 +132,23 @@ class Tag extends BaseItem {
}
}
static async setNoteTagsByIds(noteId, tagIds) {
const previousTags = await this.tagsByNoteId(noteId);
const addedIds = [];
for (let i = 0; i < tagIds.length; i++) {
const tagId = tagIds[i];
await this.addNote(tagId, noteId);
addedIds.push(tagId);
}
for (let i = 0; i < previousTags.length; i++) {
if (addedIds.indexOf(previousTags[i].id) < 0) {
await this.removeNote(previousTags[i].id, noteId);
}
}
}
static async save(o, options = null) {
if (options && options.userSideValidation) {
if ('title' in o) {

View File

@@ -59,10 +59,10 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
reg.logger().info('Scheduling sync operation...');
// if (Setting.value("env") === "dev" && delay !== 0) {
// reg.logger().info("Schedule sync DISABLED!!!");
// return;
// }
if (Setting.value("env") === "dev" && delay !== 0) {
reg.logger().info("Schedule sync DISABLED!!!");
return;
}
const timeoutCallback = async () => {
reg.scheduleSyncId_ = null;

View File

@@ -0,0 +1,12 @@
class BaseService {
logger() {
if (!BaseService.logger_) throw new Error('BaseService.logger_ not set!!');
return BaseService.logger_;
}
}
BaseService.logger_ = null;
module.exports = BaseService;

View File

@@ -0,0 +1,100 @@
const ItemChange = require('lib/models/ItemChange');
const NoteResource = require('lib/models/NoteResource');
const Note = require('lib/models/Note');
const Resource = require('lib/models/Resource');
const BaseModel = require('lib/BaseModel');
const BaseService = require('lib/services/BaseService');
const { shim } = require('lib/shim');
class ResourceService extends BaseService {
async indexNoteResources() {
this.logger().info('ResourceService::indexNoteResources: Start');
let lastId = 0;
const processedChangeIds = [];
await ItemChange.waitForAllSaved();
while (true) {
const changes = await ItemChange.modelSelectAll(`
SELECT id, item_id, type
FROM item_changes
WHERE item_type = ?
AND id > ?
ORDER BY id ASC
LIMIT 100
`, [BaseModel.TYPE_NOTE, lastId]);
if (!changes.length) break;
const noteIds = changes.map(a => a.item_id);
const notes = await Note.modelSelectAll('SELECT id, title, body FROM notes WHERE id IN ("' + noteIds.join('","') + '")');
const noteById = (noteId) => {
for (let i = 0; i < notes.length; i++) {
if (notes[i].id === noteId) return notes[i];
}
throw new Error('Invalid note ID: ' + noteId);
}
for (let i = 0; i < changes.length; i++) {
const change = changes[i];
if (change.type === ItemChange.TYPE_CREATE || change.type === ItemChange.TYPE_UPDATE) {
const note = noteById(change.item_id);
const resourceIds = Note.linkedResourceIds(note.body);
await NoteResource.setAssociatedResources(note.id, resourceIds);
} else if (change.type === ItemChange.TYPE_DELETE) {
await NoteResource.remove(change.item_id);
} else {
throw new Error('Invalid change type: ' + change.type);
}
lastId = change.id;
processedChangeIds.push(change.id);
}
}
if (lastId) {
await ItemChange.db().exec('DELETE FROM item_changes WHERE id <= ?', [lastId]);
}
await NoteResource.addOrphanedResources();
this.logger().info('ResourceService::indexNoteResources: Completed');
}
async deleteOrphanResources(expiryDelay = null) {
const resourceIds = await NoteResource.orphanResources(expiryDelay);
this.logger().info('ResourceService::deleteOrphanResources:', resourceIds);
for (let i = 0; i < resourceIds.length; i++) {
await Resource.delete(resourceIds[i]);
}
}
async maintenance() {
await this.indexNoteResources();
await this.deleteOrphanResources();
}
static runInBackground() {
if (this.isRunningInBackground_) return;
this.isRunningInBackground_ = true;
const service = new ResourceService();
setTimeout(() => {
service.maintenance();
}, 1000 * 30);
shim.setInterval(() => {
service.maintenance();
}, 1000 * 60 * 60 * 4);
}
}
module.exports = ResourceService;

View File

@@ -72,7 +72,7 @@ class Synchronizer {
if (report.deleteLocal) lines.push(_("Deleted local items: %d.", report.deleteLocal));
if (report.deleteRemote) lines.push(_("Deleted remote items: %d.", report.deleteRemote));
if (report.fetchingTotal && report.fetchingProcessed) lines.push(_("Fetched items: %d/%d.", report.fetchingProcessed, report.fetchingTotal));
if (!report.completedTime && report.state) lines.push(_('State: "%s".', Synchronizer.stateToLabel(report.state)));
if (!report.completedTime && report.state) lines.push(_('State: %s.', Synchronizer.stateToLabel(report.state)));
if (report.cancelling && !report.completedTime) lines.push(_("Cancelling..."));
if (report.completedTime) lines.push(_("Completed: %s", time.unixMsToLocalDateTime(report.completedTime)));
if (report.errors && report.errors.length) lines.push(_("Last error: %s", report.errors[report.errors.length - 1].toString().substr(0, 500)));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -22,6 +22,8 @@ const NoteTag = require('lib/models/NoteTag.js');
const BaseItem = require('lib/models/BaseItem.js');
const MasterKey = require('lib/models/MasterKey.js');
const BaseModel = require('lib/BaseModel.js');
const BaseService = require('lib/services/BaseService.js');
const ResourceService = require('lib/services/ResourceService');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { Database } = require('lib/database.js');
const { NotesScreen } = require('lib/components/screens/notes.js');
@@ -30,6 +32,7 @@ const { ConfigScreen } = require('lib/components/screens/config.js');
const { FolderScreen } = require('lib/components/screens/folder.js');
const { LogScreen } = require('lib/components/screens/log.js');
const { StatusScreen } = require('lib/components/screens/status.js');
const { NoteTagsScreen } = require('lib/components/screens/note-tags.js');
const { WelcomeScreen } = require('lib/components/screens/welcome.js');
const { SearchScreen } = require('lib/components/screens/search.js');
const { OneDriveLoginScreen } = require('lib/components/screens/onedrive-login.js');
@@ -125,9 +128,9 @@ const generalMiddleware = store => next => async (action) => {
let navHistory = [];
function historyCanGoBackTo(route) {
if (route.routeName == 'Note') return false;
if (route.routeName == 'Folder') return false;
function historyCanGoBackTo(route, nextRoute) {
if (route.routeName === 'Note' && nextRoute.routeName !== 'NoteTags') return false;
if (route.routeName === 'Folder') return false;
return true;
}
@@ -170,7 +173,7 @@ const appReducer = (state = appDefaultState, action) => {
const currentRoute = state.route;
const currentRouteName = currentRoute ? currentRoute.routeName : '';
if (!historyGoingBack && historyCanGoBackTo(currentRoute)) {
if (!historyGoingBack && historyCanGoBackTo(currentRoute, action)) {
// If the route *name* is the same (even if the other parameters are different), we
// overwrite the last route in the history with the current one. If the route name
// is different, we push a new history entry.
@@ -318,6 +321,8 @@ async function initialize(dispatch) {
reg.setLogger(mainLogger);
reg.setShowErrorMessageBoxHandler((message) => { alert(message) });
BaseService.logger_ = mainLogger;
reg.logger().info('====================================');
reg.logger().info('Starting application ' + Setting.value('appId') + ' (' + Setting.value('env') + ')');
@@ -450,6 +455,8 @@ async function initialize(dispatch) {
AlarmService.garbageCollect();
}, 1000 * 60 * 60);
ResourceService.runInBackground();
reg.scheduleSync().then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
@@ -558,6 +565,7 @@ class AppComponent extends React.Component {
Status: { screen: StatusScreen },
Search: { screen: SearchScreen },
Config: { screen: ConfigScreen },
NoteTags: { screen: NoteTagsScreen },
};
return (

View File

@@ -205,7 +205,7 @@
<div class="content">
<p>Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. 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 <a href="#markdown">Markdown format</a>.</p>
<p>Notes exported from Evernote via .enex files <a href="#importing">can be imported</a> into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.</p>
<p>The notes can be <a href="#synchronisation">synchronised</a> with various targets including <a href="https://nextcloud.com/">Nextcloud</a>, the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.</p>
<p>The notes can be <a href="#synchronisation">synchronised</a> with various cloud services including <a href="https://nextcloud.com/">Nextcloud</a>, the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.</p>
<p>The UI of the terminal client is built on top of the great <a href="https://github.com/cronvel/terminal-kit">terminal-kit</a> library, the desktop client using <a href="https://electronjs.org/">Electron</a>, and the Android client front end is done using <a href="https://facebook.github.io/react-native/">React Native</a>.</p>
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
@@ -311,7 +311,7 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<p>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. Most of the synchronisation process is done at an abstract level and access to external services, such as Nextcloud or OneDrive, is done via lightweight drivers. It is easy to support new services by creating simple drivers that provide a filesystem-like interface, i.e. the ability to read, write, delete and list items. It is also simple to switch from one service to another or to even sync to multiple services at once. Each note, notebook, tags, as well as the relation between items is transmitted as plain text files during synchronisation, which means the data can also be moved to a different application, can be easily backed up, inspected, etc.</p>
<p>Currently, synchronisation is possible with Nextcloud and OneDrive (by default) or the local filesystem. A Dropbox one will also be available once <a href="https://github.com/facebook/react-native/issues/14445">this React Native bug</a> is fixed. To setup synchronisation please follow the instructions below. After that, the application will synchronise in the background whenever it is running, or you can click on &quot;Synchronise&quot; to start a synchronisation manually.</p>
<h2 id="nextcloud-synchronisation">Nextcloud synchronisation</h2>
<p>On the <strong>desktop application</strong> or <strong>mobile application</strong>, go to the config screen and select Nextcloud as the synchronisation target. Then input <a href="https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html">the WebDAV URL</a>, this is normally <code>https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin</code> (make sure to create the &quot;Joplin&quot; directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.</p>
<p>On the <strong>desktop application</strong> or <strong>mobile application</strong>, go to the config screen and select Nextcloud as the synchronisation target. Then input <a href="https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html">the WebDAV URL</a>, this is normally <code>https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin</code> (<strong>make sure to create the &quot;Joplin&quot; directory in Nextcloud</strong> and to replace USERNAME by your Nextcloud username), and set the username and password. If it does not work, please <a href="https://github.com/laurent22/joplin/issues/61#issuecomment-373282608">see this explanation</a> for more details.</p>
<p>On the <strong>terminal application</strong>, you will need to set the <code>sync.target</code> config variable and all the <code>sync.5.path</code>, <code>sync.5.username</code> and <code>sync.5.password</code> config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:</p>
<pre><code>:config sync.5.path https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin
:config sync.5.username YOUR_USERNAME
@@ -403,21 +403,21 @@ $$
<td>Basque</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po">eu</a></td>
<td>juan.abasolo@ehu.eus</td>
<td>80%</td>
<td>79%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png" alt=""></td>
<td>Croatian</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po">hr_HR</a></td>
<td>Hrvoje Mandić <a href="&#x6d;&#97;&#105;&#108;&#116;&#x6f;&#x3a;&#x74;&#x72;&#x62;&#117;&#x68;&#x6f;&#109;&#64;&#x6e;&#x65;&#116;&#x2e;&#104;&#114;">&#x74;&#x72;&#x62;&#117;&#x68;&#x6f;&#109;&#64;&#x6e;&#x65;&#116;&#x2e;&#104;&#114;</a></td>
<td>65%</td>
<td>Hrvoje Mandić <a href="&#x6d;&#x61;&#x69;&#108;&#116;&#111;&#x3a;&#116;&#114;&#x62;&#117;&#104;&#x6f;&#109;&#x40;&#x6e;&#101;&#116;&#46;&#x68;&#x72;">&#116;&#114;&#x62;&#117;&#104;&#x6f;&#109;&#x40;&#x6e;&#101;&#116;&#46;&#x68;&#x72;</a></td>
<td>64%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png" alt=""></td>
<td>Deutsch</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po">de_DE</a></td>
<td>Tobias Strobel <a href="&#109;&#x61;&#105;&#108;&#116;&#111;&#x3a;&#x67;&#x69;&#x74;&#64;&#115;&#116;&#114;&#111;&#x62;&#x65;&#x6c;&#x74;&#111;&#x62;&#x69;&#97;&#115;&#x2e;&#x64;&#x65;">&#x67;&#x69;&#x74;&#64;&#115;&#116;&#114;&#111;&#x62;&#x65;&#x6c;&#x74;&#111;&#x62;&#x69;&#97;&#115;&#x2e;&#x64;&#x65;</a></td>
<td>97%</td>
<td>Tobias Grasse <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;&#x3a;&#109;&#97;&#x69;&#x6c;&#x40;&#116;&#111;&#x62;&#x69;&#97;&#x73;&#45;&#103;&#x72;&#97;&#x73;&#x73;&#101;&#x2e;&#x6e;&#x65;&#116;">&#109;&#97;&#x69;&#x6c;&#x40;&#116;&#111;&#x62;&#x69;&#97;&#x73;&#45;&#103;&#x72;&#97;&#x73;&#x73;&#101;&#x2e;&#x6e;&#x65;&#116;</a></td>
<td>99%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png" alt=""></td>
@@ -430,15 +430,15 @@ $$
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png" alt=""></td>
<td>Español</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po">es_ES</a></td>
<td>Fernando Martín <a href="&#x6d;&#x61;&#105;&#108;&#116;&#111;&#58;&#102;&#x40;&#x6d;&#x72;&#116;&#x6e;&#x2e;&#x65;&#115;">&#102;&#x40;&#x6d;&#x72;&#116;&#x6e;&#x2e;&#x65;&#115;</a></td>
<td>100%</td>
<td>Fernando Martín <a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#x3a;&#x66;&#x40;&#x6d;&#114;&#116;&#x6e;&#x2e;&#x65;&#115;">&#x66;&#x40;&#x6d;&#114;&#116;&#x6e;&#x2e;&#x65;&#115;</a></td>
<td>99%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png" alt=""></td>
<td>Français</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po">fr_FR</a></td>
<td>Laurent Cozic</td>
<td>100%</td>
<td>99%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png" alt=""></td>
@@ -458,21 +458,21 @@ $$
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png" alt=""></td>
<td>Português (Brasil)</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po">pt_BR</a></td>
<td>Renato Nunes Bastos <a href="&#109;&#x61;&#x69;&#x6c;&#116;&#111;&#x3a;&#x72;&#110;&#98;&#x61;&#115;&#x74;&#111;&#115;&#x40;&#103;&#x6d;&#x61;&#x69;&#108;&#46;&#x63;&#x6f;&#x6d;">&#x72;&#110;&#98;&#x61;&#115;&#x74;&#111;&#115;&#x40;&#103;&#x6d;&#x61;&#x69;&#108;&#46;&#x63;&#x6f;&#x6d;</a></td>
<td>Renato Nunes Bastos <a href="&#x6d;&#97;&#105;&#108;&#x74;&#111;&#58;&#x72;&#110;&#x62;&#97;&#115;&#x74;&#x6f;&#115;&#64;&#x67;&#x6d;&#x61;&#105;&#108;&#46;&#99;&#x6f;&#109;">&#x72;&#110;&#x62;&#97;&#115;&#x74;&#x6f;&#115;&#64;&#x67;&#x6d;&#x61;&#105;&#108;&#46;&#99;&#x6f;&#109;</a></td>
<td>98%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png" alt=""></td>
<td>Русский</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po">ru_RU</a></td>
<td>Artyom Karlov <a href="&#x6d;&#x61;&#105;&#108;&#x74;&#111;&#58;&#97;&#114;&#x74;&#x79;&#111;&#109;&#46;&#107;&#x61;&#x72;&#x6c;&#111;&#118;&#64;&#103;&#109;&#97;&#105;&#108;&#x2e;&#x63;&#111;&#x6d;">&#97;&#114;&#x74;&#x79;&#111;&#109;&#46;&#107;&#x61;&#x72;&#x6c;&#111;&#118;&#64;&#103;&#109;&#97;&#105;&#108;&#x2e;&#x63;&#111;&#x6d;</a></td>
<td>100%</td>
<td>Artyom Karlov <a href="&#109;&#97;&#105;&#x6c;&#116;&#111;&#58;&#x61;&#114;&#x74;&#121;&#x6f;&#109;&#x2e;&#x6b;&#x61;&#x72;&#108;&#111;&#118;&#x40;&#103;&#109;&#x61;&#105;&#108;&#46;&#99;&#x6f;&#109;">&#x61;&#114;&#x74;&#121;&#x6f;&#109;&#x2e;&#x6b;&#x61;&#x72;&#108;&#111;&#118;&#x40;&#103;&#109;&#x61;&#105;&#108;&#46;&#99;&#x6f;&#109;</a></td>
<td>99%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png" alt=""></td>
<td>中文 (简体)</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po">zh_CN</a></td>
<td>RCJacH <a href="&#109;&#97;&#x69;&#108;&#116;&#x6f;&#x3a;&#x52;&#67;&#74;&#97;&#99;&#x48;&#64;&#x6f;&#117;&#116;&#108;&#111;&#x6f;&#x6b;&#x2e;&#x63;&#111;&#109;">&#x52;&#67;&#74;&#97;&#99;&#x48;&#64;&#x6f;&#117;&#116;&#108;&#111;&#x6f;&#x6b;&#x2e;&#x63;&#111;&#109;</a></td>
<td>RCJacH <a href="&#x6d;&#97;&#x69;&#x6c;&#x74;&#x6f;&#x3a;&#x52;&#67;&#74;&#x61;&#99;&#72;&#64;&#111;&#x75;&#x74;&#x6c;&#x6f;&#x6f;&#107;&#46;&#x63;&#x6f;&#109;">&#x52;&#67;&#74;&#x61;&#99;&#72;&#64;&#111;&#x75;&#x74;&#x6c;&#x6f;&#x6f;&#107;&#46;&#x63;&#x6f;&#109;</a></td>
<td>66%</td>
</tr>
<tr>
@@ -480,7 +480,7 @@ $$
<td>日本語</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po">ja_JP</a></td>
<td></td>
<td>65%</td>
<td>64%</td>
</tr>
</tbody>
</table>

View File

@@ -6,6 +6,14 @@ It seems to be due to the setting `set term=ansi` in .vimrc. Removing it should
Unfortunately it is not possible. Joplin synchronises with file systems using an open format however it does not mean the sync files are meant to be user-editable. The format is designed to be performant and reliable, not user friendly (it cannot be both), and that cannot be changed. Joplin sync directory is basically just a database.
# Could there be a PIN or password to restrict access to Joplin?
Short answer: no. The end to end encryption that Joplin implements is to protect the data during transmission and on the cloud service so that only you can access it.
On the local device it is assumed that the data is safe due to the OS built-in security features. If additional security is needed it's always possible to put the notes on an encrypted Truecrypt drive for instance.
If someone that you don't trust has access to the computer, they can put a keylogger anyway so any local encryption or PIN access would not be useful.
# Why is it named Joplin?
The name comes from the composer and pianist [Scott Joplin](https://en.wikipedia.org/wiki/Scott_Joplin), which I often listen to. His name is also easy to remember and type so it fell like a good choice. And, to quote a user on Hacker News, "though Scott Joplin's ragtime musical style has a lot in common with some very informal music, his own approach was more educated, sophisticated, and precise. Every note was in its place for a reason, and he was known to prefer his pieces to be performed exactly as written. So you could say that compared to the people who came before him, his notes were more organized".
The name comes from the composer and pianist [Scott Joplin](https://en.wikipedia.org/wiki/Scott_Joplin), which I often listen to. His name is also easy to remember and type so it fell like a good choice. And, to quote a user on Hacker News, "though Scott Joplin's ragtime musical style has a lot in common with some very informal music, his own approach was more educated, sophisticated, and precise. Every note was in its place for a reason, and he was known to prefer his pieces to be performed exactly as written. So you could say that compared to the people who came before him, his notes were more organized".