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

Compare commits

...

7 Commits

Author SHA1 Message Date
Laurent Cozic
e08a909bdb Android release v2.1.4 2021-07-03 09:31:15 +01:00
Laurent Cozic
94ea177b76 Desktop release v2.1.8 2021-07-02 18:17:37 +01:00
Laurent Cozic
07ea79bb46 Desktop: Fixed issue where untitled notes where created after a note had been shared and synced 2021-07-02 18:17:08 +01:00
Laurent Cozic
f792fbb5fc Desktop: Fixes #5133: Items keep being uploaded to Joplin Server after a note has been shared 2021-07-02 18:17:01 +01:00
Laurent Cozic
7ed1a453e5 Clipper release v2.1.2 2021-06-28 11:40:53 +01:00
Laurent Cozic
55248ed08b Clipper: Fixed auth mechanism when extension popup is closed before process is finished 2021-06-28 11:39:31 +01:00
Laurent Cozic
1476cdf467 CLI v2.1.2 2021-06-27 16:52:03 +01:00
12 changed files with 146 additions and 45 deletions

View File

@@ -36,7 +36,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.0.11/J
Operating System | Download | Alt. Download
---|---|---
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.1.3/joplin-v2.1.3.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.1.3/joplin-v2.1.3-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.1.4/joplin-v2.1.4.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.1.4/joplin-v2.1.4-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | -
## Terminal application

View File

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

View File

