1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-23 23:33:01 +02:00

Compare commits

..

26 Commits

Author SHA1 Message Date
Laurent Cozic
63136a6f0d ios-v12.5.2 2021-11-06 11:59:13 +00:00
Laurent Cozic
b45585dc21 Desktop release v2.4.12 2021-10-13 17:43:03 +01:00
Laurent Cozic
46786cc186 Tools: Update Electron Builder time server (previous was not working anymore) 2021-10-13 17:42:49 +01:00
Laurent Cozic
eca6e2ff3d Desktop release v2.4.11 2021-10-13 12:51:15 +01:00
Laurent Cozic
523382ec83 Desktop release v2.4.10 2021-10-13 10:58:10 +01:00
Laurent Cozic
3d87e89753 Desktop, Cli: Fixes #5543: Fixed running out of memory when importing large ENEX files 2021-10-13 10:57:43 +01:00
Laurent Cozic
acc5959284 ios-v12.4.2 2021-09-29 19:50:34 +01:00
Laurent Cozic
6c4f71689c Android 2.4.3 2021-09-29 19:50:18 +01:00
Laurent Cozic
bb44c4e6ab Desktop release v2.4.9 2021-09-29 19:28:24 +01:00
Laurent Cozic
4b39d30255 All: Fix default sync target 2021-09-29 19:27:26 +01:00
Laurent Cozic
3a9867db33 CLI v2.4.1 2021-09-29 16:29:06 +01:00
Laurent Cozic
c831c7bf6f Releasing sub-packages 2021-09-29 16:23:56 +01:00
Laurent Cozic
c7cc5cc1a9 publish 2021-09-29 16:22:20 +01:00
Laurent Cozic
eb4b0e64ea Releasing sub-packages 2021-09-29 16:16:40 +01:00
Laurent Cozic
9dabac0afe lockfile 2021-09-29 16:09:02 +01:00
Laurent Cozic
73f0f861a5 ios-v12.4.1 2021-09-29 15:31:37 +01:00
Laurent Cozic
dd0b983a09 Server v2.4.11 2021-09-26 18:11:12 +01:00
Laurent Cozic
c45f961b8c Server: Fixed Stripe checkout when a coupon is used 2021-09-26 18:09:52 +01:00
Laurent Cozic
05ec7cc8fa All: Implemented htmlpack package which could be used to export an HTML file and all its resources into a single HTML file 2021-09-26 17:58:06 +01:00
Laurent Cozic
57a1d03b4b Server: Do not allow accepting share more than once 2021-09-26 17:58:06 +01:00
小骏
1df2d8d7af All: Translation: Update zh_CN.po (#5504) 2021-09-26 09:15:39 -04:00
Laurent Cozic
b1d0c15210 Desktop, Cli: Make exported HTML more readable on mobile 2021-09-26 12:01:46 +01:00
Laurent Cozic
2fd4fb3e73 Server v2.4.10 2021-09-25 20:07:23 +01:00
Laurent Cozic
9f17b28f85 Chore: Cleaned up server UserItem interface 2021-09-25 19:51:44 +01:00
Laurent Cozic
8ada059401 Desktop: Improved accepting a folder share 2021-09-25 18:00:43 +01:00
Laurent Cozic
0175348868 Server: Improved share service reliability and optimised performance 2021-09-25 17:39:42 +01:00
57 changed files with 961 additions and 125 deletions

View File

@@ -856,6 +856,9 @@ packages/generator-joplin/generators/app/templates/api_index.js.map
packages/generator-joplin/generators/app/templates/src/index.d.ts
packages/generator-joplin/generators/app/templates/src/index.js
packages/generator-joplin/generators/app/templates/src/index.js.map
packages/htmlpack/src/index.d.ts
packages/htmlpack/src/index.js
packages/htmlpack/src/index.js.map
packages/lib/AsyncActionQueue.d.ts
packages/lib/AsyncActionQueue.js
packages/lib/AsyncActionQueue.js.map

3
.gitignore vendored
View File

@@ -841,6 +841,9 @@ packages/generator-joplin/generators/app/templates/api_index.js.map
packages/generator-joplin/generators/app/templates/src/index.d.ts
packages/generator-joplin/generators/app/templates/src/index.js
packages/generator-joplin/generators/app/templates/src/index.js.map
packages/htmlpack/src/index.d.ts
packages/htmlpack/src/index.js
packages/htmlpack/src/index.js.map
packages/lib/AsyncActionQueue.d.ts
packages/lib/AsyncActionQueue.js
packages/lib/AsyncActionQueue.js.map

View File

@@ -1,12 +1,12 @@
{
"name": "joplin",
"version": "2.3.2",
"version": "2.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "joplin",
"version": "2.3.2",
"version": "2.4.1",
"license": "MIT",
"dependencies": {
"aws-sdk": "^2.588.0",

View File

@@ -33,7 +33,7 @@
],
"owner": "Laurent Cozic"
},
"version": "2.4.0",
"version": "2.4.1",
"bin": {
"joplin": "./main.js"
},

View File

@@ -75,6 +75,7 @@ interface Props {
shareInvitations: ShareInvitation[];
isSafeMode: boolean;
needApiAuth: boolean;
processingShareInvitationResponse: boolean;
}
interface ShareFolderDialogOptions {
@@ -197,6 +198,7 @@ class MainScreenComponent extends React.Component<Props, State> {
}
private showShareInvitationNotification(props: Props): boolean {
if (props.processingShareInvitationResponse) return false;
return !!props.shareInvitations.find(i => i.status === 0);
}
@@ -546,8 +548,16 @@ class MainScreenComponent extends React.Component<Props, State> {
};
const onInvitationRespond = async (shareUserId: string, accept: boolean) => {
await ShareService.instance().respondInvitation(shareUserId, accept);
await ShareService.instance().refreshShareInvitations();
// The below functions can take a bit of time to complete so in the
// meantime we hide the notification so that the user doesn't click
// multiple times on the Accept link.
ShareService.instance().setProcessingShareInvitationResponse(true);
try {
await ShareService.instance().respondInvitation(shareUserId, accept);
await ShareService.instance().refreshShareInvitations();
} finally {
ShareService.instance().setProcessingShareInvitationResponse(false);
}
void reg.scheduleSync(1000);
};
@@ -853,6 +863,7 @@ const mapStateToProps = (state: AppState) => {
mainLayout: state.mainLayout,
startupPluginsLoaded: state.startupPluginsLoaded,
shareInvitations: state.shareService.shareInvitations,
processingShareInvitationResponse: state.shareService.processingShareInvitationResponse,
isSafeMode: state.settings.isSafeMode,
needApiAuth: state.needApiAuth,
showInstallTemplatesPlugin: state.hasLegacyTemplates && !state.pluginService.plugins['joplin.plugin.templates'],

View File

@@ -1,12 +1,12 @@
{
"name": "@joplin/app-desktop",
"version": "2.4.8",
"version": "2.4.12",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@joplin/app-desktop",
"version": "2.4.8",
"version": "2.4.12",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.13.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.4.8",
"version": "2.4.12",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -37,7 +37,7 @@
"asar": true,
"asarUnpack": "./node_modules/node-notifier/vendor/**",
"win": {
"rfc3161TimeStampServer": "http://timestamp.comodoca.com/rfc3161",
"rfc3161TimeStampServer": "http://sha256timestamp.ws.symantec.com/sha256/timestamp",
"icon": "../../Assets/ImageSources/Joplin.ico",
"target": [
{

View File

@@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097650
versionName "2.4.2"
versionCode 2097651
versionName "2.4.3"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -486,13 +486,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 72;
CURRENT_PROJECT_VERSION = 76;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.4.0;
MARKETING_VERSION = 12.5.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -514,12 +514,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 72;
CURRENT_PROJECT_VERSION = 76;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.4.0;
MARKETING_VERSION = 12.5.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -659,14 +659,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 72;
CURRENT_PROJECT_VERSION = 76;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.4.0;
MARKETING_VERSION = 12.5.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -690,14 +690,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 72;
CURRENT_PROJECT_VERSION = 76;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.4.0;
MARKETING_VERSION = 12.5.2;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -488,7 +488,7 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: d2f54de51f69366bd1f5c1fb9270698dce678f8d
FBReactNativeSpec: 6da2c8ff1ebe6b6cf4510fcca58c24c4d02b16fc
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
JoplinCommonShareExtension: 270b4f8eb4e22828eeda433a04ed689fc1fd09b5
JoplinRNShareExtension: 7137e9787374e1b0797ecbef9103d1588d90e403

View File

@@ -498,6 +498,7 @@ async function initialize(dispatch: Function) {
let locale = NativeModules.I18nManager.localeIdentifier;
if (!locale) locale = defaultLocale();
Setting.setValue('locale', closestSupportedLocale(locale));
Setting.setValue('sync.target', 0);
Setting.setValue('firstStart', 0);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-htmlparser2",
"version": "4.1.34",
"version": "4.1.36",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,7 +1,7 @@
{
"name": "@joplin/fork-htmlparser2",
"description": "Fast & forgiving HTML/XML/RSS parser",
"version": "4.1.34",
"version": "4.1.36",
"author": "Felix Boehm <me@feedic.com>",
"publishConfig": {
"access": "public"
@@ -65,5 +65,5 @@
"prettier": {
"tabWidth": 4
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-sax",
"version": "1.2.38",
"version": "1.2.40",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -2,7 +2,7 @@
"name": "@joplin/fork-sax",
"description": "An evented streaming XML parser in JavaScript",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"version": "1.2.38",
"version": "1.2.40",
"main": "lib/sax.js",
"publishConfig": {
"access": "public"
@@ -18,5 +18,5 @@
"standard": "^8.6.0",
"tap": "^10.5.1"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

1
packages/htmlpack/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/*

View File

@@ -0,0 +1,19 @@
# HTMLPACK
Pack an HTML and all its JavaScript, CSS, image, fonts, and external files into a single HTML file. JavaScript and CSS is embedded in STYLE and SCRIPT tags, while all other files and images are converted to dataUri format and embedded in the document.
## Usage
```javascript
import htmlpack from '@joplin/htmlpack';
htmlpack('/path/to/input.html', '/path/to/output.html');
```
## Notes
- The script works in synchronous way so it will block the calling process while running.
- No security check on what's included.
## License
MIT

489
packages/htmlpack/package-lock.json generated Normal file
View File

@@ -0,0 +1,489 @@
{
"name": "@joplin/htmlpack",
"version": "1.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@joplin/htmlpack",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.34",
"css": "^3.0.0",
"datauri": "^4.1.0",
"fs-extra": "^10.0.0",
"html-entities": "^1.2.1"
},
"devDependencies": {
"@types/fs-extra": "^9.0.6"
}
},
"../fork-htmlparser2": {
"name": "@joplin/fork-htmlparser2",
"version": "4.1.34",
"extraneous": true,
"license": "MIT",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"entities": "^2.0.0",
"fs-extra": "^10.0.0"
},
"devDependencies": {
"@types/jest": "^25.1.3",
"@types/node": "^13.1.1",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"coveralls": "^3.0.1",
"eslint": "^6.0.0",
"eslint-config-prettier": "^6.0.0",
"jest": "^26.6.3",
"prettier": "^1.18.2",
"ts-jest": "^24.0.2",
"typescript": "^3.5.3"
}
},
"node_modules/@joplin/fork-htmlparser2": {
"version": "4.1.34",
"resolved": "https://registry.npmjs.org/@joplin/fork-htmlparser2/-/fork-htmlparser2-4.1.34.tgz",
"integrity": "sha512-1/tQZEDnI36RaEJte0eumw1/c8OhmJOpgFyW+Nxsk2u/vvcgnEvjFjauiH2ZxtO5FTJB3BMQ4M23+Y5dw2cnnw==",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"entities": "^2.0.0"
}
},
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "16.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz",
"integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==",
"dev": true
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/css": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
"dependencies": {
"inherits": "^2.0.4",
"source-map": "^0.6.1",
"source-map-resolve": "^0.6.0"
}
},
"node_modules/datauri": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/datauri/-/datauri-4.1.0.tgz",
"integrity": "sha512-y17kh32+I82G+ED9MNWFkZiP/Cq/vO1hN9+tSZsT9C9qn3NrvcBnh7crSepg0AQPge1hXx2Ca44s1FRdv0gFWA==",
"dependencies": {
"image-size": "1.0.0",
"mimer": "^2.0.2"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"engines": {
"node": ">=0.10"
}
},
"node_modules/dom-serializer": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
"entities": "^2.0.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/dom-serializer/node_modules/domhandler": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
"integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
"dependencies": {
"domelementtype": "^2.2.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
"integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
"dependencies": {
"domelementtype": "^2.0.1"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
"dependencies": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/domutils/node_modules/domhandler": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
"integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
"dependencies": {
"domelementtype": "^2.2.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg=="
},
"node_modules/html-entities": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz",
"integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA=="
},
"node_modules/image-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
"integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/mimer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/mimer/-/mimer-2.0.2.tgz",
"integrity": "sha512-izxvjsB7Ur5HrTbPu6VKTrzxSMBFBqyZQc6dWlZNQ4/wAvf886fD4lrjtFd8IQ8/WmZKdxKjUtqFFNaj3hQ52g==",
"bin": {
"mimer": "bin/mimer"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-resolve": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
"dependencies": {
"atob": "^2.1.2",
"decode-uri-component": "^0.2.0"
}
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"engines": {
"node": ">= 10.0.0"
}
}
},
"dependencies": {
"@joplin/fork-htmlparser2": {
"version": "4.1.34",
"resolved": "https://registry.npmjs.org/@joplin/fork-htmlparser2/-/fork-htmlparser2-4.1.34.tgz",
"integrity": "sha512-1/tQZEDnI36RaEJte0eumw1/c8OhmJOpgFyW+Nxsk2u/vvcgnEvjFjauiH2ZxtO5FTJB3BMQ4M23+Y5dw2cnnw==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"entities": "^2.0.0"
}
},
"@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "16.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz",
"integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==",
"dev": true
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"css": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
"requires": {
"inherits": "^2.0.4",
"source-map": "^0.6.1",
"source-map-resolve": "^0.6.0"
}
},
"datauri": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/datauri/-/datauri-4.1.0.tgz",
"integrity": "sha512-y17kh32+I82G+ED9MNWFkZiP/Cq/vO1hN9+tSZsT9C9qn3NrvcBnh7crSepg0AQPge1hXx2Ca44s1FRdv0gFWA==",
"requires": {
"image-size": "1.0.0",
"mimer": "^2.0.2"
}
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"dom-serializer": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
"entities": "^2.0.0"
},
"dependencies": {
"domhandler": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
"integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
"requires": {
"domelementtype": "^2.2.0"
}
}
}
},
"domelementtype": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
},
"domhandler": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
"integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
"requires": {
"domelementtype": "^2.0.1"
}
},
"domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
"requires": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0"
},
"dependencies": {
"domhandler": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
"integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
"requires": {
"domelementtype": "^2.2.0"
}
}
}
},
"entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
"fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg=="
},
"html-entities": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz",
"integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA=="
},
"image-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
"integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
"requires": {
"queue": "6.0.2"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"mimer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/mimer/-/mimer-2.0.2.tgz",
"integrity": "sha512-izxvjsB7Ur5HrTbPu6VKTrzxSMBFBqyZQc6dWlZNQ4/wAvf886fD4lrjtFd8IQ8/WmZKdxKjUtqFFNaj3hQ52g=="
},
"queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"requires": {
"inherits": "~2.0.3"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-resolve": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
"requires": {
"atob": "^2.1.2",
"decode-uri-component": "^0.2.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
}
}
}

View File

@@ -0,0 +1,24 @@
{
"name": "@joplin/htmlpack",
"version": "1.0.1",
"description": "Pack an HTML file and all its linked resources into a single HTML file",
"main": "dist/index.js",
"private": true,
"scripts": {
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --project tsconfig.json"
},
"author": "Laurent Czoic",
"license": "MIT",
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.35",
"css": "^3.0.0",
"datauri": "^4.1.0",
"fs-extra": "^10.0.0",
"html-entities": "^1.2.1"
},
"devDependencies": {
"@types/fs-extra": "^9.0.6"
},
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -0,0 +1,218 @@
import * as fs from 'fs-extra';
const Entities = require('html-entities').AllHtmlEntities;
const htmlparser2 = require('@joplin/fork-htmlparser2');
const Datauri = require('datauri/sync');
const cssParse = require('css/lib/parse');
const cssStringify = require('css/lib/stringify');
const selfClosingElements = [
'area',
'base',
'basefont',
'br',
'col',
'command',
'embed',
'frame',
'hr',
'img',
'input',
'isindex',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
];
const htmlentities = (s: string): string => {
const output = (new Entities()).encode(s);
return output.replace(/&Tab;/ig, '\t');
};
const dataUriEncode = (filePath: string): string => {
const result = Datauri(filePath);
return result.content;
};
const attributesHtml = (attr: any) => {
const output = [];
for (const n in attr) {
if (!attr.hasOwnProperty(n)) continue;
output.push(`${n}="${htmlentities(attr[n])}"`);
}
return output.join(' ');
};
const attrValue = (attrs: any, name: string): string => {
if (!attrs[name]) return '';
return attrs[name].toLowerCase();
};
const isSelfClosingTag = (tagName: string) => {
return selfClosingElements.includes(tagName.toLowerCase());
};
const processCssContent = (cssBaseDir: string, content: string): string => {
const o = cssParse(content, {
silent: false,
});
for (const rule of o.stylesheet.rules) {
if (rule.type === 'font-face') {
for (const declaration of rule.declarations) {
if (declaration.property === 'src') {
declaration.value = declaration.value.replace(/url\((.*?)\)/g, (_v: any, url: string) => {
const cssFilePath = `${cssBaseDir}/${url}`;
if (fs.existsSync(cssFilePath)) {
return `url(${dataUriEncode(cssFilePath)})`;
} else {
return `url(${url})`;
}
});
}
}
}
}
return cssStringify(o);
};
const processLinkTag = (baseDir: string, _name: string, attrs: any): string => {
const href = attrValue(attrs, 'href');
if (!href) return null;
const filePath = `${baseDir}/${href}`;
const content = fs.readFileSync(filePath, 'utf8');
return `<style>${processCssContent(dirname(filePath), content)}</style>`;
};
const processScriptTag = (baseDir: string, _name: string, attrs: any): string => {
const src = attrValue(attrs, 'src');
if (!src) return null;
const content = fs.readFileSync(`${baseDir}/${src}`, 'utf8');
return `<script>${htmlentities(content)}</script>`;
};
const processImgTag = (baseDir: string, _name: string, attrs: any): string => {
const src = attrValue(attrs, 'src');
if (!src) return null;
const filePath = `${baseDir}/${src}`;
if (!fs.existsSync(filePath)) return null;
const modAttrs = { ...attrs };
delete modAttrs.src;
return `<img src="${dataUriEncode(filePath)}" ${attributesHtml(modAttrs)}/>`;
};
const processAnchorTag = (baseDir: string, _name: string, attrs: any): string => {
const href = attrValue(attrs, 'href');
if (!href) return null;
const filePath = `${baseDir}/${href}`;
if (!fs.existsSync(filePath)) return null;
const modAttrs = { ...attrs };
modAttrs.href = dataUriEncode(filePath);
modAttrs.download = basename(filePath);
return `<a ${attributesHtml(modAttrs)}>`;
};
function basename(path: string) {
if (!path) throw new Error('Path is empty');
const s = path.split(/\/|\\/);
return s[s.length - 1];
}
function dirname(path: string) {
if (!path) throw new Error('Path is empty');
const s = path.split(/\/|\\/);
s.pop();
return s.join('/');
}
export default async function htmlpack(inputFile: string, outputFile: string) {
const inputHtml = await fs.readFile(inputFile, 'utf8');
const baseDir = dirname(inputFile);
const output: string[] = [];
interface Tag {
name: string;
}
const tagStack: Tag[] = [];
const currentTag = () => {
if (!tagStack.length) return { name: '', processed: false };
return tagStack[tagStack.length - 1];
};
const parser = new htmlparser2.Parser({
onopentag: (name: string, attrs: any) => {
name = name.toLowerCase();
let processedResult = '';
if (name === 'link') {
processedResult = processLinkTag(baseDir, name, attrs);
}
if (name === 'script') {
processedResult = processScriptTag(baseDir, name, attrs);
}
if (name === 'img') {
processedResult = processImgTag(baseDir, name, attrs);
}
if (name === 'a') {
processedResult = processAnchorTag(baseDir, name, attrs);
}
tagStack.push({ name });
if (processedResult) {
output.push(processedResult);
} else {
let attrHtml = attributesHtml(attrs);
if (attrHtml) attrHtml = ` ${attrHtml}`;
const closingSign = isSelfClosingTag(name) ? '/>' : '>';
output.push(`<${name}${attrHtml}${closingSign}`);
}
},
ontext: (decodedText: string) => {
if (currentTag().name === 'style') {
// For CSS, we have to put the style as-is inside the tag because if we html-entities encode
// it, it's not going to work. But it's ok because JavaScript won't run within the style tag.
// Ideally CSS should be loaded from an external file.
output.push(decodedText);
} else {
output.push(htmlentities(decodedText));
}
},
onclosetag: (name: string) => {
const current = currentTag();
if (current.name === name.toLowerCase()) tagStack.pop();
if (isSelfClosingTag(name)) return;
output.push(`</${name}>`);
},
}, { decodeEntities: true });
parser.write(inputHtml);
parser.end();
await fs.writeFile(outputFile, output.join(''), 'utf8');
}

View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist"
},
"rootDir": ".",
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"**/node_modules",
],
}

View File

@@ -790,6 +790,7 @@ export default class BaseApplication {
Setting.setValue('sync.interval', 3600);
}
Setting.setValue('sync.target', 0);
Setting.setValue('firstStart', 0);
} else {
setLocale(Setting.value('locale'));

View File

@@ -301,28 +301,38 @@ interface NoteResourceRecognition {
}
const preProcessFile = async (filePath: string): Promise<string> => {
const content: string = await shim.fsDriver().readFile(filePath, 'utf8');
// Disabled pre-processing for now because it runs out of memory:
// https://github.com/laurent22/joplin/issues/5543
//
// It could be fixed by not loading the whole file in memory, but there are
// other issues because people import 1GB+ files so pre-processing
// everything means creating a new copy of that file, and that has its own
// problems.
// The note content in an ENEX file is wrapped in a CDATA block so it means
// that any "]]>" inside the note must be somehow escaped, or else the CDATA
// block would be closed at the wrong point.
//
// The problem is that Evernote appears to encode "]]>" as "]]<![CDATA[>]]>"
// instead of the more sensible "]]&gt;", or perhaps they have nothing in
// place to properly escape data imported from their web clipper. In any
// case it results in invalid XML that Evernote cannot even import back.
//
// Handling that invalid XML with SAX would also be very tricky, so instead
// we add a pre-processing step that converts this tags to just "&gt;". It
// should be safe to do so because such content can only be within the body
// of a note - and ">" or "&gt;" is equivalent.
//
// Ref: https://discourse.joplinapp.org/t/20470/4
const newContent = content.replace(/<!\[CDATA\[>\]\]>/g, '&gt;');
if (content === newContent) return filePath;
const newFilePath = `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.enex`;
await shim.fsDriver().writeFile(newFilePath, newContent, 'utf8');
return newFilePath;
return filePath;
// const content: string = await shim.fsDriver().readFile(filePath, 'utf8');
// // The note content in an ENEX file is wrapped in a CDATA block so it means
// // that any "]]>" inside the note must be somehow escaped, or else the CDATA
// // block would be closed at the wrong point.
// //
// // The problem is that Evernote appears to encode "]]>" as "]]<![CDATA[>]]>"
// // instead of the more sensible "]]&gt;", or perhaps they have nothing in
// // place to properly escape data imported from their web clipper. In any
// // case it results in invalid XML that Evernote cannot even import back.
// //
// // Handling that invalid XML with SAX would also be very tricky, so instead
// // we add a pre-processing step that converts this tags to just "&gt;". It
// // should be safe to do so because such content can only be within the body
// // of a note - and ">" or "&gt;" is equivalent.
// //
// // Ref: https://discourse.joplinapp.org/t/20470/4
// const newContent = content.replace(/<!\[CDATA\[>\]\]>/g, '&gt;');
// if (content === newContent) return filePath;
// const newFilePath = `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.enex`;
// await shim.fsDriver().writeFile(newFilePath, newContent, 'utf8');
// return newFilePath;
};
export default async function importEnex(parentFolderId: string, filePath: string, importOptions: ImportOptions = null) {

View File

@@ -2,7 +2,7 @@ const { setupDatabaseAndSynchronizer, switchClient } = require('../testing/test-
const Folder = require('../models/Folder').default;
const Note = require('../models/Note').default;
describe('models_BaseItem', function() {
describe('models/BaseItem', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -9,7 +9,7 @@ import ResourceService from '../services/ResourceService';
const testImagePath = `${supportDir}/photo.jpg`;
describe('models_Folder.sharing', function() {
describe('models/Folder.sharing', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -9,7 +9,7 @@ async function allItems() {
return folders.concat(notes);
}
describe('models_Folder', function() {
describe('models/Folder', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -15,7 +15,7 @@ async function allItems() {
return folders.concat(notes);
}
describe('models_Note', function() {
describe('models/Note', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);

View File

@@ -3,7 +3,7 @@ const { setupDatabaseAndSynchronizer, switchClient } = require('../testing/test-
const Folder = require('../models/Folder').default;
const Note = require('../models/Note').default;
describe('models_Note_CustomSortOrder', function() {
describe('models/Note_CustomSortOrder', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);

View File

@@ -6,7 +6,7 @@ const shim = require('../shim').default;
const testImagePath = `${supportDir}/photo.jpg`;
describe('models_Resource', function() {
describe('models/Resource', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -7,7 +7,7 @@ async function loadSettingsFromFile(): Promise<any> {
return JSON.parse(await fs.readFile(Setting.settingFilePath, 'utf8'));
}
describe('models_Setting', function() {
describe('models/Setting', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
@@ -174,7 +174,7 @@ describe('models_Setting', function() {
}));
it('should not save to file if nothing has changed', (async () => {
Setting.setValue('sync.target', 9);
Setting.setValue('sync.mobileWifiOnly', true);
await Setting.saveAll();
{
@@ -182,7 +182,7 @@ describe('models_Setting', function() {
// changed.
const beforeStat = await fs.stat(Setting.settingFilePath);
await msleep(1001);
Setting.setValue('sync.target', 8);
Setting.setValue('sync.mobileWifiOnly', false);
await Setting.saveAll();
const afterStat = await fs.stat(Setting.settingFilePath);
expect(afterStat.mtime.getTime()).toBeGreaterThan(beforeStat.mtime.getTime());
@@ -191,7 +191,7 @@ describe('models_Setting', function() {
{
const beforeStat = await fs.stat(Setting.settingFilePath);
await msleep(1001);
Setting.setValue('sync.target', 8);
Setting.setValue('sync.mobileWifiOnly', false);
const afterStat = await fs.stat(Setting.settingFilePath);
await Setting.saveAll();
expect(afterStat.mtime.getTime()).toBe(beforeStat.mtime.getTime());

View File

@@ -319,7 +319,7 @@ class Setting extends BaseModel {
},
'sync.target': {
value: 0,
value: 7, // Dropbox
type: SettingItemType.Int,
isEnum: true,
public: true,

View File

@@ -3,7 +3,7 @@ const Folder = require('../models/Folder').default;
const Note = require('../models/Note').default;
const Tag = require('../models/Tag').default;
describe('models_Tag', function() {
describe('models/Tag', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "2.4.1",
"version": "2.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "2.4.1",
"version": "2.4.3",
"description": "Joplin Core library",
"author": "Laurent Cozic",
"homepage": "",
@@ -26,11 +26,11 @@
"typescript": "^4.0.5"
},
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.34",
"@joplin/fork-sax": "^1.2.38",
"@joplin/renderer": "^2.4.1",
"@joplin/turndown": "^4.0.56",
"@joplin/turndown-plugin-gfm": "^1.0.38",
"@joplin/fork-htmlparser2": "^4.1.36",
"@joplin/fork-sax": "^1.2.40",
"@joplin/renderer": "^2.4.3",
"@joplin/turndown": "^4.0.58",
"@joplin/turndown-plugin-gfm": "^1.0.40",
"async-mutex": "^0.1.3",
"aws-sdk": "^2.588.0",
"base-64": "^0.1.0",
@@ -85,5 +85,5 @@
"word-wrap": "^1.2.3",
"xml2js": "^0.4.19"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -127,6 +127,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
${assetsToHeaders(result.pluginAssets, { asHtml: true })}
<title>${escapeHtml(item.title)}</title>
</head>

View File

@@ -200,6 +200,13 @@ export default class ShareService {
return this.api().exec('GET', 'api/share_users');
}
public setProcessingShareInvitationResponse(v: boolean) {
this.store.dispatch({
type: 'SHARE_INVITATION_RESPONSE_PROCESSING',
value: v,
});
}
public async respondInvitation(shareUserId: string, accept: boolean) {
if (accept) {
await this.api().exec('PATCH', `api/share_users/${shareUserId}`, null, { status: 1 });

View File

@@ -38,6 +38,7 @@ export interface State {
shares: StateShare[];
shareUsers: Record<string, StateShareUser>;
shareInvitations: ShareInvitation[];
processingShareInvitationResponse: boolean;
}
export const stateRootKey = 'shareService';
@@ -46,6 +47,7 @@ export const defaultState: State = {
shares: [],
shareUsers: {},
shareInvitations: [],
processingShareInvitationResponse: false,
};
export function isSharedFolderOwner(state: RootState, folderId: string): boolean {
@@ -82,6 +84,11 @@ const reducer = (draftRoot: Draft<RootState>, action: any) => {
draft.shareInvitations = action.shareInvitations;
break;
case 'SHARE_INVITATION_RESPONSE_PROCESSING':
draft.processingShareInvitationResponse = action.value;
break;
}
} catch (error) {
error.message = `In share reducer: ${error.message} Action: ${JSON.stringify(action)}`;

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/plugin-repo-cli",
"version": "2.4.1",
"version": "2.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/plugin-repo-cli",
"version": "2.4.1",
"version": "2.4.3",
"description": "",
"main": "index.js",
"bin": {
@@ -19,8 +19,8 @@
"author": "",
"license": "MIT",
"dependencies": {
"@joplin/lib": "^2.4.1",
"@joplin/tools": "^2.4.1",
"@joplin/lib": "^2.4.3",
"@joplin/tools": "^2.4.3",
"fs-extra": "^9.0.1",
"gh-release-assets": "^2.0.0",
"node-fetch": "^2.6.1",
@@ -32,5 +32,6 @@
"@types/node": "^14.14.6",
"jest": "^26.6.3",
"typescript": "^4.1.3"
}
},
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/renderer",
"version": "2.4.1",
"version": "2.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/renderer",
"version": "2.4.1",
"version": "2.4.3",
"description": "The Joplin note renderer, used the mobile and desktop application",
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
"main": "index.js",
@@ -24,7 +24,7 @@
"typescript": "^4.0.5"
},
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.34",
"@joplin/fork-htmlparser2": "^4.1.36",
"font-awesome-filetypes": "^2.1.0",
"fs-extra": "^8.1.0",
"highlight.js": "^11.2.0",
@@ -48,5 +48,5 @@
"mermaid": "^8.12.1",
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@joplin/server",
"version": "2.4.9",
"version": "2.4.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@joplin/server",
"version": "2.4.9",
"version": "2.4.11",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"@koa/cors": "^3.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.4.9",
"version": "2.4.11",
"private": true,
"scripts": {
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",

View File

@@ -1,5 +1,5 @@
import { Item, Share, ShareType, ShareUser, ShareUserStatus, User, Uuid } from '../services/database/types';
import { ErrorForbidden, ErrorNotFound } from '../utils/errors';
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from '../utils/errors';
import BaseModel, { AclAction, DeleteOptions } from './BaseModel';
import { getCanShareFolder } from './utils/user';
@@ -117,6 +117,8 @@ export default class ShareUserModel extends BaseModel<ShareUser> {
const shareUser = await this.byShareAndUserId(shareId, userId);
if (!shareUser) throw new ErrorNotFound(`Item has not been shared with this user: ${shareId} / ${userId}`);
if (shareUser.status === status) throw new ErrorBadRequest(`Share ${shareId} status is already ${status}`);
const share = await this.models().share().load(shareId);
if (!share) throw new ErrorNotFound(`No such share: ${shareId}`);

View File

@@ -28,13 +28,6 @@ export default class UserItemModel extends BaseModel<UserItem> {
return false;
}
public async add(userId: Uuid, itemId: Uuid): Promise<UserItem> {
return this.save({
user_id: userId,
item_id: itemId,
});
}
public async remove(userId: Uuid, itemId: Uuid): Promise<void> {
await this.deleteByUserItem(userId, itemId);
}
@@ -59,7 +52,6 @@ export default class UserItemModel extends BaseModel<UserItem> {
.leftJoin('items', 'user_items.item_id', 'items.id')
.select(this.selectFields(options, this.defaultFields, 'user_items'))
.where('items.jop_share_id', '=', shareId);
// return this.db(this.tableName).select(this.defaultFields).where('share_id', '=', shareId);
}
public async byShareAndUserId(shareId: Uuid, userId: Uuid, options: LoadOptions = {}): Promise<UserItem[]> {
@@ -69,10 +61,6 @@ export default class UserItemModel extends BaseModel<UserItem> {
.select(this.selectFields(options, this.defaultFields, 'user_items'))
.where('items.jop_share_id', '=', shareId)
.where('user_items.user_id', '=', userId);
// return this.db(this.tableName).select(this.defaultFields)
// .where('share_id', '=', shareId)
// .where('user_id', '=', userId);
}
public async byUserId(userId: Uuid): Promise<UserItem[]> {
@@ -91,7 +79,6 @@ export default class UserItemModel extends BaseModel<UserItem> {
.select(this.selectFields(options, this.defaultFields, 'user_items'))
.where('items.jop_share_id', '!=', '')
.where('user_items.user_id', '=', userId);
// return this.db(this.tableName).select(this.defaultFields).where('share_id', '!=', '').where('user_id', '=', userId);
}
public async deleteByUserItem(userId: Uuid, itemId: Uuid): Promise<void> {
@@ -124,6 +111,11 @@ export default class UserItemModel extends BaseModel<UserItem> {
await this.deleteBy({ byShareId: shareId, byUserId: userId });
}
public async add(userId: Uuid, itemId: Uuid, options: SaveOptions = {}): Promise<void> {
const item = await this.models().item().load(itemId, { fields: ['id', 'name'] });
await this.addMulti(userId, [item], options);
}
public async addMulti(userId: Uuid, itemsQuery: Knex.QueryBuilder | Item[], options: SaveOptions = {}): Promise<void> {
const items: Item[] = Array.isArray(itemsQuery) ? itemsQuery : await itemsQuery.whereNotIn('id', this.db('user_items').select('item_id').where('user_id', '=', userId));
if (!items.length) return;
@@ -151,11 +143,8 @@ export default class UserItemModel extends BaseModel<UserItem> {
}, 'UserItemModel::addMulti');
}
public async save(userItem: UserItem, options: SaveOptions = {}): Promise<UserItem> {
if (userItem.id) throw new Error('User items cannot be modified (only created or deleted)'); // Sanity check - shouldn't happen
const item = await this.models().item().load(userItem.item_id, { fields: ['id', 'name'] });
await this.addMulti(userItem.user_id, [item], options);
return this.byUserAndItemId(userItem.user_id, item.id);
public async save(_userItem: UserItem, _options: SaveOptions = {}): Promise<UserItem> {
throw new Error('Call add() or addMulti()');
}
public async delete(_id: string | string[], _options: DeleteOptions = {}): Promise<void> {

View File

@@ -1,8 +1,8 @@
import { ShareType, ShareUserStatus } from '../../services/database/types';
import { beforeAllDb, afterAllTests, beforeEachDb, createUserAndSession, models, createItemTree, expectHttpError } from '../../utils/testing/testUtils';
import { getApi, patchApi } from '../../utils/testing/apiUtils';
import { shareWithUserAndAccept } from '../../utils/testing/shareApiUtils';
import { ErrorForbidden } from '../../utils/errors';
import { shareFolderWithUser, shareWithUserAndAccept } from '../../utils/testing/shareApiUtils';
import { ErrorBadRequest, ErrorForbidden } from '../../utils/errors';
import { PaginatedResults } from '../../models/utils/pagination';
describe('share_users', function() {
@@ -53,4 +53,17 @@ describe('share_users', function() {
await expectHttpError(async () => patchApi(session1.id, `share_users/${shareUser.id}`, { status: ShareUserStatus.Accepted }), ErrorForbidden.httpCode);
});
test('should not allow accepting a share twice or more', async function() {
const { session: session1 } = await createUserAndSession(1);
const { session: session2 } = await createUserAndSession(2);
const { shareUser } = await shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', {
'000000000000000000000000000000F1': {
'00000000000000000000000000000001': null,
},
});
await expectHttpError(async () => patchApi(session2.id, `share_users/${shareUser.id}`, { status: ShareUserStatus.Accepted }), ErrorBadRequest.httpCode);
});
});

View File

@@ -86,6 +86,8 @@ export const postHandlers: PostHandlers = {
};
if (fields.coupon) {
delete checkoutSession.allow_promotion_codes;
checkoutSession.discounts = [
{
coupon: fields.coupon.trim(),

View File

@@ -1118,7 +1118,7 @@ msgstr "请升级 Joplin 以使用此插件"
msgid ""
"The Joplin team has vetted this plugin and it meets our standards for "
"security and performance."
msgstr ""
msgstr "Joplin 团队已经通过了该插件的审查,它符合我们对于安全和性能的要求。"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js:192
#, javascript-format
@@ -1144,9 +1144,8 @@ msgid "You do not have any installed plugin."
msgstr "您尚未安装任何插件。"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:232
#, fuzzy
msgid "Could not connect to plugin repository."
msgstr "无法连接到插件库"
msgstr "无法连接到插件库"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:236
msgid "Try again"
@@ -1395,9 +1394,8 @@ msgstr "加密状态:"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js:103
#: packages/app-mobile/components/screens/encryption-config.js:89
#, fuzzy
msgid "Master password"
msgstr "输入主密码"
msgstr "主密码"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js:118
msgid "Source: "
@@ -1442,20 +1440,17 @@ msgstr "动作"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js:166
#: packages/app-mobile/components/screens/encryption-config.js:159
#, fuzzy
msgid "Master password:"
msgstr "输入主密码:"
msgstr "主密码:"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js:170
#: packages/app-mobile/components/screens/encryption-config.js:160
#, fuzzy
msgid "Loaded"
msgstr "已载"
msgstr "已载"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js:178
#, fuzzy
msgid "Enter your master password"
msgstr "输入主密码"
msgstr "输入主密码"
#: packages/app-desktop/gui/ExtensionBadge.min.js:10
msgid "Firefox Extension"
@@ -4224,7 +4219,7 @@ msgid "attachment"
msgstr "附件"
#: packages/server/dist/models/UserModel.js:199
#, fuzzy, javascript-format
#, javascript-format
msgid "Cannot save %s \"%s\" because it is larger than the allowed limit (%s)"
msgstr "无法保存 %s “%s”,因为超过了允许的限制大小(%s)。"

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/tools",
"version": "2.4.1",
"version": "2.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/tools",
"version": "2.4.1",
"version": "2.4.3",
"description": "Various tools for Joplin",
"main": "index.js",
"author": "Laurent Cozic",
@@ -20,8 +20,8 @@
},
"license": "MIT",
"dependencies": {
"@joplin/lib": "^2.4.1",
"@joplin/renderer": "^2.4.1",
"@joplin/lib": "^2.4.3",
"@joplin/renderer": "^2.4.3",
"execa": "^4.1.0",
"fs-extra": "^4.0.3",
"gettext-parser": "^1.3.0",
@@ -41,14 +41,14 @@
"devDependencies": {
"@rmp135/sql-ts": "^1.6.0",
"@types/fs-extra": "^9.0.6",
"@types/jest": "^26.0.15",
"@types/mustache": "^0.8.32",
"@types/node": "^14.14.6",
"@types/jest": "^26.0.15",
"jest": "^26.6.3",
"gulp": "^4.0.2",
"jest": "^26.6.3",
"sass": "^1.39.2",
"sqlite3": "^5.0.0",
"typescript": "^4.1.3"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/turndown-plugin-gfm",
"version": "1.0.38",
"version": "1.0.40",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -4,7 +4,7 @@
"publishConfig": {
"access": "public"
},
"version": "1.0.38",
"version": "1.0.40",
"author": "Dom Christie",
"main": "lib/turndown-plugin-gfm.cjs.js",
"devDependencies": {
@@ -41,5 +41,5 @@
"build-test": "browserify test/turndown-plugin-gfm-test.js --outfile test/turndown-plugin-gfm-test.browser.js",
"prepare": "npm run build"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/turndown",
"version": "4.0.56",
"version": "4.0.58",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,7 +1,7 @@
{
"name": "@joplin/turndown",
"description": "A library that converts HTML to Markdown",
"version": "4.0.56",
"version": "4.0.58",
"author": "Dom Christie",
"main": "lib/turndown.cjs.js",
"publishConfig": {
@@ -48,5 +48,5 @@
"build-test": "browserify test/turndown-test.js --outfile test/turndown-test.browser.js",
"prepare": "npm run build"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
}

View File

@@ -1,5 +1,9 @@
# Joplin Android app changelog
## [android-v2.4.3](https://github.com/laurent22/joplin/releases/tag/android-v2.4.3) - 2021-09-29T18:47:24Z
- Fixed: Fix default sync target (4b39d30)
## [android-v2.4.2](https://github.com/laurent22/joplin/releases/tag/android-v2.4.2) (Pre-release) - 2021-09-22T17:02:37Z
- Improved: Allow disabling any master key, including default or active one (9407efd)

View File

@@ -1,5 +1,16 @@
# Joplin terminal app changelog
## [cli-v2.4.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.4.1) - 2021-09-29T15:28:01Z
- New: Add a way to disable a master key (7faa58e)
- New: Add support for single master password, to simplify handling of multiple encryption keys (ce89ee5)
- New: Added "None" sync target to allow disabling synchronisation (f5f05e6)
- Improved: Allow importing certain corrupted ENEX files (f144dae)
- Improved: Improved sync locks so that they do not prevent upgrading a sync target (06ed58b)
- Fixed: Fixed file paths when exporting as HTML (#5325)
- Fixed: Misinterpreted search term after filter in quotation marks (#5445) (#5444 by [@JackGruber](https://github.com/JackGruber))
- Fixed: Setting note contents using "set" command does not update note timestamp (#5435)
## [cli-v2.3.2](https://github.com/laurent22/joplin/releases/tag/cli-v2.3.2) - 2021-08-16T09:38:40Z
- Improved: Improved E2EE usability by making its state a property of the sync target (#5276)

View File

@@ -1,5 +1,15 @@
# Joplin Server Changelog
## [server-v2.4.11-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.11-beta) - 2021-09-26T17:10:57Z
- Improved: Do not allow accepting share more than once (57a1d03)
- Fixed: Fixed Stripe checkout when a coupon is used (c45f961)
## [server-v2.4.10-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.10-beta) (Pre-release) - 2021-09-25T19:07:05Z
- Improved: Improved share service reliability and optimised performance (0175348)
- Security: Implement clickjacking defense (e3fd34e)
## [server-v2.4.9](https://github.com/laurent22/joplin/releases/tag/server-v2.4.9-beta) - 2021-09-22T16:31:23Z
- New: Add support for changing user own email (63e88c0)