You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
45 Commits
plugin_sys
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
f9bf7550c6 | ||
|
108ba55939 | ||
|
9dfe084a02 | ||
|
66204401c9 | ||
|
0644e2897d | ||
|
5761451def | ||
|
d819e6ee0c | ||
|
b66be79351 | ||
|
433fa21069 | ||
|
12db3d20ee | ||
|
80a70a6649 | ||
|
02ed297529 | ||
|
30543104be | ||
|
b6a004086c | ||
|
4265b3afb0 | ||
|
ccd7ba28d7 | ||
|
871f8b3a72 | ||
|
f276d2b2d4 | ||
|
2bb5acdfb1 | ||
|
dda0d8ca08 | ||
|
15f22c0507 | ||
|
fa7bd2cfab | ||
|
dc51781976 | ||
|
c874aee774 | ||
|
6c84fdc51d | ||
|
8ff0f7c529 | ||
|
b326ffc41c | ||
|
a0de8582e6 | ||
|
5eb0417b1a | ||
|
c42d9cf069 | ||
|
d965a7b6db | ||
|
c1919c2908 | ||
|
3fd7470104 | ||
|
0bf74142ac | ||
|
b3bf7144ac | ||
|
5738a4f92b | ||
|
784851b217 | ||
|
0e0803e050 | ||
|
3d3abfe259 | ||
|
3f38b518fe | ||
|
e673ee97de | ||
|
7cb55ffdc4 | ||
|
b706217d41 | ||
|
6f680081f4 | ||
|
fe41d37f8f |
@@ -68,6 +68,7 @@ CliClient/tests/models_Setting.js
|
||||
CliClient/tests/services_CommandService.js
|
||||
CliClient/tests/services_InteropService.js
|
||||
CliClient/tests/services_PluginService.js
|
||||
CliClient/tests/services_rest_Api.js
|
||||
CliClient/tests/services/plugins/sandboxProxy.js
|
||||
CliClient/tests/synchronizer_LockHandler.js
|
||||
CliClient/tests/synchronizer_MigrationHandler.js
|
||||
@@ -77,6 +78,7 @@ ElectronClient/commands/copyDevCommand.js
|
||||
ElectronClient/commands/focusElement.js
|
||||
ElectronClient/commands/startExternalEditing.js
|
||||
ElectronClient/commands/stopExternalEditing.js
|
||||
ElectronClient/commands/toggleExternalEditing.js
|
||||
ElectronClient/ElectronAppWrapper.js
|
||||
ElectronClient/global.d.js
|
||||
ElectronClient/gui/Button/Button.js
|
||||
@@ -198,6 +200,7 @@ ReactNativeClient/lib/commands/historyBackward.js
|
||||
ReactNativeClient/lib/commands/historyForward.js
|
||||
ReactNativeClient/lib/commands/synchronize.js
|
||||
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
|
||||
ReactNativeClient/lib/components/SelectDateTimeDialog.js
|
||||
ReactNativeClient/lib/errorUtils.js
|
||||
ReactNativeClient/lib/eventManager.js
|
||||
ReactNativeClient/lib/hooks/useEffectDebugger.js
|
||||
@@ -215,6 +218,7 @@ ReactNativeClient/lib/markdownUtils.js
|
||||
ReactNativeClient/lib/models/Alarm.js
|
||||
ReactNativeClient/lib/models/Setting.js
|
||||
ReactNativeClient/lib/ntpDate.js
|
||||
ReactNativeClient/lib/PoorManIntervals.js
|
||||
ReactNativeClient/lib/reducer.js
|
||||
ReactNativeClient/lib/services/AlarmService.js
|
||||
ReactNativeClient/lib/services/AlarmServiceDriver.android.js
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE
vendored
2
.github/PULL_REQUEST_TEMPLATE
vendored
@@ -20,6 +20,6 @@ If it's not related to any platform (such as a translation, change to the docume
|
||||
|
||||
Then please append the issue that you've addressed or fixed. Use "Resolves #123" for new features or improvements and "Fixes #123" for bug fixes.
|
||||
|
||||
AND PLEASE READ THE GUIDE: https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md
|
||||
AND PLEASE READ THE GUIDE: https://github.com/laurent22/joplin/blob/dev/CONTRIBUTING.md
|
||||
|
||||
-->
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -62,6 +62,7 @@ CliClient/tests/models_Setting.js
|
||||
CliClient/tests/services_CommandService.js
|
||||
CliClient/tests/services_InteropService.js
|
||||
CliClient/tests/services_PluginService.js
|
||||
CliClient/tests/services_rest_Api.js
|
||||
CliClient/tests/services/plugins/sandboxProxy.js
|
||||
CliClient/tests/synchronizer_LockHandler.js
|
||||
CliClient/tests/synchronizer_MigrationHandler.js
|
||||
@@ -71,6 +72,7 @@ ElectronClient/commands/copyDevCommand.js
|
||||
ElectronClient/commands/focusElement.js
|
||||
ElectronClient/commands/startExternalEditing.js
|
||||
ElectronClient/commands/stopExternalEditing.js
|
||||
ElectronClient/commands/toggleExternalEditing.js
|
||||
ElectronClient/ElectronAppWrapper.js
|
||||
ElectronClient/global.d.js
|
||||
ElectronClient/gui/Button/Button.js
|
||||
@@ -192,6 +194,7 @@ ReactNativeClient/lib/commands/historyBackward.js
|
||||
ReactNativeClient/lib/commands/historyForward.js
|
||||
ReactNativeClient/lib/commands/synchronize.js
|
||||
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
|
||||
ReactNativeClient/lib/components/SelectDateTimeDialog.js
|
||||
ReactNativeClient/lib/errorUtils.js
|
||||
ReactNativeClient/lib/eventManager.js
|
||||
ReactNativeClient/lib/hooks/useEffectDebugger.js
|
||||
@@ -209,6 +212,7 @@ ReactNativeClient/lib/markdownUtils.js
|
||||
ReactNativeClient/lib/models/Alarm.js
|
||||
ReactNativeClient/lib/models/Setting.js
|
||||
ReactNativeClient/lib/ntpDate.js
|
||||
ReactNativeClient/lib/PoorManIntervals.js
|
||||
ReactNativeClient/lib/reducer.js
|
||||
ReactNativeClient/lib/services/AlarmService.js
|
||||
ReactNativeClient/lib/services/AlarmServiceDriver.android.js
|
||||
|
@@ -38,7 +38,7 @@ If you want to start contributing to the project's code, please follow these gui
|
||||
- All the applications share the same backend (database, synchronisation, settings, models, business logic, etc.) so if you change something in the backend in one app, makes sure it still work in the other apps. Usually it does, but keep this in mind.
|
||||
- Pull requests that make many changes using an automated tool, like for spell fixing, styling, etc. will not be accepted. An exception would be if the changes have been discussed in the forum and someone has agreed to review **and test** the pull request.
|
||||
|
||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/dev/BUILD.md) for more details.
|
||||
|
||||
## Coding style
|
||||
|
||||
|
@@ -124,7 +124,6 @@ class Command extends BaseCommand {
|
||||
|
||||
if (args.name == 'locale') {
|
||||
setLocale(Setting.value('locale'));
|
||||
app().onLocaleChanged();
|
||||
}
|
||||
|
||||
await Setting.saveAll();
|
||||
|
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
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
@@ -42,38 +42,38 @@ stats['ar'] = {"percentDone":80};
|
||||
stats['eu'] = {"percentDone":34};
|
||||
stats['bs_BA'] = {"percentDone":83};
|
||||
stats['bg_BG'] = {"percentDone":66};
|
||||
stats['ca'] = {"percentDone":53};
|
||||
stats['hr_HR'] = {"percentDone":28};
|
||||
stats['ca'] = {"percentDone":96};
|
||||
stats['hr_HR'] = {"percentDone":27};
|
||||
stats['cs_CZ'] = {"percentDone":82};
|
||||
stats['da_DK'] = {"percentDone":74};
|
||||
stats['de_DE'] = {"percentDone":95};
|
||||
stats['de_DE'] = {"percentDone":98};
|
||||
stats['et_EE'] = {"percentDone":66};
|
||||
stats['en_GB'] = {"percentDone":100};
|
||||
stats['en_US'] = {"percentDone":100};
|
||||
stats['es_ES'] = {"percentDone":95};
|
||||
stats['eo'] = {"percentDone":38};
|
||||
stats['fr_FR'] = {"percentDone":94};
|
||||
stats['fr_FR'] = {"percentDone":99};
|
||||
stats['gl_ES'] = {"percentDone":43};
|
||||
stats['id_ID'] = {"percentDone":93};
|
||||
stats['it_IT'] = {"percentDone":91};
|
||||
stats['nl_NL'] = {"percentDone":96};
|
||||
stats['it_IT'] = {"percentDone":90};
|
||||
stats['nl_BE'] = {"percentDone":34};
|
||||
stats['nl_NL'] = {"percentDone":95};
|
||||
stats['nb_NO'] = {"percentDone":88};
|
||||
stats['fa'] = {"percentDone":80};
|
||||
stats['pl_PL'] = {"percentDone":96};
|
||||
stats['pt_PT'] = {"percentDone":89};
|
||||
stats['fa'] = {"percentDone":83};
|
||||
stats['pl_PL'] = {"percentDone":98};
|
||||
stats['pt_PT'] = {"percentDone":88};
|
||||
stats['pt_BR'] = {"percentDone":96};
|
||||
stats['ro'] = {"percentDone":78};
|
||||
stats['ro'] = {"percentDone":77};
|
||||
stats['sl_SI'] = {"percentDone":42};
|
||||
stats['sv'] = {"percentDone":71};
|
||||
stats['sv'] = {"percentDone":70};
|
||||
stats['th_TH'] = {"percentDone":52};
|
||||
stats['vi'] = {"percentDone":85};
|
||||
stats['tr_TR'] = {"percentDone":96};
|
||||
stats['tr_TR'] = {"percentDone":98};
|
||||
stats['el_GR'] = {"percentDone":96};
|
||||
stats['ru_RU'] = {"percentDone":95};
|
||||
stats['sr_RS'] = {"percentDone":72};
|
||||
stats['sr_RS'] = {"percentDone":71};
|
||||
stats['zh_CN'] = {"percentDone":96};
|
||||
stats['zh_TW'] = {"percentDone":95};
|
||||
stats['ja_JP'] = {"percentDone":96};
|
||||
stats['ko'] = {"percentDone":86};
|
||||
stats['ja_JP'] = {"percentDone":98};
|
||||
stats['ko'] = {"percentDone":98};
|
||||
module.exports = { locales: locales, stats: stats };
|
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
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 it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6
CliClient/package-lock.json
generated
6
CliClient/package-lock.json
generated
@@ -5901,9 +5901,9 @@
|
||||
}
|
||||
},
|
||||
"slug": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/slug/-/slug-3.3.4.tgz",
|
||||
"integrity": "sha512-VpHbtRCEWmgaZsrZcTsVl/Dhw98lcrOYDO17DNmJCNpppI6s3qJvnNu2Q3D4L84/2bi6vkW40mjNQI9oGQsflg=="
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/slug/-/slug-3.5.0.tgz",
|
||||
"integrity": "sha512-+pZLDhMtmAc+ZcojQSMlUKDZBYmvhZiZmK8Ffx/D3Q/MIMHPDBAMbWvWN8vJb9xl2MfbDdRWxFzrdOhBiyVpow=="
|
||||
},
|
||||
"snapdragon": {
|
||||
"version": "0.8.2",
|
||||
|
@@ -102,7 +102,7 @@
|
||||
"sax": "^1.2.4",
|
||||
"server-destroy": "^1.0.1",
|
||||
"sharp": "^0.23.2",
|
||||
"slug": "^3.3.4",
|
||||
"slug": "^3.5.0",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"sqlite3": "^4.1.1",
|
||||
"string-padding": "^1.0.2",
|
||||
|
@@ -4,7 +4,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, mockDate, restoreDate } = require('test-utils.js');
|
||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
const Note = require('lib/models/Note');
|
||||
const ItemChange = require('lib/models/ItemChange');
|
||||
@@ -33,16 +33,32 @@ const calculateScore = (searchString, notes) => {
|
||||
const numTokens = notes.map(note => note.title.split(' ').length);
|
||||
const avgTokens = Math.round(numTokens.reduce((a, b) => a + b, 0) / notes.length);
|
||||
|
||||
let titleBM25 = new Array(notes.length).fill(-1);
|
||||
const msSinceEpoch = Math.round(new Date().getTime());
|
||||
const msPerDay = 86400000;
|
||||
const weightForDaysSinceLastUpdate = (row) => {
|
||||
// BM25 weights typically range 0-10, and last updated date should weight similarly, though prioritizing recency logarithmically.
|
||||
// An alpha of 200 ensures matches in the last week will show up front (11.59) and often so for matches within 2 weeks (5.99),
|
||||
// but is much less of a factor at 30 days (2.84) or very little after 90 days (0.95), focusing mostly on content at that point.
|
||||
if (!row.user_updated_time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const alpha = 200;
|
||||
const daysSinceLastUpdate = (msSinceEpoch - row.user_updated_time) / msPerDay;
|
||||
return alpha * Math.log(1 + 1 / Math.max(daysSinceLastUpdate, 0.5));
|
||||
};
|
||||
|
||||
let titleBM25WeightedByLastUpdate = new Array(notes.length).fill(-1);
|
||||
if (avgTokens != 0) {
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
titleBM25[i] = IDF(notes.length, notesWithWord) * ((freqTitle[i] * (K1 + 1)) / (freqTitle[i] + K1 * (1 - B + B * (numTokens[i] / avgTokens))));
|
||||
titleBM25WeightedByLastUpdate[i] = IDF(notes.length, notesWithWord) * ((freqTitle[i] * (K1 + 1)) / (freqTitle[i] + K1 * (1 - B + B * (numTokens[i] / avgTokens))));
|
||||
titleBM25WeightedByLastUpdate[i] += weightForDaysSinceLastUpdate(notes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const scores = [];
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
if (freqTitle[i]) scores.push(titleBM25[i]);
|
||||
if (freqTitle[i]) scores.push(titleBM25WeightedByLastUpdate[i]);
|
||||
}
|
||||
|
||||
scores.sort().reverse();
|
||||
@@ -142,33 +158,54 @@ describe('services_SearchEngine', function() {
|
||||
expect(rows[1].id).toBe(n2.id);
|
||||
}));
|
||||
|
||||
it('should correctly weigh notes using BM25', asyncTest(async () => {
|
||||
|
||||
it('should correctly weigh notes using BM25 and user_updated_time', asyncTest(async () => {
|
||||
await mockDate(2020, 9, 30, 50);
|
||||
const noteData = [
|
||||
{
|
||||
title: 'abc test2 test2',
|
||||
updated_time: 1601425064756,
|
||||
user_updated_time: 1601425064756,
|
||||
created_time: 1601425064756,
|
||||
user_created_time: 1601425064756,
|
||||
},
|
||||
{
|
||||
title: 'foo foo',
|
||||
updated_time: 1601425064758,
|
||||
user_updated_time: 1601425064758,
|
||||
created_time: 1601425064758,
|
||||
user_created_time: 1601425064758,
|
||||
},
|
||||
{
|
||||
title: 'dead beef',
|
||||
updated_time: 1601425064760,
|
||||
user_updated_time: 1601425064760,
|
||||
created_time: 1601425064760,
|
||||
user_created_time: 1601425064760,
|
||||
},
|
||||
{
|
||||
title: 'test2 bar',
|
||||
updated_time: 1601425064761,
|
||||
user_updated_time: 1601425064761,
|
||||
created_time: 1601425064761,
|
||||
user_created_time: 1601425064761,
|
||||
},
|
||||
{
|
||||
title: 'blah blah abc',
|
||||
updated_time: 1601425064763,
|
||||
user_updated_time: 1601425064763,
|
||||
created_time: 1601425064763,
|
||||
user_created_time: 1601425064763,
|
||||
},
|
||||
];
|
||||
|
||||
const n0 = await Note.save(noteData[0]);
|
||||
const n1 = await Note.save(noteData[1]);
|
||||
const n2 = await Note.save(noteData[2]);
|
||||
const n3 = await Note.save(noteData[3]);
|
||||
const n4 = await Note.save(noteData[4]);
|
||||
|
||||
const n0 = await Note.save(noteData[0], { autoTimestamp: false });
|
||||
const n1 = await Note.save(noteData[1], { autoTimestamp: false });
|
||||
const n2 = await Note.save(noteData[2], { autoTimestamp: false });
|
||||
const n3 = await Note.save(noteData[3], { autoTimestamp: false });
|
||||
const n4 = await Note.save(noteData[4], { autoTimestamp: false });
|
||||
restoreDate();
|
||||
await engine.syncTables();
|
||||
await mockDate(2020, 9, 30, 50);
|
||||
|
||||
let searchString = 'abc';
|
||||
let scores = calculateScore(searchString, noteData);
|
||||
@@ -198,6 +235,7 @@ describe('services_SearchEngine', function() {
|
||||
// console.log(scores);
|
||||
|
||||
expect(rows[0].weight).toEqual(scores[0]);
|
||||
await restoreDate();
|
||||
}));
|
||||
|
||||
it('should tell where the results are found', asyncTest(async () => {
|
||||
|
@@ -1,21 +1,18 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
import Api from 'lib/services/rest/Api';
|
||||
import shim from 'lib/shim';
|
||||
|
||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('test-utils.js');
|
||||
const Api = require('lib/services/rest/Api').default;
|
||||
const Folder = require('lib/models/Folder');
|
||||
const Resource = require('lib/models/Resource');
|
||||
const Note = require('lib/models/Note');
|
||||
const Tag = require('lib/models/Tag');
|
||||
const NoteTag = require('lib/models/NoteTag');
|
||||
const shim = require('lib/shim').default;
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
let api = null;
|
||||
let api:Api = null;
|
||||
|
||||
describe('services_rest_Api', function() {
|
||||
|
||||
@@ -28,21 +25,23 @@ describe('services_rest_Api', function() {
|
||||
|
||||
it('should ping', asyncTest(async () => {
|
||||
const response = await api.route('GET', 'ping');
|
||||
expect(response).toBe('JoplinClipperServer');
|
||||
}));
|
||||
|
||||
it('should handle Not Found errors', asyncTest(async () => {
|
||||
const hasThrown = await checkThrowAsync(async () => await api.route('GET', 'pong'));
|
||||
expect(hasThrown).toBe(true);
|
||||
}));
|
||||
|
||||
it('should get folders', asyncTest(async () => {
|
||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
||||
await Folder.save({ title: 'mon carnet' });
|
||||
const response = await api.route('GET', 'folders');
|
||||
expect(response.length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should update folders', asyncTest(async () => {
|
||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
||||
const response = await api.route('PUT', `folders/${f1.id}`, null, JSON.stringify({
|
||||
await api.route('PUT', `folders/${f1.id}`, null, JSON.stringify({
|
||||
title: 'modifié',
|
||||
}));
|
||||
|
||||
@@ -84,8 +83,8 @@ describe('services_rest_Api', function() {
|
||||
const response2 = await api.route('GET', `folders/${f1.id}/notes`);
|
||||
expect(response2.length).toBe(0);
|
||||
|
||||
const n1 = await Note.save({ title: 'un', parent_id: f1.id });
|
||||
const n2 = await Note.save({ title: 'deux', parent_id: f1.id });
|
||||
await Note.save({ title: 'un', parent_id: f1.id });
|
||||
await Note.save({ title: 'deux', parent_id: f1.id });
|
||||
const response = await api.route('GET', `folders/${f1.id}/notes`);
|
||||
expect(response.length).toBe(2);
|
||||
}));
|
||||
@@ -100,7 +99,7 @@ describe('services_rest_Api', function() {
|
||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
||||
const f2 = await Folder.save({ title: 'mon deuxième carnet' });
|
||||
const n1 = await Note.save({ title: 'un', parent_id: f1.id });
|
||||
const n2 = await Note.save({ title: 'deux', parent_id: f1.id });
|
||||
await Note.save({ title: 'deux', parent_id: f1.id });
|
||||
const n3 = await Note.save({ title: 'trois', parent_id: f2.id });
|
||||
|
||||
response = await api.route('GET', 'notes');
|
||||
@@ -134,6 +133,41 @@ describe('services_rest_Api', function() {
|
||||
expect(!!response.id).toBe(true);
|
||||
}));
|
||||
|
||||
it('should allow setting note properties', asyncTest(async () => {
|
||||
let response:any = null;
|
||||
const f = await Folder.save({ title: 'mon carnet' });
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing',
|
||||
parent_id: f.id,
|
||||
latitude: '48.732071',
|
||||
longitude: '-3.458700',
|
||||
altitude: '21',
|
||||
}));
|
||||
|
||||
const noteId = response.id;
|
||||
|
||||
{
|
||||
const note = await Note.load(noteId);
|
||||
expect(note.latitude).toBe('48.73207100');
|
||||
expect(note.longitude).toBe('-3.45870000');
|
||||
expect(note.altitude).toBe('21.0000');
|
||||
}
|
||||
|
||||
await api.route('PUT', 'notes/' + noteId, null, JSON.stringify({
|
||||
latitude: '49',
|
||||
longitude: '-3',
|
||||
altitude: '22',
|
||||
}));
|
||||
|
||||
{
|
||||
const note = await Note.load(noteId);
|
||||
expect(note.latitude).toBe('49.00000000');
|
||||
expect(note.longitude).toBe('-3.00000000');
|
||||
expect(note.altitude).toBe('22.0000');
|
||||
}
|
||||
}));
|
||||
|
||||
it('should preserve user timestamps when creating notes', asyncTest(async () => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: 'mon carnet' });
|
||||
@@ -221,10 +255,9 @@ describe('services_rest_Api', function() {
|
||||
}));
|
||||
|
||||
it('should delete resources', asyncTest(async () => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: 'mon carnet' });
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing image',
|
||||
parent_id: f.id,
|
||||
image_data_url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII=',
|
||||
@@ -286,7 +319,7 @@ describe('services_rest_Api', function() {
|
||||
const tag = await Tag.save({ title: 'mon étiquette' });
|
||||
const note = await Note.save({ title: 'ma note' });
|
||||
|
||||
const response = await api.route('POST', `tags/${tag.id}/notes`, null, JSON.stringify({
|
||||
await api.route('POST', `tags/${tag.id}/notes`, null, JSON.stringify({
|
||||
id: note.id,
|
||||
}));
|
||||
|
||||
@@ -299,7 +332,7 @@ describe('services_rest_Api', function() {
|
||||
const note = await Note.save({ title: 'ma note' });
|
||||
await Tag.addNote(tag.id, note.id);
|
||||
|
||||
const response = await api.route('DELETE', `tags/${tag.id}/notes/${note.id}`);
|
||||
await api.route('DELETE', `tags/${tag.id}/notes/${note.id}`);
|
||||
|
||||
const noteIds = await Tag.noteIds(tag.id);
|
||||
expect(noteIds.length).toBe(0);
|
@@ -634,6 +634,16 @@ function tempFilePath(ext) {
|
||||
return `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.${ext}`;
|
||||
}
|
||||
|
||||
function mockDate(year, month, day, tick) {
|
||||
const fixedDate = new Date(2020, 0, 1);
|
||||
jasmine.clock().install();
|
||||
jasmine.clock().mockDate(fixedDate);
|
||||
}
|
||||
|
||||
function restoreDate() {
|
||||
jasmine.clock().uninstall();
|
||||
}
|
||||
|
||||
// Application for feature integration testing
|
||||
class TestApp extends BaseApplication {
|
||||
constructor(hasGui = true) {
|
||||
@@ -702,4 +712,4 @@ class TestApp extends BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { synchronizerStart, syncTargetName, setSyncTargetName, syncDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
|
||||
module.exports = { synchronizerStart, syncTargetName, setSyncTargetName, syncDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, mockDate, restoreDate, TestApp };
|
||||
|
@@ -75,6 +75,7 @@ const globalCommands = [
|
||||
require('./commands/focusElement'),
|
||||
require('./commands/startExternalEditing'),
|
||||
require('./commands/stopExternalEditing'),
|
||||
require('./commands/toggleExternalEditing'),
|
||||
require('./commands/copyDevCommand'),
|
||||
require('lib/commands/synchronize'),
|
||||
require('lib/commands/historyBackward'),
|
||||
@@ -465,14 +466,14 @@ class Application extends BaseApplication {
|
||||
async start(argv:string[]):Promise<any> {
|
||||
const electronIsDev = require('electron-is-dev');
|
||||
|
||||
await fs.mkdirp(Setting.value('templateDir'), 0o755);
|
||||
|
||||
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
|
||||
// insert an extra argument so that they can be processed in a consistent way everywhere.
|
||||
if (!electronIsDev) argv.splice(1, 0, '.');
|
||||
|
||||
argv = await super.start(argv);
|
||||
|
||||
await fs.mkdirp(Setting.value('templateDir'), 0o755);
|
||||
|
||||
await this.applySettingsSideEffects();
|
||||
|
||||
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
|
||||
|
42
ElectronClient/commands/toggleExternalEditing.ts
Normal file
42
ElectronClient/commands/toggleExternalEditing.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { CommandRuntime, CommandDeclaration } from '../lib/services/CommandService';
|
||||
import { _ } from 'lib/locale';
|
||||
import { AppState } from '../app';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
|
||||
interface Props {
|
||||
noteId: string
|
||||
noteIsBeingWatched: boolean
|
||||
}
|
||||
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'toggleExternalEditing',
|
||||
label: () => _('Toggle external editing'),
|
||||
iconName: 'icon-share',
|
||||
};
|
||||
|
||||
export const runtime = ():CommandRuntime => {
|
||||
return {
|
||||
execute: async (props:Props) => {
|
||||
if (!props.noteId) return;
|
||||
|
||||
if (props.noteIsBeingWatched) {
|
||||
CommandService.instance().execute('stopExternalEditing', { noteId: props.noteId });
|
||||
} else {
|
||||
CommandService.instance().execute('startExternalEditing', { noteId: props.noteId });
|
||||
}
|
||||
},
|
||||
isEnabled: (props:Props) => {
|
||||
return !!props.noteId;
|
||||
},
|
||||
mapStateToProps: (state:AppState):Props => {
|
||||
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
||||
return {
|
||||
noteId: noteId,
|
||||
noteIsBeingWatched: noteId ? state.watchedNoteFiles.includes(noteId) : false,
|
||||
};
|
||||
},
|
||||
title: (props:Props) => {
|
||||
return props.noteIsBeingWatched ? _('Stop') : '';
|
||||
},
|
||||
};
|
||||
};
|
@@ -65,7 +65,7 @@ const commandNames:string[] = [
|
||||
'toggleSidebar',
|
||||
'toggleNoteList',
|
||||
'toggleVisiblePanes',
|
||||
'startExternalEditing',
|
||||
'toggleExternalEditing',
|
||||
'setTags',
|
||||
'showNoteContentProperties',
|
||||
'copyDevCommand',
|
||||
@@ -564,7 +564,7 @@ function useMenu(props:Props) {
|
||||
note: {
|
||||
label: _('&Note'),
|
||||
submenu: [
|
||||
menuItemDic.startExternalEditing,
|
||||
menuItemDic.toggleExternalEditing,
|
||||
menuItemDic.setTags,
|
||||
separator(),
|
||||
menuItemDic.showNoteContentProperties,
|
||||
@@ -735,7 +735,7 @@ function useMenu(props:Props) {
|
||||
|
||||
const layoutButtonSequenceOptions = Setting.enumOptions('layoutButtonSequence');
|
||||
for (const value in layoutButtonSequenceOptions) {
|
||||
menuItemSetEnabled(`layoutButtonSequence_${value}`, props.layoutButtonSequence === Number(value));
|
||||
menuItemSetChecked(`layoutButtonSequence_${value}`, props.layoutButtonSequence === Number(value));
|
||||
}
|
||||
|
||||
function applySortItemCheckState(type:string) {
|
||||
|
@@ -34,7 +34,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
const commandNames = [
|
||||
'historyBackward',
|
||||
'historyForward',
|
||||
'startExternalEditing',
|
||||
'toggleExternalEditing',
|
||||
'-',
|
||||
'textBold',
|
||||
'textItalic',
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import 'codemirror/addon/mode/multiplex';
|
||||
import 'codemirror/mode/stex/stex';
|
||||
import Setting from 'lib/models/Setting';
|
||||
|
||||
// Joplin markdown is a the same as markdown mode, but it has configured defaults
|
||||
// and support for katex math blocks
|
||||
@@ -10,7 +11,7 @@ export default function useJoplinMode(CodeMirror: any) {
|
||||
name: 'markdown',
|
||||
taskLists: true,
|
||||
strikethrough: true,
|
||||
emoji: true,
|
||||
emoji: Setting.value('markdown.plugin.emoji'),
|
||||
tokenTypeOverrides: {
|
||||
linkText: 'link-text',
|
||||
},
|
||||
@@ -33,7 +34,7 @@ export default function useJoplinMode(CodeMirror: any) {
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function(): {outer: any, openCharacter: string, inner: any} {
|
||||
startState: function(): { outer: any, openCharacter: string, inner: any } {
|
||||
return {
|
||||
outer: CodeMirror.startState(markdownMode),
|
||||
openCharacter: '',
|
||||
|
@@ -1112,7 +1112,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
/>;
|
||||
}
|
||||
|
||||
const leftButtonCommandNames = ['historyBackward', 'historyForward', 'startExternalEditing'];
|
||||
const leftButtonCommandNames = ['historyBackward', 'historyForward', 'toggleExternalEditing'];
|
||||
|
||||
function renderLeftExtraToolbarButtons() {
|
||||
const buttons = [];
|
||||
|
@@ -591,7 +591,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
'historyBackward',
|
||||
'historyForward',
|
||||
'toggleEditors',
|
||||
'startExternalEditing',
|
||||
'toggleExternalEditing',
|
||||
]),
|
||||
setTagsToolbarButtonInfo: toolbarButtonUtils.commandsToToolbarButtons(state, [
|
||||
'setTags',
|
||||
|
@@ -60,7 +60,7 @@ class ToolbarBaseComponent extends React.Component<Props, any> {
|
||||
toolbarButtonInfo={o}
|
||||
/>);
|
||||
} else if (itemType === 'button') {
|
||||
const target = ['historyForward', 'historyBackward', 'startExternalEditing'].includes(o.name) ? leftItemComps : centerItemComps;
|
||||
const target = ['historyForward', 'historyBackward', 'toggleExternalEditing'].includes(o.name) ? leftItemComps : centerItemComps;
|
||||
target.push(<ToolbarButton {...props} />);
|
||||
} else if (itemType === 'separator') {
|
||||
centerItemComps.push(<ToolbarSpace {...props} />);
|
||||
|
@@ -29,6 +29,8 @@ export default class NoteListUtils {
|
||||
|
||||
const notes = noteIds.map(id => BaseModel.byId(props.notes, id));
|
||||
|
||||
const singleNoteId = noteIds.length === 1 ? noteIds[0] : null;
|
||||
|
||||
let hasEncrypted = false;
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
if (notes[i].encryption_applied) hasEncrypted = true;
|
||||
@@ -59,14 +61,9 @@ export default class NoteListUtils {
|
||||
})
|
||||
);
|
||||
|
||||
if (props.watchedNoteFiles.indexOf(noteIds[0]) < 0) {
|
||||
menu.append(
|
||||
new MenuItem(menuUtils.commandToStatefulMenuItem('startExternalEditing', { noteId: noteIds[0] }))
|
||||
);
|
||||
} else {
|
||||
menu.append(
|
||||
new MenuItem(menuUtils.commandToStatefulMenuItem('stopExternalEditing', { noteId: noteIds[0] }))
|
||||
);
|
||||
if (singleNoteId) {
|
||||
const cmd = props.watchedNoteFiles.includes(singleNoteId) ? 'stopExternalEditing' : 'startExternalEditing';
|
||||
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem(cmd, { noteId: singleNoteId })));
|
||||
}
|
||||
|
||||
if (noteIds.length <= 1) {
|
||||
|
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user