@@ -32,7 +32,7 @@
],
"owner": "Laurent Cozic"
},
"version": "2.1.1",
"version": "2.1.2",
"bin": {
"joplin": "./main.js"
},
@@ -40,8 +40,8 @@
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "2.0",
"@joplin/renderer": "2.0",
"@joplin/lib": "2.1",
"@joplin/renderer": "2.1",
"aws-sdk": "^2.588.0",
"chalk": "^4.1.0",
"compare-version": "^0.1.2",
@@ -65,7 +65,7 @@
"yargs-parser": "^7.0.0"
},
"devDependencies": {
"@joplin/tools": "2.0",
"@joplin/tools": "2.1",
"@types/fs-extra": "^9.0.6",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper [DEV]",
"version": "2.1.1",
"version": "2.1.2",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",

View File

@@ -237,12 +237,18 @@ class AppComponent extends Component {
renderStartupScreen() {
const messages = {
serverFoundState: {
// We need to display the "Connecting to the Joplin
// application..." message because if the app doesn't currently
// allow access to the clipper API, the clipper tries several
// ports and it takes time before failing. So if we don't
// display any message, it looks like it's not doing anything
// when clicking on the extension button.
'searching': 'Connecting to the Joplin application...',
'not_found': 'Error: Could not connect to the Joplin application. Please ensure that it is started and that the clipper service is enabled in the configuration.',
},
authState: {
'starting': 'Starting...',
'waiting': 'The Joplin Web Clipper requires your authorisation in order to access your data. To do, please open the Joplin desktop application and grant permission. Note: Joplin 2.1+ is needed to use this version of the Web Clipper.',
'waiting': 'The Joplin Web Clipper requires your authorisation in order to access your data. To proceed, please open the Joplin desktop application and grant permission. Note: Joplin 2.1+ is needed to use this version of the Web Clipper.',
'rejected': 'Permission to access your data was not granted. To try again please close this popup and open it again.',
},
};

View File

@@ -85,7 +85,13 @@ class Bridge {
type: 'ENV_SET',
env: this.env(),
});
}
token() {
return this.token_;
}
async onReactAppStarts() {
await this.findClipperServerPort();
if (this.clipperServerPortStatus_ !== 'found') {
@@ -94,13 +100,7 @@ class Bridge {
}
await this.restoreState();
}
token() {
return this.token_;
}
async onReactAppStarts() {
await this.checkAuth();
if (!this.token_) return; // Didn't get a token
@@ -138,29 +138,59 @@ class Bridge {
this.dispatch({ type: 'AUTH_STATE_SET', value: 'waiting' });
const response = await this.clipperApiExec('POST', 'auth');
const authToken = response.auth_token;
// Note that Firefox and Chrome works differently for this:
//
// - In Chrome, the popup stays open, even when the user leaves the
// browser to grant permission in the application.
//
// - In Firefox, as soon as the browser loses focus, the popup closes.
//
// It means we can't rely on local state to get this working - instead
// we request the auth token, and cache it to local storage (along
// with a timestamp). Then next time the user opens the popup (after
// it was automatically closed by Firefox), that cached auth token is
// re-used and the auth process continues.
//
// https://github.com/laurent22/joplin/issues/5125#issuecomment-869547421
const existingAuthInfo = await this.storageGet(['authToken', 'authTokenTimestamp']);
let authToken = null;
if (existingAuthInfo.authToken && Date.now() - existingAuthInfo.authTokenTimestamp < 5 * 60 * 1000) {
console.info('checkAuth: we already have an auth token - reusing it');
authToken = existingAuthInfo.authToken;
} else {
console.info('checkAuth: we do not have an auth token - requesting it...');
const response = await this.clipperApiExec('POST', 'auth');
authToken = response.auth_token;
await this.storageSet({ authToken: authToken, authTokenTimestamp: Date.now() });
}
console.info('checkAuth: we do not have a token - requesting one using auth_token: ', authToken);
while (true) {
const response = await this.clipperApiExec('GET', 'auth/check', { auth_token: authToken });
try {
while (true) {
const response = await this.clipperApiExec('GET', 'auth/check', { auth_token: authToken });
if (response.status === 'rejected') {
console.info('checkAuth: Auth request was not accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'rejected' });
break;
} else if (response.status === 'accepted') {
console.info('checkAuth: Auth request was accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'accepted' });
this.token_ = response.token;
await this.storageSet({ token: this.token_ });
break;
} else if (response.status === 'waiting') {
await msleep(1000);
} else {
throw new Error(`Unknown auth/check status: ${response.status}`);
if (response.status === 'rejected') {
console.info('checkAuth: Auth request was not accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'rejected' });
break;
} else if (response.status === 'accepted') {
console.info('checkAuth: Auth request was accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'accepted' });
this.token_ = response.token;
await this.storageSet({ token: this.token_ });
break;
} else if (response.status === 'waiting') {
await msleep(1000);
} else {
throw new Error(`Unknown auth/check status: ${response.status}`);
}
}
} finally {
await this.storageSet({ authToken: '', authTokenTimestamp: 0 });
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.1.7",
"version": "2.1.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.1.7",
"version": "2.1.8",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097638
versionName "2.1.3"
versionCode 2097639
versionName "2.1.4"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -1,4 +1,4 @@
import { setupDatabaseAndSynchronizer, switchClient, createFolderTree, supportDir } from '../testing/test-utils';
import { setupDatabaseAndSynchronizer, switchClient, createFolderTree, supportDir, msleep } from '../testing/test-utils';
import Folder from '../models/Folder';
import { allNotesFolders } from '../testing/test-utils-synchronizer';
import Note from '../models/Note';
@@ -356,6 +356,8 @@ describe('models_Folder.sharing', function() {
const resourceService = new ResourceService();
const folder1: FolderEntity = await Folder.loadByTitle('folder 1');
const folder2: FolderEntity = await Folder.loadByTitle('folder 2');
let note1: NoteEntity = await Note.loadByTitle('note 1');
let note2: NoteEntity = await Note.loadByTitle('note 2');
note1 = await shim.attachFileToNote(note1, testImagePath);
@@ -369,6 +371,13 @@ describe('models_Folder.sharing', function() {
await Folder.updateNoLongerSharedItems(['1']);
// Since `updateNoLongerSharedItems` sets the parent_id too,
// double-check that it's not actually modified.
expect((await Note.loadByTitle('note 1')).parent_id).toBe(folder1.id);
expect((await Note.loadByTitle('note 2')).parent_id).toBe(folder2.id);
expect((await Folder.loadByTitle('folder 1')).parent_id).toBe(folder1.parent_id);
expect((await Folder.loadByTitle('folder 2')).parent_id).toBe(folder2.parent_id);
// At this point, all items associated with share 2 should have their
// share_id cleared, because the share no longer exists. We also
// double-check that share 1 hasn't been cleared.
@@ -385,6 +394,28 @@ describe('models_Folder.sharing', function() {
expect((await Note.loadByTitle('note 1')).share_id).toBe('');
expect((await Folder.loadByTitle('folder 1')).share_id).toBe('');
expect((await Resource.load(resourceId1)).share_id).toBe('');
{
// If we run it again, it should not update the notes since the share_id
// has already been cleared.
const resource1 = await Resource.load(resourceId1);
const resource2 = await Resource.load(resourceId2);
const note1 = await Note.loadByTitle('note 1');
const note2 = await Note.loadByTitle('note 2');
const folder1 = await Folder.loadByTitle('folder 1');
const folder2 = await Folder.loadByTitle('folder 2');
await msleep(1);
await Folder.updateNoLongerSharedItems(['1']);
expect((await Resource.load(resourceId1)).updated_time).toBe(resource1.updated_time);
expect((await Resource.load(resourceId2)).updated_time).toBe(resource2.updated_time);
expect((await Note.loadByTitle('note 1')).updated_time).toBe(note1.updated_time);
expect((await Note.loadByTitle('note 2')).updated_time).toBe(note2.updated_time);
expect((await Folder.loadByTitle('folder 1')).updated_time).toBe(folder1.updated_time);
expect((await Folder.loadByTitle('folder 2')).updated_time).toBe(folder2.updated_time);
}
});
});

View File

@@ -7,8 +7,11 @@ import Database from '../database';
import BaseItem from './BaseItem';
import Resource from './Resource';
import { isRootSharedFolder } from '../services/share/reducer';
import Logger from '../Logger';
const { substrWithEllipsis } = require('../string-utils.js');
const logger = Logger.create('models/Folder');
interface FolderEntityWithChildren extends FolderEntity {
children?: FolderEntity[];
}
@@ -288,9 +291,16 @@ export default class Folder extends BaseItem {
let sharedFolderIds: string[] = [];
const report = {
shareUpdateCount: 0,
unshareUpdateCount: 0,
};
for (const rootFolder of rootFolders) {
const children = await this.allChildrenFolders(rootFolder.id);
report.shareUpdateCount += children.length;
for (const child of children) {
if (child.share_id !== rootFolder.share_id) {
await this.save({
@@ -310,19 +320,25 @@ export default class Folder extends BaseItem {
// if they've been moved out of a shared folder.
// await this.unshareItems(ModelType.Folder, sharedFolderIds);
const sql = ['SELECT id FROM folders WHERE share_id != ""'];
const sql = ['SELECT id, parent_id FROM folders WHERE share_id != ""'];
if (sharedFolderIds.length) {
sql.push(` AND id NOT IN ("${sharedFolderIds.join('","')}")`);
}
const foldersToUnshare = await this.db().selectAll(sql.join(' '));
const foldersToUnshare: FolderEntity[] = await this.db().selectAll(sql.join(' '));
report.unshareUpdateCount += foldersToUnshare.length;
for (const item of foldersToUnshare) {
await this.save({
id: item.id,
share_id: '',
updated_time: Date.now(),
parent_id: item.parent_id,
}, { autoTimestamp: false });
}
logger.debug('updateFolderShareIds:', report);
}
public static async updateNoteShareIds() {
@@ -335,6 +351,8 @@ export default class Folder extends BaseItem {
WHERE notes.share_id != folders.share_id
`);
logger.debug('updateNoteShareIds: notes to update:', rows.length);
for (const row of rows) {
await Note.save({
id: row.id,
@@ -359,6 +377,8 @@ export default class Folder extends BaseItem {
OR n.is_shared != r.is_shared
`);
logger.debug('updateResourceShareIds: resources to update:', rows.length);
for (const row of rows) {
await Resource.save({
id: row.id,
@@ -384,27 +404,41 @@ export default class Folder extends BaseItem {
'resources': Resource,
};
const report: any = {};
for (const tableName of ['folders', 'notes', 'resources']) {
const ItemClass = tableNameToClasses[tableName];
const hasParentId = tableName !== 'resources';
const fields = ['id'];
if (hasParentId) fields.push('parent_id');
const query = activeShareIds.length ? `
SELECT id FROM ${tableName}
WHERE share_id NOT IN ("${activeShareIds.join('","')}")
SELECT ${this.db().escapeFields(fields)} FROM ${tableName}
WHERE share_id != "" AND share_id NOT IN ("${activeShareIds.join('","')}")
` : `
SELECT id FROM ${tableName}
SELECT ${this.db().escapeFields(fields)} FROM ${tableName}
WHERE share_id != ''
`;
const rows = await this.db().selectAll(query);
report[tableName] = rows.length;
for (const row of rows) {
await ItemClass.save({
const toSave: any = {
id: row.id,
share_id: '',
updated_time: Date.now(),
}, { autoTimestamp: false });
};
if (hasParentId) toSave.parent_id = row.parent_id;
await ItemClass.save(toSave, { autoTimestamp: false });
}
}
logger.debug('updateNoLongerSharedItems:', report);
}
static async allAsTree(folders: FolderEntity[] = null, options: any = null) {

View File

@@ -1,6 +1,6 @@
# Joplin terminal app changelog
## [cli-v2.1.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.1.1) - 2021-06-27T14:08:02Z
## [cli-v2.1.2](https://github.com/laurent22/joplin/releases/tag/cli-v2.1.2) - 2021-06-27T15:51:36Z
- New: Add support for X-API-MIN-VERSION header (51f3c00)
- New: Added flag to disable local lock when synchronising (7aff6d2)