You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Added support for checkboxes and fixed various issues with WYSIWYG editor
This commit is contained in:
		| @@ -21,38 +21,40 @@ Clipper/content_scripts/Readability.js | ||||
| Clipper/dist | ||||
| Clipper/icons | ||||
| Clipper/popup/build | ||||
| Clipper/popup/config/webpack.config.js | ||||
| Clipper/popup/node_modules | ||||
| Clipper/popup/scripts/build.js | ||||
| docs/ | ||||
| ElectronClient/dist | ||||
| ElectronClient/gui/editors/TinyMCE/plugins/lists.js | ||||
| ElectronClient/lib | ||||
| ElectronClient/lib/vendor/sjcl-rn.js | ||||
| ElectronClient/lib/vendor/sjcl.js | ||||
| ElectronClient/locales | ||||
| ElectronClient/node_modules | ||||
| ElectronClient/packageInfo.js | ||||
| highlight.pack.js | ||||
| Modules/TinyMCE/JoplinLists/ | ||||
| node_modules/ | ||||
| ReactNativeClient/android | ||||
| ReactNativeClient/ios | ||||
| ReactNativeClient/lib/joplin-renderer/assets/ | ||||
| ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js | ||||
| ReactNativeClient/lib/rnInjectedJs/ | ||||
| ReactNativeClient/lib/vendor/ | ||||
| ReactNativeClient/lib/welcomeAssets.js | ||||
| ReactNativeClient/locales | ||||
| ReactNativeClient/node_modules | ||||
| ReactNativeClient/pluginAssets/ | ||||
| readme/ | ||||
| Tools/node_modules | ||||
| Tools/PortableAppsLauncher | ||||
| Server/.git/ | ||||
| Server/.github/ | ||||
| Server/docs/ | ||||
| Server/dist/ | ||||
| Server/bin/ | ||||
| Server/dist/ | ||||
| Server/docs/ | ||||
| Server/node_modules/ | ||||
| ElectronClient/packageInfo.js | ||||
| ReactNativeClient/pluginAssets/ | ||||
| ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js | ||||
| ReactNativeClient/lib/joplin-renderer/assets/ | ||||
| ReactNativeClient/lib/rnInjectedJs/ | ||||
| Clipper/popup/config/webpack.config.js | ||||
| Clipper/popup/scripts/build.js | ||||
| Tools/node_modules | ||||
| Tools/PortableAppsLauncher | ||||
|  | ||||
| # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD | ||||
| ElectronClient/gui/editors/PlainEditor.js | ||||
| @@ -64,6 +66,7 @@ ElectronClient/gui/ResourceScreen.js | ||||
| ElectronClient/gui/ShareNoteDialog.js | ||||
| ElectronClient/gui/utils/NoteText.js | ||||
| ReactNativeClient/lib/AsyncActionQueue.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -59,6 +59,7 @@ ElectronClient/gui/ResourceScreen.js | ||||
| ElectronClient/gui/ShareNoteDialog.js | ||||
| ElectronClient/gui/utils/NoteText.js | ||||
| ReactNativeClient/lib/AsyncActionQueue.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js | ||||
| ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js | ||||
|   | ||||
							
								
								
									
										189
									
								
								CliClient/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										189
									
								
								CliClient/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -33,9 +33,9 @@ | ||||
|       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" | ||||
|     }, | ||||
|     "acorn": { | ||||
|       "version": "5.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", | ||||
|       "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" | ||||
|       "version": "7.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", | ||||
|       "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" | ||||
|     }, | ||||
|     "acorn-globals": { | ||||
|       "version": "4.3.4", | ||||
| @@ -47,9 +47,9 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "acorn": { | ||||
|           "version": "6.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", | ||||
|           "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==" | ||||
|           "version": "6.4.1", | ||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", | ||||
|           "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @@ -344,11 +344,6 @@ | ||||
|       "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "async-limiter": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", | ||||
|       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" | ||||
|     }, | ||||
|     "async-mutex": { | ||||
|       "version": "0.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.3.tgz", | ||||
| @@ -574,9 +569,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "browser-process-hrtime": { | ||||
|       "version": "0.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", | ||||
|       "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", | ||||
|       "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" | ||||
|     }, | ||||
|     "buffer-equal": { | ||||
|       "version": "1.0.0", | ||||
| @@ -1161,16 +1156,23 @@ | ||||
|       } | ||||
|     }, | ||||
|     "cssom": { | ||||
|       "version": "0.3.8", | ||||
|       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", | ||||
|       "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" | ||||
|       "version": "0.4.4", | ||||
|       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", | ||||
|       "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" | ||||
|     }, | ||||
|     "cssstyle": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", | ||||
|       "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz", | ||||
|       "integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==", | ||||
|       "requires": { | ||||
|         "cssom": "0.3.x" | ||||
|         "cssom": "~0.3.6" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "cssom": { | ||||
|           "version": "0.3.8", | ||||
|           "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", | ||||
|           "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "cwise-compiler": { | ||||
| @@ -1207,18 +1209,6 @@ | ||||
|         "abab": "^2.0.0", | ||||
|         "whatwg-mimetype": "^2.2.0", | ||||
|         "whatwg-url": "^7.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "whatwg-url": { | ||||
|           "version": "7.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", | ||||
|           "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", | ||||
|           "requires": { | ||||
|             "lodash.sortby": "^4.7.0", | ||||
|             "tr46": "^1.0.1", | ||||
|             "webidl-conversions": "^4.0.2" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "debug": { | ||||
| @@ -3448,6 +3438,11 @@ | ||||
|       "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", | ||||
|       "integrity": "sha1-ge9X/l0FgUzVjCSDYyqZwwoOgIc=" | ||||
|     }, | ||||
|     "ip-regex": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", | ||||
|       "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" | ||||
|     }, | ||||
|     "is-absolute": { | ||||
|       "version": "0.2.6", | ||||
|       "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", | ||||
| @@ -3735,13 +3730,13 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "joplin-turndown": { | ||||
|       "version": "4.0.23", | ||||
|       "resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.23.tgz", | ||||
|       "integrity": "sha512-Dh93R7G/S/KRbOu4/+FIxoUcUDcoUL4QDsqGhperOi/cUxUeg8fngrmEzdP8kEpQzqm5+8jkq9Cc1w6695owpQ==", | ||||
|       "version": "4.0.24", | ||||
|       "resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.24.tgz", | ||||
|       "integrity": "sha512-mKd2rAFzJKnhTVjEpHomG+T01//uz5rXVSAOYRh3/JKXpY7QUhVp8jCmFfO+kaadrLABTz04mvTmyyoOadxdTA==", | ||||
|       "requires": { | ||||
|         "css": "^2.2.4", | ||||
|         "html-entities": "^1.2.1", | ||||
|         "jsdom": "^11.9.0" | ||||
|         "jsdom": "^15.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "joplin-turndown-plugin-gfm": { | ||||
| @@ -3766,35 +3761,35 @@ | ||||
|       "optional": true | ||||
|     }, | ||||
|     "jsdom": { | ||||
|       "version": "11.12.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", | ||||
|       "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", | ||||
|       "version": "15.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", | ||||
|       "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", | ||||
|       "requires": { | ||||
|         "abab": "^2.0.0", | ||||
|         "acorn": "^5.5.3", | ||||
|         "acorn-globals": "^4.1.0", | ||||
|         "acorn": "^7.1.0", | ||||
|         "acorn-globals": "^4.3.2", | ||||
|         "array-equal": "^1.0.0", | ||||
|         "cssom": ">= 0.3.2 < 0.4.0", | ||||
|         "cssstyle": "^1.0.0", | ||||
|         "data-urls": "^1.0.0", | ||||
|         "cssom": "^0.4.1", | ||||
|         "cssstyle": "^2.0.0", | ||||
|         "data-urls": "^1.1.0", | ||||
|         "domexception": "^1.0.1", | ||||
|         "escodegen": "^1.9.1", | ||||
|         "escodegen": "^1.11.1", | ||||
|         "html-encoding-sniffer": "^1.0.2", | ||||
|         "left-pad": "^1.3.0", | ||||
|         "nwsapi": "^2.0.7", | ||||
|         "parse5": "4.0.0", | ||||
|         "nwsapi": "^2.2.0", | ||||
|         "parse5": "5.1.0", | ||||
|         "pn": "^1.1.0", | ||||
|         "request": "^2.87.0", | ||||
|         "request-promise-native": "^1.0.5", | ||||
|         "sax": "^1.2.4", | ||||
|         "request": "^2.88.0", | ||||
|         "request-promise-native": "^1.0.7", | ||||
|         "saxes": "^3.1.9", | ||||
|         "symbol-tree": "^3.2.2", | ||||
|         "tough-cookie": "^2.3.4", | ||||
|         "tough-cookie": "^3.0.1", | ||||
|         "w3c-hr-time": "^1.0.1", | ||||
|         "w3c-xmlserializer": "^1.1.2", | ||||
|         "webidl-conversions": "^4.0.2", | ||||
|         "whatwg-encoding": "^1.0.3", | ||||
|         "whatwg-mimetype": "^2.1.0", | ||||
|         "whatwg-url": "^6.4.1", | ||||
|         "ws": "^5.2.0", | ||||
|         "whatwg-encoding": "^1.0.5", | ||||
|         "whatwg-mimetype": "^2.3.0", | ||||
|         "whatwg-url": "^7.0.0", | ||||
|         "ws": "^7.0.0", | ||||
|         "xml-name-validator": "^3.0.0" | ||||
|       } | ||||
|     }, | ||||
| @@ -3935,11 +3930,6 @@ | ||||
|         "flush-write-stream": "^1.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "left-pad": { | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", | ||||
|       "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" | ||||
|     }, | ||||
|     "levenshtein": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/levenshtein/-/levenshtein-1.0.5.tgz", | ||||
| @@ -4999,9 +4989,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "parse5": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", | ||||
|       "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" | ||||
|       "version": "5.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", | ||||
|       "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" | ||||
|     }, | ||||
|     "pascalcase": { | ||||
|       "version": "0.1.1", | ||||
| @@ -5693,6 +5683,22 @@ | ||||
|         "request-promise-core": "1.1.3", | ||||
|         "stealthy-require": "^1.1.1", | ||||
|         "tough-cookie": "^2.3.3" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "punycode": { | ||||
|           "version": "2.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", | ||||
|           "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" | ||||
|         }, | ||||
|         "tough-cookie": { | ||||
|           "version": "2.5.0", | ||||
|           "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", | ||||
|           "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", | ||||
|           "requires": { | ||||
|             "psl": "^1.1.28", | ||||
|             "punycode": "^2.1.1" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "require-directory": { | ||||
| @@ -5786,6 +5792,14 @@ | ||||
|       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", | ||||
|       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" | ||||
|     }, | ||||
|     "saxes": { | ||||
|       "version": "3.1.11", | ||||
|       "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", | ||||
|       "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", | ||||
|       "requires": { | ||||
|         "xmlchars": "^2.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "semver": { | ||||
|       "version": "5.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", | ||||
| @@ -6699,10 +6713,11 @@ | ||||
|       "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" | ||||
|     }, | ||||
|     "tough-cookie": { | ||||
|       "version": "2.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", | ||||
|       "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", | ||||
|       "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", | ||||
|       "requires": { | ||||
|         "ip-regex": "^2.1.0", | ||||
|         "psl": "^1.1.28", | ||||
|         "punycode": "^2.1.1" | ||||
|       }, | ||||
| @@ -7118,11 +7133,21 @@ | ||||
|       } | ||||
|     }, | ||||
|     "w3c-hr-time": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", | ||||
|       "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", | ||||
|       "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", | ||||
|       "requires": { | ||||
|         "browser-process-hrtime": "^0.1.2" | ||||
|         "browser-process-hrtime": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "w3c-xmlserializer": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", | ||||
|       "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", | ||||
|       "requires": { | ||||
|         "domexception": "^1.0.1", | ||||
|         "webidl-conversions": "^4.0.2", | ||||
|         "xml-name-validator": "^3.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "webidl-conversions": { | ||||
| @@ -7154,9 +7179,9 @@ | ||||
|       "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" | ||||
|     }, | ||||
|     "whatwg-url": { | ||||
|       "version": "6.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", | ||||
|       "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", | ||||
|       "version": "7.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", | ||||
|       "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", | ||||
|       "requires": { | ||||
|         "lodash.sortby": "^4.7.0", | ||||
|         "tr46": "^1.0.1", | ||||
| @@ -7237,12 +7262,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "ws": { | ||||
|       "version": "5.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", | ||||
|       "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", | ||||
|       "requires": { | ||||
|         "async-limiter": "~1.0.0" | ||||
|       } | ||||
|       "version": "7.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", | ||||
|       "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" | ||||
|     }, | ||||
|     "xdg-basedir": { | ||||
|       "version": "3.0.0", | ||||
| @@ -7268,6 +7290,11 @@ | ||||
|       "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", | ||||
|       "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=" | ||||
|     }, | ||||
|     "xmlchars": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", | ||||
|       "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" | ||||
|     }, | ||||
|     "xtend": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", | ||||
|   | ||||
| @@ -55,7 +55,7 @@ | ||||
|     "htmlparser2": "^4.1.0", | ||||
|     "image-data-uri": "^2.0.0", | ||||
|     "image-type": "^3.0.0", | ||||
|     "joplin-turndown": "^4.0.23", | ||||
|     "joplin-turndown": "^4.0.24", | ||||
|     "joplin-turndown-plugin-gfm": "^1.0.12", | ||||
|     "json-stringify-safe": "^5.0.1", | ||||
|     "jssha": "^2.3.0", | ||||
|   | ||||
| @@ -12,6 +12,7 @@ const BaseModel = require('lib/BaseModel.js'); | ||||
| const { shim } = require('lib/shim'); | ||||
| const MdToHtml = require('lib/joplin-renderer/MdToHtml'); | ||||
| const { enexXmlToMd } = require('lib/import-enex-md-gen.js'); | ||||
| const { themeStyle } = require('../../ElectronClient/theme.js'); | ||||
|  | ||||
| jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit | ||||
|  | ||||
| @@ -19,6 +20,18 @@ process.on('unhandledRejection', (reason, p) => { | ||||
| 	console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); | ||||
| }); | ||||
|  | ||||
| function newTestMdToHtml(options = null) { | ||||
| 	options = { | ||||
| 		ResourceModel: { | ||||
| 			isResourceUrl: () => false, | ||||
| 		}, | ||||
| 		fsDriver: shim.fsDriver(), | ||||
| 		...options, | ||||
| 	}; | ||||
|  | ||||
| 	return  new MdToHtml(options); | ||||
| } | ||||
|  | ||||
| describe('MdToHtml', function() { | ||||
|  | ||||
| 	beforeEach(async (done) => { | ||||
| @@ -30,11 +43,7 @@ describe('MdToHtml', function() { | ||||
| 	it('should convert from Markdown to Html', asyncTest(async () => { | ||||
| 		const basePath = `${__dirname}/md_to_html`; | ||||
| 		const files = await shim.fsDriver().readDirStats(basePath); | ||||
| 		const mdToHtml = new MdToHtml({ | ||||
| 			ResourceModel: { | ||||
| 				isResourceUrl: () => false, | ||||
| 			}, | ||||
| 		}); | ||||
| 		const mdToHtml = newTestMdToHtml(); | ||||
|  | ||||
| 		for (let i = 0; i < files.length; i++) { | ||||
| 			const mdFilename = files[i].path; | ||||
| @@ -49,6 +58,14 @@ describe('MdToHtml', function() { | ||||
| 				bodyOnly: true, | ||||
| 			}; | ||||
|  | ||||
| 			if (mdFilename === 'checkbox_alternative.md') { | ||||
| 				mdToHtmlOptions.plugins = { | ||||
| 					checkbox: { | ||||
| 						renderingType: 2, | ||||
| 					}, | ||||
| 				}; | ||||
| 			} | ||||
|  | ||||
| 			const markdown = await shim.fsDriver().readFile(mdFilePath); | ||||
| 			let expectedHtml = await shim.fsDriver().readFile(htmlPath); | ||||
|  | ||||
| @@ -80,12 +97,61 @@ describe('MdToHtml', function() { | ||||
| 		} | ||||
| 	})); | ||||
|  | ||||
| 	// it('should write CSS to an external file', asyncTest(async () => { | ||||
| 	// 	const mdToHtml = new MdToHtml({ | ||||
| 	// 		fsDriver: shim.fsDriver(), | ||||
| 	// 		tempDir: Setting.value('tempDir'), | ||||
| 	// 	}); | ||||
| 	it('should return enabled plugin assets', asyncTest(async () => { | ||||
| 		const pluginOptions = {}; | ||||
| 		const pluginNames = MdToHtml.pluginNames(); | ||||
|  | ||||
| 		for (const n of pluginNames) pluginOptions[n] = { enabled: false }; | ||||
|  | ||||
| 		{ | ||||
| 			const mdToHtml = newTestMdToHtml({ pluginOptions: pluginOptions }); | ||||
| 			const assets = await mdToHtml.allAssets(themeStyle(1)); | ||||
| 			expect(assets.length).toBe(1); // Base note style should always be returned | ||||
| 		} | ||||
|  | ||||
| 		{ | ||||
| 			pluginOptions['checkbox'].enabled = true; | ||||
| 			const mdToHtml = newTestMdToHtml({ pluginOptions: pluginOptions }); | ||||
|  | ||||
| 			const assets = await mdToHtml.allAssets(themeStyle(1)); | ||||
| 			expect(assets.length).toBe(2); | ||||
| 			expect(assets[1].mime).toBe('text/css'); | ||||
|  | ||||
| 			const content = await shim.fsDriver().readFile(assets[1].path); | ||||
| 			expect(content.indexOf('joplin-checklist') >= 0).toBe(true); | ||||
| 		} | ||||
| 	})); | ||||
|  | ||||
| 	it('should wrapped the rendered Markdown', asyncTest(async () => { | ||||
| 		const mdToHtml = newTestMdToHtml(); | ||||
|  | ||||
| 		// In this case, the HTML contains both the style and | ||||
| 		// the rendered markdown wrapped in a DIV. | ||||
| 		const result = await mdToHtml.render('just **testing**'); | ||||
| 		expect(result.cssStrings.length).toBe(0); | ||||
| 		expect(result.html.indexOf('rendered-md') >= 0).toBe(true); | ||||
| 	})); | ||||
|  | ||||
| 	it('should return the rendered body only', asyncTest(async () => { | ||||
| 		const mdToHtml = newTestMdToHtml(); | ||||
|  | ||||
| 		// In this case, the HTML contains only the rendered markdown, | ||||
| 		// with no wrapper and no style. | ||||
| 		// The style is instead in the cssStrings property. | ||||
| 		const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true }); | ||||
| 		expect(result.cssStrings.length).toBe(1); | ||||
| 		expect(result.html.trim()).toBe('<p>just <strong>testing</strong></p>'); | ||||
| 	})); | ||||
|  | ||||
| 	it('should split HTML and CSS', asyncTest(async () => { | ||||
| 		const mdToHtml = newTestMdToHtml(); | ||||
|  | ||||
| 		// It is similar to the bodyOnly option, excepts that | ||||
| 		// the rendered Markdown is wrapped in a DIV | ||||
| 		const result = await mdToHtml.render('just **testing**', null, { splitted: true }); | ||||
| 		expect(result.cssStrings.length).toBe(1); | ||||
| 		expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>'); | ||||
| 	})); | ||||
|  | ||||
| 	// })); | ||||
|  | ||||
| }); | ||||
|   | ||||
| @@ -1,53 +0,0 @@ | ||||
| <ul> | ||||
| <li class="md-checkbox joplin-checkbox"><div class="checkbox-wrapper"><input type="checkbox" id="md-checkbox-7" onclick=" | ||||
| 		try { | ||||
| 			if (this.checked) { | ||||
| 				this.setAttribute('checked', 'checked'); | ||||
| 			} else { | ||||
| 				this.removeAttribute('checked'); | ||||
| 			} | ||||
|  | ||||
| 			ipcProxySendToHost('checkboxclick:checked:0'); | ||||
| 			const label = document.getElementById("cb-label-md-checkbox-7"); | ||||
| 			label.classList.remove(this.checked ? 'checkbox-label-unchecked' : 'checkbox-label-checked'); | ||||
| 			label.classList.add(this.checked ? 'checkbox-label-checked' : 'checkbox-label-unchecked'); | ||||
| 		} catch (error) { | ||||
| 			console.warn('Checkbox checked:0 error', error); | ||||
| 		} | ||||
| 		return true; | ||||
| 	" checked="checked"><label id="cb-label-md-checkbox-7" for="md-checkbox-7" class="checkbox-label-checked">one</label></div></li> | ||||
| <li class="md-checkbox joplin-checkbox"><div class="checkbox-wrapper"><input type="checkbox" id="md-checkbox-8" onclick=" | ||||
| 		try { | ||||
| 			if (this.checked) { | ||||
| 				this.setAttribute('checked', 'checked'); | ||||
| 			} else { | ||||
| 				this.removeAttribute('checked'); | ||||
| 			} | ||||
|  | ||||
| 			ipcProxySendToHost('checkboxclick:unchecked:1'); | ||||
| 			const label = document.getElementById("cb-label-md-checkbox-8"); | ||||
| 			label.classList.remove(this.checked ? 'checkbox-label-unchecked' : 'checkbox-label-checked'); | ||||
| 			label.classList.add(this.checked ? 'checkbox-label-checked' : 'checkbox-label-unchecked'); | ||||
| 		} catch (error) { | ||||
| 			console.warn('Checkbox unchecked:1 error', error); | ||||
| 		} | ||||
| 		return true; | ||||
| 	"><label id="cb-label-md-checkbox-8" for="md-checkbox-8" class="checkbox-label-unchecked">two</label></div></li> | ||||
| <li class="md-checkbox joplin-checkbox"><div class="checkbox-wrapper"><input type="checkbox" id="md-checkbox-9" onclick=" | ||||
| 		try { | ||||
| 			if (this.checked) { | ||||
| 				this.setAttribute('checked', 'checked'); | ||||
| 			} else { | ||||
| 				this.removeAttribute('checked'); | ||||
| 			} | ||||
|  | ||||
| 			ipcProxySendToHost('checkboxclick:unchecked:2'); | ||||
| 			const label = document.getElementById("cb-label-md-checkbox-9"); | ||||
| 			label.classList.remove(this.checked ? 'checkbox-label-unchecked' : 'checkbox-label-checked'); | ||||
| 			label.classList.add(this.checked ? 'checkbox-label-checked' : 'checkbox-label-unchecked'); | ||||
| 		} catch (error) { | ||||
| 			console.warn('Checkbox unchecked:2 error', error); | ||||
| 		} | ||||
| 		return true; | ||||
| 	"><label id="cb-label-md-checkbox-9" for="md-checkbox-9" class="checkbox-label-unchecked">with <strong>bold</strong> text</label></div></li> | ||||
| </ul> | ||||
| @@ -1,3 +0,0 @@ | ||||
| - [x] one | ||||
| - [ ] two | ||||
| - [ ] with **bold** text | ||||
							
								
								
									
										9
									
								
								CliClient/tests/html_to_md/joplin_checkboxes_2.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								CliClient/tests/html_to_md/joplin_checkboxes_2.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <ul class="joplin-checklist"> | ||||
| <li>Not checked</li> | ||||
| <li class="checked">Checked!! | ||||
| <ul class="joplin-checklist"> | ||||
| <li class="checked">Indented, with <strong>bold</strong></li> | ||||
| <li>Indented, not checked</li> | ||||
| </ul> | ||||
| </li> | ||||
| </ul> | ||||
							
								
								
									
										4
									
								
								CliClient/tests/html_to_md/joplin_checkboxes_2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								CliClient/tests/html_to_md/joplin_checkboxes_2.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| - [ ] Not checked | ||||
| - [x] Checked!! | ||||
|     - [x] Indented, with **bold** | ||||
|     - [ ] Indented, not checked | ||||
| @@ -1,4 +1,4 @@ | ||||
| _Block formulas_ are surrounded by double dollar signs. For example, `$$x = \frac{-b \pm \sqrt{b^2 - 4ac} }{2a}$$` renders, _on a separate line_, as | ||||
| *Block formulas* are surrounded by double dollar signs. For example, `$$x = \frac{-b \pm \sqrt{b^2 - 4ac} }{2a}$$` renders, *on a separate line*, as | ||||
|  | ||||
| $$ | ||||
| x = \frac{-b \pm \sqrt{b^2 - 4ac} }{2a}. | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| _Inline formulas_ are surrounded by single dollar signs. For example, `$f(x) = ax^2 + bx + c$` renders as $f(x)=ax^2+bx+c$. | ||||
| *Inline formulas* are surrounded by single dollar signs. For example, `$f(x) = ax^2 + bx + c$` renders as $f(x)=ax^2+bx+c$. | ||||
							
								
								
									
										9
									
								
								CliClient/tests/md_to_html/checkbox_alternative.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								CliClient/tests/md_to_html/checkbox_alternative.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <ul class="joplin-checklist"> | ||||
| <li>Not checked</li> | ||||
| <li class="checked">Checked!! | ||||
| <ul class="joplin-checklist"> | ||||
| <li class="checked">Indented, with <strong>bold</strong></li> | ||||
| <li>Indented, not checked</li> | ||||
| </ul> | ||||
| </li> | ||||
| </ul> | ||||
							
								
								
									
										4
									
								
								CliClient/tests/md_to_html/checkbox_alternative.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								CliClient/tests/md_to_html/checkbox_alternative.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| - [ ] Not checked | ||||
| - [x] Checked!! | ||||
|     - [x] Indented, with **bold** | ||||
|     - [ ] Indented, not checked | ||||
| @@ -348,6 +348,32 @@ function NoteText2(props:NoteTextProps) { | ||||
| 		return result; | ||||
| 	}, [props.theme]); | ||||
|  | ||||
| 	const allAssets = useCallback(async (markupLanguage:number):Promise<any[]> => { | ||||
| 		const theme = themeStyle(props.theme); | ||||
|  | ||||
| 		const markupToHtml = markupLanguageUtils.newMarkupToHtml({ | ||||
| 			resourceBaseUrl: `file://${Setting.value('resourceDir')}/`, | ||||
| 		}); | ||||
|  | ||||
| 		return markupToHtml.allAssets(markupLanguage, theme); | ||||
| 	}, [props.theme]); | ||||
|  | ||||
| 	const joplinHtml = useCallback(async (type:string) => { | ||||
| 		if (type === 'checkbox') { | ||||
| 			const result = await markupToHtml(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, '- [ ] xxxxxREMOVExxxxx', { | ||||
| 				bodyOnly: true, | ||||
| 				externalAssetsOnly: true, | ||||
| 			}); | ||||
| 			const html = result.html | ||||
| 				.replace(/xxxxxREMOVExxxxx/m, ' ') | ||||
| 				.replace(/<ul.*?>/, '') | ||||
| 				.replace(/<\/ul>/, ''); | ||||
| 			return { ...result, html: html }; | ||||
| 		} | ||||
|  | ||||
| 		throw new Error(`Invalid type:${type}`); | ||||
| 	}, [markupToHtml]); | ||||
|  | ||||
| 	const handleProvisionalFlag = useCallback(() => { | ||||
| 		if (props.isProvisional) { | ||||
| 			props.dispatch({ | ||||
| @@ -501,8 +527,10 @@ function NoteText2(props:NoteTextProps) { | ||||
| 		onWillChange: onBodyWillChange, | ||||
| 		defaultEditorState: defaultEditorState, | ||||
| 		markupToHtml: markupToHtml, | ||||
| 		allAssets: allAssets, | ||||
| 		attachResources: attachResources, | ||||
| 		disabled: waitingToSaveNote, | ||||
| 		joplinHtml: joplinHtml, | ||||
| 	}; | ||||
|  | ||||
| 	let editor = null; | ||||
|   | ||||
| @@ -14,7 +14,9 @@ interface TinyMCEProps { | ||||
| 	onWillChange(event:any): void, | ||||
| 	defaultEditorState: DefaultEditorState, | ||||
| 	markupToHtml: Function, | ||||
| 	allAssets: Function, | ||||
| 	attachResources: Function, | ||||
| 	joplinHtml: Function, | ||||
| 	disabled: boolean, | ||||
| } | ||||
|  | ||||
| @@ -106,6 +108,9 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 	const markupToHtml = useRef(null); | ||||
| 	markupToHtml.current = props.markupToHtml; | ||||
|  | ||||
| 	const joplinHtml = useRef(null); | ||||
| 	joplinHtml.current = props.joplinHtml; | ||||
|  | ||||
| 	const rootIdRef = useRef<string>(`tinymce-${Date.now()}${Math.round(Math.random() * 10000)}`); | ||||
|  | ||||
| 	const dispatchDidUpdate = (editor:any) => { | ||||
| @@ -166,21 +171,69 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 	// module would not load these extra files. | ||||
| 	// ----------------------------------------------------------------------------------------- | ||||
|  | ||||
| 	const loadScript = async (script:any) => { | ||||
| 		return new Promise((resolve) => { | ||||
| 			let element:any = document.createElement('script'); | ||||
| 			if (script.src.indexOf('.css') >= 0) { | ||||
| 				element = document.createElement('link'); | ||||
| 				element.rel = 'stylesheet'; | ||||
| 				element.href = script.src; | ||||
| 			} else { | ||||
| 				element.src = script.src; | ||||
|  | ||||
| 				if (script.attrs) { | ||||
| 					for (const attr in script.attrs) { | ||||
| 						element[attr] = script.attrs[attr]; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			element.id = script.id; | ||||
|  | ||||
| 			element.onload = () => { | ||||
| 				resolve(); | ||||
| 			}; | ||||
|  | ||||
| 			document.getElementsByTagName('head')[0].appendChild(element); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		if (document.getElementById('tinyMceScript')) { | ||||
| 		let cancelled = false; | ||||
|  | ||||
| 		async function loadScripts() { | ||||
| 			const scriptsToLoad:any[] = [ | ||||
| 				{ | ||||
| 					src: 'node_modules/tinymce/tinymce.min.js', | ||||
| 					id: 'tinyMceScript', | ||||
| 					loaded: false, | ||||
| 				}, | ||||
| 				{ | ||||
| 					src: 'gui/editors/TinyMCE/plugins/lists.js', | ||||
| 					id: 'tinyMceListsPluginScript', | ||||
| 					loaded: false, | ||||
| 				}, | ||||
| 			]; | ||||
|  | ||||
| 			for (const s of scriptsToLoad) { | ||||
| 				if (document.getElementById(s.id)) { | ||||
| 					s.loaded = true; | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				console.info('Loading script', s.src); | ||||
|  | ||||
| 				await loadScript(s); | ||||
| 				if (cancelled) return; | ||||
|  | ||||
| 				s.loaded = true; | ||||
| 			} | ||||
|  | ||||
| 			setScriptLoaded(true); | ||||
| 			return () => {}; | ||||
| 		} | ||||
|  | ||||
| 		let cancelled = false; | ||||
| 		const script = document.createElement('script'); | ||||
| 		script.src = 'node_modules/tinymce/tinymce.min.js'; | ||||
| 		script.id = 'tinyMceScript'; | ||||
| 		script.onload = () => { | ||||
| 			if (cancelled) return; | ||||
| 			setScriptLoaded(true); | ||||
| 		}; | ||||
| 		document.getElementsByTagName('head')[0].appendChild(script); | ||||
| 		loadScripts(); | ||||
|  | ||||
| 		return () => { | ||||
| 			cancelled = true; | ||||
| 		}; | ||||
| @@ -210,12 +263,12 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 				width: '100%', | ||||
| 				height: '100%', | ||||
| 				resize: false, | ||||
| 				plugins: 'noneditable link lists hr searchreplace', | ||||
| 				plugins: 'noneditable link joplinLists hr searchreplace codesample', | ||||
| 				noneditable_noneditable_class: 'joplin-editable', // Can be a regex too | ||||
| 				valid_elements: '*[*]', // We already filter in sanitize_html | ||||
| 				menubar: false, | ||||
| 				branding: false, | ||||
| 				toolbar: 'bold italic | link codeformat customAttach | numlist bullist h1 h2 h3 hr blockquote', | ||||
| 				toolbar: 'bold italic | link codeformat codesample joplinAttach | numlist bullist joplinChecklist | h1 h2 h3 hr blockquote', | ||||
| 				setup: (editor:any) => { | ||||
|  | ||||
| 					function openEditDialog(editable:any) { | ||||
| @@ -266,7 +319,7 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 						}); | ||||
| 					} | ||||
|  | ||||
| 					editor.ui.registry.addButton('customAttach', { | ||||
| 					editor.ui.registry.addButton('joplinAttach', { | ||||
| 						tooltip: 'Attach...', | ||||
| 						icon: 'upload', | ||||
| 						onAction: async function() { | ||||
| @@ -310,43 +363,55 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 	// Set the initial content and load the plugin CSS and JS files | ||||
| 	// ----------------------------------------------------------------------------------------- | ||||
|  | ||||
| 	const loadDocumentAssets = (editor:any, pluginAssets:any[]) => { | ||||
| 		const cssFiles = ['css/fork-awesome.min.css'].concat(pluginAssets | ||||
| 			.filter((a:any) => a.mime === 'text/css' && !loadedAssetFiles_.includes(a.path)) | ||||
| 			.map((a:any) => a.path)); | ||||
|  | ||||
| 		const jsFiles = pluginAssets | ||||
| 			.filter((a:any) => a.mime === 'application/javascript' && !loadedAssetFiles_.includes(a.path)) | ||||
| 			.map((a:any) => a.path); | ||||
|  | ||||
| 		for (const cssFile of cssFiles) loadedAssetFiles_.push(cssFile); | ||||
| 		for (const jsFile of jsFiles) loadedAssetFiles_.push(jsFile); | ||||
|  | ||||
| 		console.info('loadDocumentAssets: files to load', cssFiles, jsFiles); | ||||
|  | ||||
| 		if (cssFiles.length) editor.dom.loadCSS(cssFiles.join(',')); | ||||
|  | ||||
| 		if (jsFiles.length) { | ||||
| 			const editorElementId = editor.dom.uniqueId(); | ||||
|  | ||||
| 			for (const jsFile of jsFiles) { | ||||
| 				const script = editor.dom.create('script', { | ||||
| 					id: editorElementId, | ||||
| 					type: 'text/javascript', | ||||
| 					src: jsFile, | ||||
| 				}); | ||||
|  | ||||
| 				editor.getDoc().getElementsByTagName('head')[0].appendChild(script); | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		if (!editor) return () => {}; | ||||
|  | ||||
| 		let cancelled = false; | ||||
|  | ||||
| 		const loadContent = async () => { | ||||
| 			const result = await props.markupToHtml(props.defaultEditorState.markupLanguage, props.defaultEditorState.value); | ||||
| 			const result = await props.markupToHtml(props.defaultEditorState.markupLanguage, props.defaultEditorState.value, { | ||||
| 				plugins: { | ||||
| 					checkbox: { | ||||
| 						renderingType: 2, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}); | ||||
| 			if (cancelled) return; | ||||
|  | ||||
| 			editor.setContent(result.html); | ||||
|  | ||||
| 			const cssFiles = result.pluginAssets | ||||
| 				.filter((a:any) => a.mime === 'text/css' && !loadedAssetFiles_.includes(a.path)) | ||||
| 				.map((a:any) => a.path); | ||||
|  | ||||
| 			const jsFiles = result.pluginAssets | ||||
| 				.filter((a:any) => a.mime === 'application/javascript' && !loadedAssetFiles_.includes(a.path)) | ||||
| 				.map((a:any) => a.path); | ||||
|  | ||||
| 			for (const cssFile of cssFiles) loadedAssetFiles_.push(cssFile); | ||||
| 			for (const jsFile of jsFiles) loadedAssetFiles_.push(jsFile); | ||||
|  | ||||
| 			if (cssFiles.length) editor.dom.loadCSS(cssFiles.join(',')); | ||||
|  | ||||
| 			if (jsFiles.length) { | ||||
| 				const editorElementId = editor.dom.uniqueId(); | ||||
|  | ||||
| 				for (const jsFile of jsFiles) { | ||||
| 					const script = editor.dom.create('script', { | ||||
| 						id: editorElementId, | ||||
| 						type: 'text/javascript', | ||||
| 						src: jsFile, | ||||
| 					}); | ||||
|  | ||||
| 					editor.getDoc().getElementsByTagName('head')[0].appendChild(script); | ||||
| 				} | ||||
| 			} | ||||
| 			await loadDocumentAssets(editor, await props.allAssets(props.defaultEditorState.markupLanguage)); | ||||
|  | ||||
| 			editor.getDoc().addEventListener('click', onEditorContentClick); | ||||
|  | ||||
| @@ -359,7 +424,7 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 			cancelled = true; | ||||
| 			editor.getDoc().removeEventListener('click', onEditorContentClick); | ||||
| 		}; | ||||
| 	}, [editor, props.markupToHtml, props.defaultEditorState, onEditorContentClick]); | ||||
| 	}, [editor, props.markupToHtml, props.allAssets, props.defaultEditorState, onEditorContentClick]); | ||||
|  | ||||
| 	// ----------------------------------------------------------------------------------------- | ||||
| 	// Handle onChange event | ||||
| @@ -410,7 +475,7 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => { | ||||
| 			// | ||||
| 			// Any maybe others, so to catch them all we only check the prefix | ||||
|  | ||||
| 			const changeCommands = ['mceBlockQuote']; | ||||
| 			const changeCommands = ['mceBlockQuote', 'ToggleJoplinChecklistItem']; | ||||
|  | ||||
| 			if (changeCommands.includes(c) || c.indexOf('Insert') === 0 || c.indexOf('mceToggle') === 0 || c.indexOf('mceInsert') === 0) { | ||||
| 				onChangeHandler(); | ||||
|   | ||||
							
								
								
									
										2174
									
								
								ElectronClient/gui/editors/TinyMCE/plugins/lists.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2174
									
								
								ElectronClient/gui/editors/TinyMCE/plugins/lists.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -15,14 +15,27 @@ const tasks = { | ||||
| 		fn: require('./tools/electronRebuild.js'), | ||||
| 	}, | ||||
| 	copyLib: require('../Tools/gulp/tasks/copyLib'), | ||||
| 	tsc: require('../Tools/gulp/tasks/tsc'), | ||||
| }; | ||||
|  | ||||
| utils.registerGulpTasks(gulp, tasks); | ||||
|  | ||||
| gulp.task('build', gulp.series( | ||||
| const buildSeries = [ | ||||
| 	'copyLib', | ||||
| ]; | ||||
|  | ||||
| // On Windows also run tsc because `npm run watch` locks some folders | ||||
| // which makes the copyPluginAssets command fail. For that reason, | ||||
| // it's not possible to run watch on Windows while testing the desktop app. | ||||
| if (require('os').platform() === 'win32') { | ||||
| 	buildSeries.push('tsc'); | ||||
| } | ||||
|  | ||||
| const buildParallel = [ | ||||
| 	gulp.series(...buildSeries), | ||||
| 	'compileScripts', | ||||
| 	'compilePackageInfo', | ||||
| 	'copyPluginAssets', | ||||
| 	// 'electronRebuild' | ||||
| )); | ||||
| ]; | ||||
|  | ||||
| gulp.task('build', gulp.parallel(...buildParallel)); | ||||
|   | ||||
							
								
								
									
										461
									
								
								ElectronClient/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										461
									
								
								ElectronClient/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -252,6 +252,11 @@ | ||||
|       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", | ||||
|       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" | ||||
|     }, | ||||
|     "acorn": { | ||||
|       "version": "7.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", | ||||
|       "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" | ||||
|     }, | ||||
|     "acorn-globals": { | ||||
|       "version": "4.3.4", | ||||
|       "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", | ||||
| @@ -262,9 +267,9 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "acorn": { | ||||
|           "version": "6.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", | ||||
|           "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==" | ||||
|           "version": "6.4.1", | ||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", | ||||
|           "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @@ -681,11 +686,6 @@ | ||||
|       "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "async-limiter": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", | ||||
|       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" | ||||
|     }, | ||||
|     "async-mutex": { | ||||
|       "version": "0.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.3.tgz", | ||||
| @@ -843,7 +843,8 @@ | ||||
|               "version": "2.1.1", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "aproba": { | ||||
|               "version": "1.2.0", | ||||
| @@ -867,13 +868,15 @@ | ||||
|               "version": "1.0.0", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "brace-expansion": { | ||||
|               "version": "1.1.11", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "balanced-match": "^1.0.0", | ||||
|                 "concat-map": "0.0.1" | ||||
| @@ -890,19 +893,22 @@ | ||||
|               "version": "1.1.0", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "concat-map": { | ||||
|               "version": "0.0.1", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "console-control-strings": { | ||||
|               "version": "1.1.0", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "core-util-is": { | ||||
|               "version": "1.0.2", | ||||
| @@ -1033,7 +1039,8 @@ | ||||
|               "version": "2.0.3", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "ini": { | ||||
|               "version": "1.3.5", | ||||
| @@ -1047,6 +1054,7 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "number-is-nan": "^1.0.0" | ||||
|               } | ||||
| @@ -1063,6 +1071,7 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "brace-expansion": "^1.1.7" | ||||
|               } | ||||
| @@ -1071,13 +1080,15 @@ | ||||
|               "version": "0.0.8", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "minipass": { | ||||
|               "version": "2.3.5", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "safe-buffer": "^5.1.2", | ||||
|                 "yallist": "^3.0.0" | ||||
| @@ -1098,6 +1109,7 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "minimist": "0.0.8" | ||||
|               } | ||||
| @@ -1186,7 +1198,8 @@ | ||||
|               "version": "1.0.1", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "object-assign": { | ||||
|               "version": "4.1.1", | ||||
| @@ -1200,6 +1213,7 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "wrappy": "1" | ||||
|               } | ||||
| @@ -1337,6 +1351,7 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "code-point-at": "^1.0.0", | ||||
|                 "is-fullwidth-code-point": "^1.0.0", | ||||
| @@ -1358,6 +1373,7 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "ansi-regex": "^2.0.0" | ||||
|               } | ||||
| @@ -1406,7 +1422,8 @@ | ||||
|               "version": "1.0.2", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", | ||||
|               "dev": true | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|             }, | ||||
|             "yallist": { | ||||
|               "version": "3.0.3", | ||||
| @@ -2992,6 +3009,26 @@ | ||||
|       "resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz", | ||||
|       "integrity": "sha1-QgBdgyBLK0pdk7axpWRBM7WSegI=" | ||||
|     }, | ||||
|     "cssom": { | ||||
|       "version": "0.4.4", | ||||
|       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", | ||||
|       "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" | ||||
|     }, | ||||
|     "cssstyle": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz", | ||||
|       "integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==", | ||||
|       "requires": { | ||||
|         "cssom": "~0.3.6" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "cssom": { | ||||
|           "version": "0.3.8", | ||||
|           "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", | ||||
|           "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "csstype": { | ||||
|       "version": "2.6.4", | ||||
|       "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.4.tgz", | ||||
| @@ -3304,6 +3341,16 @@ | ||||
|         "assert-plus": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "data-urls": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", | ||||
|       "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", | ||||
|       "requires": { | ||||
|         "abab": "^2.0.0", | ||||
|         "whatwg-mimetype": "^2.2.0", | ||||
|         "whatwg-url": "^7.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
| @@ -3560,6 +3607,14 @@ | ||||
|       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", | ||||
|       "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" | ||||
|     }, | ||||
|     "domexception": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", | ||||
|       "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", | ||||
|       "requires": { | ||||
|         "webidl-conversions": "^4.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "domhandler": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", | ||||
| @@ -4840,9 +4895,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "glob": { | ||||
|       "version": "7.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", | ||||
|       "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", | ||||
|       "version": "7.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", | ||||
|       "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "fs.realpath": "^1.0.0", | ||||
| @@ -5275,7 +5330,6 @@ | ||||
|               "resolved": false, | ||||
|               "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", | ||||
|               "dev": true, | ||||
|               "optional": true, | ||||
|               "requires": { | ||||
|                 "brace-expansion": "^1.1.7" | ||||
|               } | ||||
| @@ -5284,8 +5338,7 @@ | ||||
|               "version": "0.0.8", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|               "dev": true | ||||
|             }, | ||||
|             "minipass": { | ||||
|               "version": "2.9.0", | ||||
| @@ -5410,8 +5463,7 @@ | ||||
|               "version": "1.0.1", | ||||
|               "resolved": false, | ||||
|               "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", | ||||
|               "dev": true, | ||||
|               "optional": true | ||||
|               "dev": true | ||||
|             }, | ||||
|             "object-assign": { | ||||
|               "version": "4.1.1", | ||||
| @@ -6152,6 +6204,14 @@ | ||||
|         "lru-cache": "^5.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "html-encoding-sniffer": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", | ||||
|       "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", | ||||
|       "requires": { | ||||
|         "whatwg-encoding": "^1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "html-entities": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", | ||||
| @@ -6305,6 +6365,11 @@ | ||||
|       "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "ip-regex": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", | ||||
|       "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" | ||||
|     }, | ||||
|     "is-absolute": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", | ||||
| @@ -6646,140 +6711,13 @@ | ||||
|       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" | ||||
|     }, | ||||
|     "joplin-turndown": { | ||||
|       "version": "4.0.23", | ||||
|       "resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.23.tgz", | ||||
|       "integrity": "sha512-Dh93R7G/S/KRbOu4/+FIxoUcUDcoUL4QDsqGhperOi/cUxUeg8fngrmEzdP8kEpQzqm5+8jkq9Cc1w6695owpQ==", | ||||
|       "version": "4.0.24", | ||||
|       "resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.24.tgz", | ||||
|       "integrity": "sha512-mKd2rAFzJKnhTVjEpHomG+T01//uz5rXVSAOYRh3/JKXpY7QUhVp8jCmFfO+kaadrLABTz04mvTmyyoOadxdTA==", | ||||
|       "requires": { | ||||
|         "css": "^2.2.4", | ||||
|         "html-entities": "^1.2.1", | ||||
|         "jsdom": "^11.9.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "acorn": { | ||||
|           "version": "5.7.3", | ||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", | ||||
|           "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" | ||||
|         }, | ||||
|         "cssom": { | ||||
|           "version": "0.3.8", | ||||
|           "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", | ||||
|           "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" | ||||
|         }, | ||||
|         "cssstyle": { | ||||
|           "version": "1.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", | ||||
|           "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", | ||||
|           "requires": { | ||||
|             "cssom": "0.3.x" | ||||
|           } | ||||
|         }, | ||||
|         "data-urls": { | ||||
|           "version": "1.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", | ||||
|           "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", | ||||
|           "requires": { | ||||
|             "abab": "^2.0.0", | ||||
|             "whatwg-mimetype": "^2.2.0", | ||||
|             "whatwg-url": "^7.0.0" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "whatwg-url": { | ||||
|               "version": "7.1.0", | ||||
|               "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", | ||||
|               "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", | ||||
|               "requires": { | ||||
|                 "lodash.sortby": "^4.7.0", | ||||
|                 "tr46": "^1.0.1", | ||||
|                 "webidl-conversions": "^4.0.2" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "domexception": { | ||||
|           "version": "1.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", | ||||
|           "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", | ||||
|           "requires": { | ||||
|             "webidl-conversions": "^4.0.2" | ||||
|           } | ||||
|         }, | ||||
|         "html-encoding-sniffer": { | ||||
|           "version": "1.0.2", | ||||
|           "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", | ||||
|           "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", | ||||
|           "requires": { | ||||
|             "whatwg-encoding": "^1.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "jsdom": { | ||||
|           "version": "11.12.0", | ||||
|           "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", | ||||
|           "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", | ||||
|           "requires": { | ||||
|             "abab": "^2.0.0", | ||||
|             "acorn": "^5.5.3", | ||||
|             "acorn-globals": "^4.1.0", | ||||
|             "array-equal": "^1.0.0", | ||||
|             "cssom": ">= 0.3.2 < 0.4.0", | ||||
|             "cssstyle": "^1.0.0", | ||||
|             "data-urls": "^1.0.0", | ||||
|             "domexception": "^1.0.1", | ||||
|             "escodegen": "^1.9.1", | ||||
|             "html-encoding-sniffer": "^1.0.2", | ||||
|             "left-pad": "^1.3.0", | ||||
|             "nwsapi": "^2.0.7", | ||||
|             "parse5": "4.0.0", | ||||
|             "pn": "^1.1.0", | ||||
|             "request": "^2.87.0", | ||||
|             "request-promise-native": "^1.0.5", | ||||
|             "sax": "^1.2.4", | ||||
|             "symbol-tree": "^3.2.2", | ||||
|             "tough-cookie": "^2.3.4", | ||||
|             "w3c-hr-time": "^1.0.1", | ||||
|             "webidl-conversions": "^4.0.2", | ||||
|             "whatwg-encoding": "^1.0.3", | ||||
|             "whatwg-mimetype": "^2.1.0", | ||||
|             "whatwg-url": "^6.4.1", | ||||
|             "ws": "^5.2.0", | ||||
|             "xml-name-validator": "^3.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "parse5": { | ||||
|           "version": "4.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", | ||||
|           "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" | ||||
|         }, | ||||
|         "tr46": { | ||||
|           "version": "1.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", | ||||
|           "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", | ||||
|           "requires": { | ||||
|             "punycode": "^2.1.0" | ||||
|           } | ||||
|         }, | ||||
|         "webidl-conversions": { | ||||
|           "version": "4.0.2", | ||||
|           "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", | ||||
|           "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" | ||||
|         }, | ||||
|         "whatwg-url": { | ||||
|           "version": "6.5.0", | ||||
|           "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", | ||||
|           "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", | ||||
|           "requires": { | ||||
|             "lodash.sortby": "^4.7.0", | ||||
|             "tr46": "^1.0.1", | ||||
|             "webidl-conversions": "^4.0.2" | ||||
|           } | ||||
|         }, | ||||
|         "ws": { | ||||
|           "version": "5.2.2", | ||||
|           "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", | ||||
|           "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", | ||||
|           "requires": { | ||||
|             "async-limiter": "~1.0.0" | ||||
|           } | ||||
|         } | ||||
|         "jsdom": "^15.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "joplin-turndown-plugin-gfm": { | ||||
| @@ -6808,6 +6746,152 @@ | ||||
|       "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "jsdom": { | ||||
|       "version": "15.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", | ||||
|       "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", | ||||
|       "requires": { | ||||
|         "abab": "^2.0.0", | ||||
|         "acorn": "^7.1.0", | ||||
|         "acorn-globals": "^4.3.2", | ||||
|         "array-equal": "^1.0.0", | ||||
|         "cssom": "^0.4.1", | ||||
|         "cssstyle": "^2.0.0", | ||||
|         "data-urls": "^1.1.0", | ||||
|         "domexception": "^1.0.1", | ||||
|         "escodegen": "^1.11.1", | ||||
|         "html-encoding-sniffer": "^1.0.2", | ||||
|         "nwsapi": "^2.2.0", | ||||
|         "parse5": "5.1.0", | ||||
|         "pn": "^1.1.0", | ||||
|         "request": "^2.88.0", | ||||
|         "request-promise-native": "^1.0.7", | ||||
|         "saxes": "^3.1.9", | ||||
|         "symbol-tree": "^3.2.2", | ||||
|         "tough-cookie": "^3.0.1", | ||||
|         "w3c-hr-time": "^1.0.1", | ||||
|         "w3c-xmlserializer": "^1.1.2", | ||||
|         "webidl-conversions": "^4.0.2", | ||||
|         "whatwg-encoding": "^1.0.5", | ||||
|         "whatwg-mimetype": "^2.3.0", | ||||
|         "whatwg-url": "^7.0.0", | ||||
|         "ws": "^7.0.0", | ||||
|         "xml-name-validator": "^3.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ajv": { | ||||
|           "version": "6.12.0", | ||||
|           "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", | ||||
|           "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", | ||||
|           "requires": { | ||||
|             "fast-deep-equal": "^3.1.1", | ||||
|             "fast-json-stable-stringify": "^2.0.0", | ||||
|             "json-schema-traverse": "^0.4.1", | ||||
|             "uri-js": "^4.2.2" | ||||
|           } | ||||
|         }, | ||||
|         "aws4": { | ||||
|           "version": "1.9.1", | ||||
|           "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", | ||||
|           "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" | ||||
|         }, | ||||
|         "fast-deep-equal": { | ||||
|           "version": "3.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", | ||||
|           "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" | ||||
|         }, | ||||
|         "har-validator": { | ||||
|           "version": "5.1.3", | ||||
|           "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", | ||||
|           "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", | ||||
|           "requires": { | ||||
|             "ajv": "^6.5.5", | ||||
|             "har-schema": "^2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "json-schema-traverse": { | ||||
|           "version": "0.4.1", | ||||
|           "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", | ||||
|           "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" | ||||
|         }, | ||||
|         "mime-db": { | ||||
|           "version": "1.43.0", | ||||
|           "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", | ||||
|           "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" | ||||
|         }, | ||||
|         "mime-types": { | ||||
|           "version": "2.1.26", | ||||
|           "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", | ||||
|           "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", | ||||
|           "requires": { | ||||
|             "mime-db": "1.43.0" | ||||
|           } | ||||
|         }, | ||||
|         "oauth-sign": { | ||||
|           "version": "0.9.0", | ||||
|           "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", | ||||
|           "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" | ||||
|         }, | ||||
|         "request": { | ||||
|           "version": "2.88.2", | ||||
|           "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", | ||||
|           "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", | ||||
|           "requires": { | ||||
|             "aws-sign2": "~0.7.0", | ||||
|             "aws4": "^1.8.0", | ||||
|             "caseless": "~0.12.0", | ||||
|             "combined-stream": "~1.0.6", | ||||
|             "extend": "~3.0.2", | ||||
|             "forever-agent": "~0.6.1", | ||||
|             "form-data": "~2.3.2", | ||||
|             "har-validator": "~5.1.3", | ||||
|             "http-signature": "~1.2.0", | ||||
|             "is-typedarray": "~1.0.0", | ||||
|             "isstream": "~0.1.2", | ||||
|             "json-stringify-safe": "~5.0.1", | ||||
|             "mime-types": "~2.1.19", | ||||
|             "oauth-sign": "~0.9.0", | ||||
|             "performance-now": "^2.1.0", | ||||
|             "qs": "~6.5.2", | ||||
|             "safe-buffer": "^5.1.2", | ||||
|             "tough-cookie": "~2.5.0", | ||||
|             "tunnel-agent": "^0.6.0", | ||||
|             "uuid": "^3.3.2" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "tough-cookie": { | ||||
|               "version": "2.5.0", | ||||
|               "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", | ||||
|               "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", | ||||
|               "requires": { | ||||
|                 "psl": "^1.1.28", | ||||
|                 "punycode": "^2.1.1" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "safe-buffer": { | ||||
|           "version": "5.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", | ||||
|           "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" | ||||
|         }, | ||||
|         "tough-cookie": { | ||||
|           "version": "3.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", | ||||
|           "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", | ||||
|           "requires": { | ||||
|             "ip-regex": "^2.1.0", | ||||
|             "psl": "^1.1.28", | ||||
|             "punycode": "^2.1.1" | ||||
|           } | ||||
|         }, | ||||
|         "uuid": { | ||||
|           "version": "3.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", | ||||
|           "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "jsesc": { | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", | ||||
| @@ -6973,11 +7057,6 @@ | ||||
|         "flush-write-stream": "^1.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "left-pad": { | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", | ||||
|       "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" | ||||
|     }, | ||||
|     "levenshtein": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/levenshtein/-/levenshtein-1.0.5.tgz", | ||||
| @@ -8577,6 +8656,11 @@ | ||||
|       "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "parse5": { | ||||
|       "version": "5.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", | ||||
|       "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" | ||||
|     }, | ||||
|     "pascalcase": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", | ||||
| @@ -9164,8 +9248,7 @@ | ||||
|     "psl": { | ||||
|       "version": "1.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", | ||||
|       "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", | ||||
|       "dev": true | ||||
|       "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==" | ||||
|     }, | ||||
|     "pump": { | ||||
|       "version": "3.0.0", | ||||
| @@ -9985,6 +10068,14 @@ | ||||
|       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", | ||||
|       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" | ||||
|     }, | ||||
|     "saxes": { | ||||
|       "version": "3.1.11", | ||||
|       "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", | ||||
|       "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", | ||||
|       "requires": { | ||||
|         "xmlchars": "^2.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "scheduler": { | ||||
|       "version": "0.15.0", | ||||
|       "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", | ||||
| @@ -10912,6 +11003,14 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "tr46": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", | ||||
|       "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", | ||||
|       "requires": { | ||||
|         "punycode": "^2.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "trim-right": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", | ||||
| @@ -11254,7 +11353,6 @@ | ||||
|       "version": "4.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", | ||||
|       "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "punycode": "^2.1.0" | ||||
|       } | ||||
| @@ -11428,6 +11526,16 @@ | ||||
|         "browser-process-hrtime": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "w3c-xmlserializer": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", | ||||
|       "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", | ||||
|       "requires": { | ||||
|         "domexception": "^1.0.1", | ||||
|         "webidl-conversions": "^4.0.2", | ||||
|         "xml-name-validator": "^3.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "wcwidth": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", | ||||
| @@ -11437,6 +11545,11 @@ | ||||
|         "defaults": "^1.0.3" | ||||
|       } | ||||
|     }, | ||||
|     "webidl-conversions": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", | ||||
|       "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" | ||||
|     }, | ||||
|     "whatwg-encoding": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", | ||||
| @@ -11465,6 +11578,16 @@ | ||||
|       "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", | ||||
|       "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" | ||||
|     }, | ||||
|     "whatwg-url": { | ||||
|       "version": "7.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", | ||||
|       "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", | ||||
|       "requires": { | ||||
|         "lodash.sortby": "^4.7.0", | ||||
|         "tr46": "^1.0.1", | ||||
|         "webidl-conversions": "^4.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "which": { | ||||
|       "version": "1.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", | ||||
| @@ -11634,6 +11757,11 @@ | ||||
|         "typedarray-to-buffer": "^3.1.5" | ||||
|       } | ||||
|     }, | ||||
|     "ws": { | ||||
|       "version": "7.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", | ||||
|       "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" | ||||
|     }, | ||||
|     "xdg-basedir": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", | ||||
| @@ -11661,6 +11789,11 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "xmlchars": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", | ||||
|       "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" | ||||
|     }, | ||||
|     "xtend": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     "dist": "node_modules/.bin/electron-builder", | ||||
|     "build": "patch-package --patch-dir ../patches && gulp build", | ||||
|     "postinstall": "npm run build && gulp electronRebuild", | ||||
|     "start": "gulp build -L && electron . --env dev --log-level debug --no-welcome --open-dev-tools" | ||||
|     "start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
| @@ -75,6 +75,7 @@ | ||||
|     "electron": "^7.1.12", | ||||
|     "electron-builder": "22.3.2", | ||||
|     "electron-rebuild": "^1.8.8", | ||||
|     "glob": "^7.1.6", | ||||
|     "gulp": "^4.0.2", | ||||
|     "patch-package": "^6.2.0" | ||||
|   }, | ||||
| @@ -109,7 +110,7 @@ | ||||
|     "html-minifier": "^4.0.0", | ||||
|     "htmlparser2": "^4.1.0", | ||||
|     "image-type": "^3.0.0", | ||||
|     "joplin-turndown": "^4.0.23", | ||||
|     "joplin-turndown": "^4.0.24", | ||||
|     "joplin-turndown-plugin-gfm": "^1.0.12", | ||||
|     "json-stringify-safe": "^5.0.1", | ||||
|     "jssha": "^2.3.1", | ||||
|   | ||||
							
								
								
									
										14
									
								
								Modules/TinyMCE/JoplinLists/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Modules/TinyMCE/JoplinLists/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| .DS_Store | ||||
| *~ | ||||
| *.iws | ||||
| *.sublime-workspace | ||||
| .cache | ||||
| /lib | ||||
| /dist | ||||
| /scratch | ||||
| node_modules | ||||
| /config/repl | ||||
| /*.log | ||||
| ephox-*.tgz | ||||
| package-lock.json | ||||
| jenkins-plumbing | ||||
							
								
								
									
										196
									
								
								Modules/TinyMCE/JoplinLists/Gruntfile.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								Modules/TinyMCE/JoplinLists/Gruntfile.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| const { CheckerPlugin } = require('awesome-typescript-loader'); | ||||
| const LiveReloadPlugin = require('webpack-livereload-plugin'); | ||||
| const path = require('path'); | ||||
| const swag = require('@ephox/swag'); | ||||
|  | ||||
| module.exports = function(grunt) { | ||||
| 	const packageData = grunt.file.readJSON('package.json'); | ||||
| 	const BUILD_VERSION = `${packageData.version}-${process.env.BUILD_NUMBER ? process.env.BUILD_NUMBER : '0'}`; | ||||
| 	const libPluginPath = 'lib/Main.js'; | ||||
| 	const scratchPluginPath = 'scratch/compiled/joplinLists.js'; | ||||
| 	const scratchPluginMinPath = 'scratch/compiled/joplinLists.min.js'; | ||||
| 	const tsDemoSourceFile = path.resolve('src/demo/ts/Demo.ts'); | ||||
| 	const jsDemoDestFile = path.resolve('scratch/compiled/demo.js'); | ||||
|  | ||||
| 	grunt.initConfig({ | ||||
| 		pkg: packageData, | ||||
|  | ||||
| 		clean: { | ||||
| 			dirs: ['dist', 'scratch'], | ||||
| 		}, | ||||
|  | ||||
| 		// tslint: { | ||||
| 		//   options: { | ||||
| 		//     configuration: 'tslint.json' | ||||
| 		//   }, | ||||
| 		//   plugin: ['src/**/*.ts'] | ||||
| 		// }, | ||||
|  | ||||
| 		shell: { | ||||
| 			command: 'tsc', | ||||
| 		}, | ||||
|  | ||||
| 		rollup: { | ||||
| 			options: { | ||||
| 				treeshake: true, | ||||
| 				external: [ | ||||
| 					'tinymce/core/api/PluginManager', | ||||
| 					'tinymce/core/api/util/Tools', | ||||
| 					'tinymce/core/api/dom/BookmarkManager', | ||||
| 					'tinymce/core/api/Editor', | ||||
| 					'tinymce/core/api/dom/DOMUtils', | ||||
| 					'tinymce/core/api/dom/RangeUtils', | ||||
| 					'tinymce/core/api/dom/TreeWalker', | ||||
| 					'tinymce/core/api/util/VK', | ||||
| 					'tinymce/core/api/dom/DomQuery', | ||||
| 				], | ||||
|     			globals: { | ||||
|         			'tinymce/core/api/PluginManager': 'tinymce.PluginManager', | ||||
| 					'tinymce/core/api/util/Tools': 'tinymce.util.Tools', | ||||
| 					'tinymce/core/api/dom/BookmarkManager': 'tinymce.dom.BookmarkManager', | ||||
| 					'tinymce/core/api/Editor': 'tinymce.Editor', | ||||
| 					'tinymce/core/api/dom/DOMUtils': 'tinymce.dom.DOMUtils', | ||||
| 					'tinymce/core/api/dom/RangeUtils': 'tinymce.dom.RangeUtils', | ||||
| 					'tinymce/core/api/dom/TreeWalker': 'tinymce.dom.TreeWalker', | ||||
| 					'tinymce/core/api/util/VK': 'tinymce.util.VK', | ||||
| 					'tinymce/core/api/dom/DomQuery': 'tinymce.dom.DomQuery', | ||||
| 				}, | ||||
| 				format: 'iife', | ||||
| 				onwarn: swag.onwarn, | ||||
| 				plugins: [ | ||||
| 					swag.nodeResolve({ | ||||
| 						basedir: __dirname, | ||||
| 						prefixes: {}, | ||||
| 					}), | ||||
| 					swag.remapImports(), | ||||
| 				], | ||||
| 			}, | ||||
| 			plugin: { | ||||
| 				files: [ | ||||
| 					{ | ||||
| 						src: libPluginPath, | ||||
| 						dest: scratchPluginPath, | ||||
| 					}, | ||||
| 				], | ||||
| 			}, | ||||
| 		}, | ||||
|  | ||||
| 		uglify: { | ||||
| 			plugin: { | ||||
| 				files: [ | ||||
| 					{ | ||||
| 						src: scratchPluginPath, | ||||
| 						dest: scratchPluginMinPath, | ||||
| 					}, | ||||
| 				], | ||||
| 			}, | ||||
| 		}, | ||||
|  | ||||
| 		concat: { | ||||
| 			license: { | ||||
| 				options: { | ||||
| 					process: function(src) { | ||||
| 						const buildSuffix = process.env.BUILD_NUMBER | ||||
| 							? `-${process.env.BUILD_NUMBER}` | ||||
| 							: ''; | ||||
| 						return src.replace( | ||||
| 							/@BUILD_NUMBER@/g, | ||||
| 							packageData.version + buildSuffix | ||||
| 						); | ||||
| 					}, | ||||
| 				}, | ||||
| 				// scratchPluginMinPath is used twice on purpose, all outputs will be minified for premium plugins | ||||
| 				files: { | ||||
| 					'dist/joplinLists.js': [ | ||||
| 						'src/text/license-header.js', | ||||
| 						scratchPluginPath, | ||||
| 					], | ||||
| 					'dist/joplinLists.min.js': [ | ||||
| 						'src/text/license-header.js', | ||||
| 						scratchPluginMinPath, | ||||
| 					], | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
|  | ||||
| 		copy: { | ||||
| 			css: { | ||||
| 				files: [ | ||||
| 					// { | ||||
| 					// 	cwd: 'src/text', | ||||
| 					// 	src: ['license.txt'], | ||||
| 					// 	dest: 'dist', | ||||
| 					// 	expand: true, | ||||
| 					// }, | ||||
| 					// { src: ['changelog.txt'], dest: 'dist', expand: true }, | ||||
| 					{ | ||||
| 						src: ['dist/joplinLists.js'], | ||||
| 						dest: '../../../ElectronClient/gui/editors/TinyMCE/plugins/lists.js', | ||||
| 					}, | ||||
| 				], | ||||
| 			}, | ||||
| 		}, | ||||
|  | ||||
| 		webpack: { | ||||
| 			options: { | ||||
| 				mode: 'development', | ||||
| 				watch: true, | ||||
| 			}, | ||||
| 			dev: { | ||||
| 				entry: tsDemoSourceFile, | ||||
| 				devtool: 'source-map', | ||||
|  | ||||
| 				resolve: { | ||||
| 					extensions: ['.ts', '.js'], | ||||
| 				}, | ||||
|  | ||||
| 				module: { | ||||
| 					rules: [ | ||||
| 						{ | ||||
| 							test: /\.js$/, | ||||
| 							use: ['source-map-loader'], | ||||
| 							enforce: 'pre', | ||||
| 						}, | ||||
| 						{ | ||||
| 							test: /\.ts$/, | ||||
| 							use: [ | ||||
| 								{ | ||||
| 									loader: 'ts-loader', | ||||
| 									options: { | ||||
| 										transpileOnly: true, | ||||
| 										experimentalWatchApi: true, | ||||
| 									}, | ||||
| 								}, | ||||
| 							], | ||||
| 						}, | ||||
| 					], | ||||
| 				}, | ||||
|  | ||||
| 				plugins: [new LiveReloadPlugin(), new CheckerPlugin()], | ||||
|  | ||||
| 				output: { | ||||
| 					filename: path.basename(jsDemoDestFile), | ||||
| 					path: path.dirname(jsDemoDestFile), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	require('load-grunt-tasks')(grunt); | ||||
| 	grunt.loadNpmTasks('@ephox/swag'); | ||||
|  | ||||
| 	// grunt.registerTask('version', 'Creates a version file', function() { | ||||
| 	// 	grunt.file.write('dist/version.txt', BUILD_VERSION); | ||||
| 	// }); | ||||
|  | ||||
| 	grunt.registerTask('default', [ | ||||
| 		'clean', | ||||
| 		// 'tslint', | ||||
| 		'shell', | ||||
| 		'rollup', | ||||
| 		'uglify', | ||||
| 		'concat', | ||||
| 		'copy', | ||||
| 		// 'version', | ||||
| 	]); | ||||
| }; | ||||
							
								
								
									
										502
									
								
								Modules/TinyMCE/JoplinLists/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										502
									
								
								Modules/TinyMCE/JoplinLists/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,502 @@ | ||||
|       GNU LESSER GENERAL PUBLIC LICENSE | ||||
|            Version 2.1, February 1999 | ||||
|  | ||||
|  Copyright (C) 1991, 1999 Free Software Foundation, Inc. | ||||
|  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
| [This is the first released version of the Lesser GPL.  It also counts | ||||
|  as the successor of the GNU Library Public License, version 2, hence | ||||
|  the version number 2.1.] | ||||
|  | ||||
|           Preamble | ||||
|  | ||||
|   The licenses for most software are designed to take away your | ||||
| freedom to share and change it.  By contrast, the GNU General Public | ||||
| Licenses are intended to guarantee your freedom to share and change | ||||
| free software--to make sure the software is free for all its users. | ||||
|  | ||||
|   This license, the Lesser General Public License, applies to some | ||||
| specially designated software packages--typically libraries--of the | ||||
| Free Software Foundation and other authors who decide to use it.  You | ||||
| can use it too, but we suggest you first think carefully about whether | ||||
| this license or the ordinary General Public License is the better | ||||
| strategy to use in any particular case, based on the explanations below. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom of use, | ||||
| not price.  Our General Public Licenses are designed to make sure that | ||||
| you have the freedom to distribute copies of free software (and charge | ||||
| for this service if you wish); that you receive source code or can get | ||||
| it if you want it; that you can change the software and use pieces of | ||||
| it in new free programs; and that you are informed that you can do | ||||
| these things. | ||||
|  | ||||
|   To protect your rights, we need to make restrictions that forbid | ||||
| distributors to deny you these rights or to ask you to surrender these | ||||
| rights.  These restrictions translate to certain responsibilities for | ||||
| you if you distribute copies of the library or if you modify it. | ||||
|  | ||||
|   For example, if you distribute copies of the library, whether gratis | ||||
| or for a fee, you must give the recipients all the rights that we gave | ||||
| you.  You must make sure that they, too, receive or can get the source | ||||
| code.  If you link other code with the library, you must provide | ||||
| complete object files to the recipients, so that they can relink them | ||||
| with the library after making changes to the library and recompiling | ||||
| it.  And you must show them these terms so they know their rights. | ||||
|  | ||||
|   We protect your rights with a two-step method: (1) we copyright the | ||||
| library, and (2) we offer you this license, which gives you legal | ||||
| permission to copy, distribute and/or modify the library. | ||||
|  | ||||
|   To protect each distributor, we want to make it very clear that | ||||
| there is no warranty for the free library.  Also, if the library is | ||||
| modified by someone else and passed on, the recipients should know | ||||
| that what they have is not the original version, so that the original | ||||
| author's reputation will not be affected by problems that might be | ||||
| introduced by others. | ||||
|  | ||||
|   Finally, software patents pose a constant threat to the existence of | ||||
| any free program.  We wish to make sure that a company cannot | ||||
| effectively restrict the users of a free program by obtaining a | ||||
| restrictive license from a patent holder.  Therefore, we insist that | ||||
| any patent license obtained for a version of the library must be | ||||
| consistent with the full freedom of use specified in this license. | ||||
|  | ||||
|   Most GNU software, including some libraries, is covered by the | ||||
| ordinary GNU General Public License.  This license, the GNU Lesser | ||||
| General Public License, applies to certain designated libraries, and | ||||
| is quite different from the ordinary General Public License.  We use | ||||
| this license for certain libraries in order to permit linking those | ||||
| libraries into non-free programs. | ||||
|  | ||||
|   When a program is linked with a library, whether statically or using | ||||
| a shared library, the combination of the two is legally speaking a | ||||
| combined work, a derivative of the original library.  The ordinary | ||||
| General Public License therefore permits such linking only if the | ||||
| entire combination fits its criteria of freedom.  The Lesser General | ||||
| Public License permits more lax criteria for linking other code with | ||||
| the library. | ||||
|  | ||||
|   We call this license the "Lesser" General Public License because it | ||||
| does Less to protect the user's freedom than the ordinary General | ||||
| Public License.  It also provides other free software developers Less | ||||
| of an advantage over competing non-free programs.  These disadvantages | ||||
| are the reason we use the ordinary General Public License for many | ||||
| libraries.  However, the Lesser license provides advantages in certain | ||||
| special circumstances. | ||||
|  | ||||
|   For example, on rare occasions, there may be a special need to | ||||
| encourage the widest possible use of a certain library, so that it becomes | ||||
| a de-facto standard.  To achieve this, non-free programs must be | ||||
| allowed to use the library.  A more frequent case is that a free | ||||
| library does the same job as widely used non-free libraries.  In this | ||||
| case, there is little to gain by limiting the free library to free | ||||
| software only, so we use the Lesser General Public License. | ||||
|  | ||||
|   In other cases, permission to use a particular library in non-free | ||||
| programs enables a greater number of people to use a large body of | ||||
| free software.  For example, permission to use the GNU C Library in | ||||
| non-free programs enables many more people to use the whole GNU | ||||
| operating system, as well as its variant, the GNU/Linux operating | ||||
| system. | ||||
|  | ||||
|   Although the Lesser General Public License is Less protective of the | ||||
| users' freedom, it does ensure that the user of a program that is | ||||
| linked with the Library has the freedom and the wherewithal to run | ||||
| that program using a modified version of the Library. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow.  Pay close attention to the difference between a | ||||
| "work based on the library" and a "work that uses the library".  The | ||||
| former contains code derived from the library, whereas the latter must | ||||
| be combined with the library in order to run. | ||||
|  | ||||
|       GNU LESSER GENERAL PUBLIC LICENSE | ||||
|    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||||
|  | ||||
|   0. This License Agreement applies to any software library or other | ||||
| program which contains a notice placed by the copyright holder or | ||||
| other authorized party saying it may be distributed under the terms of | ||||
| this Lesser General Public License (also called "this License"). | ||||
| Each licensee is addressed as "you". | ||||
|  | ||||
|   A "library" means a collection of software functions and/or data | ||||
| prepared so as to be conveniently linked with application programs | ||||
| (which use some of those functions and data) to form executables. | ||||
|  | ||||
|   The "Library", below, refers to any such software library or work | ||||
| which has been distributed under these terms.  A "work based on the | ||||
| Library" means either the Library or any derivative work under | ||||
| copyright law: that is to say, a work containing the Library or a | ||||
| portion of it, either verbatim or with modifications and/or translated | ||||
| straightforwardly into another language.  (Hereinafter, translation is | ||||
| included without limitation in the term "modification".) | ||||
|  | ||||
|   "Source code" for a work means the preferred form of the work for | ||||
| making modifications to it.  For a library, complete source code means | ||||
| all the source code for all modules it contains, plus any associated | ||||
| interface definition files, plus the scripts used to control compilation | ||||
| and installation of the library. | ||||
|  | ||||
|   Activities other than copying, distribution and modification are not | ||||
| covered by this License; they are outside its scope.  The act of | ||||
| running a program using the Library is not restricted, and output from | ||||
| such a program is covered only if its contents constitute a work based | ||||
| on the Library (independent of the use of the Library in a tool for | ||||
| writing it).  Whether that is true depends on what the Library does | ||||
| and what the program that uses the Library does. | ||||
|    | ||||
|   1. You may copy and distribute verbatim copies of the Library's | ||||
| complete source code as you receive it, in any medium, provided that | ||||
| you conspicuously and appropriately publish on each copy an | ||||
| appropriate copyright notice and disclaimer of warranty; keep intact | ||||
| all the notices that refer to this License and to the absence of any | ||||
| warranty; and distribute a copy of this License along with the | ||||
| Library. | ||||
|  | ||||
|   You may charge a fee for the physical act of transferring a copy, | ||||
| and you may at your option offer warranty protection in exchange for a | ||||
| fee. | ||||
|  | ||||
|   2. You may modify your copy or copies of the Library or any portion | ||||
| of it, thus forming a work based on the Library, and copy and | ||||
| distribute such modifications or work under the terms of Section 1 | ||||
| above, provided that you also meet all of these conditions: | ||||
|  | ||||
|     a) The modified work must itself be a software library. | ||||
|  | ||||
|     b) You must cause the files modified to carry prominent notices | ||||
|     stating that you changed the files and the date of any change. | ||||
|  | ||||
|     c) You must cause the whole of the work to be licensed at no | ||||
|     charge to all third parties under the terms of this License. | ||||
|  | ||||
|     d) If a facility in the modified Library refers to a function or a | ||||
|     table of data to be supplied by an application program that uses | ||||
|     the facility, other than as an argument passed when the facility | ||||
|     is invoked, then you must make a good faith effort to ensure that, | ||||
|     in the event an application does not supply such function or | ||||
|     table, the facility still operates, and performs whatever part of | ||||
|     its purpose remains meaningful. | ||||
|  | ||||
|     (For example, a function in a library to compute square roots has | ||||
|     a purpose that is entirely well-defined independent of the | ||||
|     application.  Therefore, Subsection 2d requires that any | ||||
|     application-supplied function or table used by this function must | ||||
|     be optional: if the application does not supply it, the square | ||||
|     root function must still compute square roots.) | ||||
|  | ||||
| These requirements apply to the modified work as a whole.  If | ||||
| identifiable sections of that work are not derived from the Library, | ||||
| and can be reasonably considered independent and separate works in | ||||
| themselves, then this License, and its terms, do not apply to those | ||||
| sections when you distribute them as separate works.  But when you | ||||
| distribute the same sections as part of a whole which is a work based | ||||
| on the Library, the distribution of the whole must be on the terms of | ||||
| this License, whose permissions for other licensees extend to the | ||||
| entire whole, and thus to each and every part regardless of who wrote | ||||
| it. | ||||
|  | ||||
| Thus, it is not the intent of this section to claim rights or contest | ||||
| your rights to work written entirely by you; rather, the intent is to | ||||
| exercise the right to control the distribution of derivative or | ||||
| collective works based on the Library. | ||||
|  | ||||
| In addition, mere aggregation of another work not based on the Library | ||||
| with the Library (or with a work based on the Library) on a volume of | ||||
| a storage or distribution medium does not bring the other work under | ||||
| the scope of this License. | ||||
|  | ||||
|   3. You may opt to apply the terms of the ordinary GNU General Public | ||||
| License instead of this License to a given copy of the Library.  To do | ||||
| this, you must alter all the notices that refer to this License, so | ||||
| that they refer to the ordinary GNU General Public License, version 2, | ||||
| instead of to this License.  (If a newer version than version 2 of the | ||||
| ordinary GNU General Public License has appeared, then you can specify | ||||
| that version instead if you wish.)  Do not make any other change in | ||||
| these notices. | ||||
|  | ||||
|   Once this change is made in a given copy, it is irreversible for | ||||
| that copy, so the ordinary GNU General Public License applies to all | ||||
| subsequent copies and derivative works made from that copy. | ||||
|  | ||||
|   This option is useful when you wish to copy part of the code of | ||||
| the Library into a program that is not a library. | ||||
|  | ||||
|   4. You may copy and distribute the Library (or a portion or | ||||
| derivative of it, under Section 2) in object code or executable form | ||||
| under the terms of Sections 1 and 2 above provided that you accompany | ||||
| it with the complete corresponding machine-readable source code, which | ||||
| must be distributed under the terms of Sections 1 and 2 above on a | ||||
| medium customarily used for software interchange. | ||||
|  | ||||
|   If distribution of object code is made by offering access to copy | ||||
| from a designated place, then offering equivalent access to copy the | ||||
| source code from the same place satisfies the requirement to | ||||
| distribute the source code, even though third parties are not | ||||
| compelled to copy the source along with the object code. | ||||
|  | ||||
|   5. A program that contains no derivative of any portion of the | ||||
| Library, but is designed to work with the Library by being compiled or | ||||
| linked with it, is called a "work that uses the Library".  Such a | ||||
| work, in isolation, is not a derivative work of the Library, and | ||||
| therefore falls outside the scope of this License. | ||||
|  | ||||
|   However, linking a "work that uses the Library" with the Library | ||||
| creates an executable that is a derivative of the Library (because it | ||||
| contains portions of the Library), rather than a "work that uses the | ||||
| library".  The executable is therefore covered by this License. | ||||
| Section 6 states terms for distribution of such executables. | ||||
|  | ||||
|   When a "work that uses the Library" uses material from a header file | ||||
| that is part of the Library, the object code for the work may be a | ||||
| derivative work of the Library even though the source code is not. | ||||
| Whether this is true is especially significant if the work can be | ||||
| linked without the Library, or if the work is itself a library.  The | ||||
| threshold for this to be true is not precisely defined by law. | ||||
|  | ||||
|   If such an object file uses only numerical parameters, data | ||||
| structure layouts and accessors, and small macros and small inline | ||||
| functions (ten lines or less in length), then the use of the object | ||||
| file is unrestricted, regardless of whether it is legally a derivative | ||||
| work.  (Executables containing this object code plus portions of the | ||||
| Library will still fall under Section 6.) | ||||
|  | ||||
|   Otherwise, if the work is a derivative of the Library, you may | ||||
| distribute the object code for the work under the terms of Section 6. | ||||
| Any executables containing that work also fall under Section 6, | ||||
| whether or not they are linked directly with the Library itself. | ||||
|  | ||||
|   6. As an exception to the Sections above, you may also combine or | ||||
| link a "work that uses the Library" with the Library to produce a | ||||
| work containing portions of the Library, and distribute that work | ||||
| under terms of your choice, provided that the terms permit | ||||
| modification of the work for the customer's own use and reverse | ||||
| engineering for debugging such modifications. | ||||
|  | ||||
|   You must give prominent notice with each copy of the work that the | ||||
| Library is used in it and that the Library and its use are covered by | ||||
| this License.  You must supply a copy of this License.  If the work | ||||
| during execution displays copyright notices, you must include the | ||||
| copyright notice for the Library among them, as well as a reference | ||||
| directing the user to the copy of this License.  Also, you must do one | ||||
| of these things: | ||||
|  | ||||
|     a) Accompany the work with the complete corresponding | ||||
|     machine-readable source code for the Library including whatever | ||||
|     changes were used in the work (which must be distributed under | ||||
|     Sections 1 and 2 above); and, if the work is an executable linked | ||||
|     with the Library, with the complete machine-readable "work that | ||||
|     uses the Library", as object code and/or source code, so that the | ||||
|     user can modify the Library and then relink to produce a modified | ||||
|     executable containing the modified Library.  (It is understood | ||||
|     that the user who changes the contents of definitions files in the | ||||
|     Library will not necessarily be able to recompile the application | ||||
|     to use the modified definitions.) | ||||
|  | ||||
|     b) Use a suitable shared library mechanism for linking with the | ||||
|     Library.  A suitable mechanism is one that (1) uses at run time a | ||||
|     copy of the library already present on the user's computer system, | ||||
|     rather than copying library functions into the executable, and (2) | ||||
|     will operate properly with a modified version of the library, if | ||||
|     the user installs one, as long as the modified version is | ||||
|     interface-compatible with the version that the work was made with. | ||||
|  | ||||
|     c) Accompany the work with a written offer, valid for at | ||||
|     least three years, to give the same user the materials | ||||
|     specified in Subsection 6a, above, for a charge no more | ||||
|     than the cost of performing this distribution. | ||||
|  | ||||
|     d) If distribution of the work is made by offering access to copy | ||||
|     from a designated place, offer equivalent access to copy the above | ||||
|     specified materials from the same place. | ||||
|  | ||||
|     e) Verify that the user has already received a copy of these | ||||
|     materials or that you have already sent this user a copy. | ||||
|  | ||||
|   For an executable, the required form of the "work that uses the | ||||
| Library" must include any data and utility programs needed for | ||||
| reproducing the executable from it.  However, as a special exception, | ||||
| the materials to be distributed need not include anything that is | ||||
| normally distributed (in either source or binary form) with the major | ||||
| components (compiler, kernel, and so on) of the operating system on | ||||
| which the executable runs, unless that component itself accompanies | ||||
| the executable. | ||||
|  | ||||
|   It may happen that this requirement contradicts the license | ||||
| restrictions of other proprietary libraries that do not normally | ||||
| accompany the operating system.  Such a contradiction means you cannot | ||||
| use both them and the Library together in an executable that you | ||||
| distribute. | ||||
|  | ||||
|   7. You may place library facilities that are a work based on the | ||||
| Library side-by-side in a single library together with other library | ||||
| facilities not covered by this License, and distribute such a combined | ||||
| library, provided that the separate distribution of the work based on | ||||
| the Library and of the other library facilities is otherwise | ||||
| permitted, and provided that you do these two things: | ||||
|  | ||||
|     a) Accompany the combined library with a copy of the same work | ||||
|     based on the Library, uncombined with any other library | ||||
|     facilities.  This must be distributed under the terms of the | ||||
|     Sections above. | ||||
|  | ||||
|     b) Give prominent notice with the combined library of the fact | ||||
|     that part of it is a work based on the Library, and explaining | ||||
|     where to find the accompanying uncombined form of the same work. | ||||
|  | ||||
|   8. You may not copy, modify, sublicense, link with, or distribute | ||||
| the Library except as expressly provided under this License.  Any | ||||
| attempt otherwise to copy, modify, sublicense, link with, or | ||||
| distribute the Library is void, and will automatically terminate your | ||||
| rights under this License.  However, parties who have received copies, | ||||
| or rights, from you under this License will not have their licenses | ||||
| terminated so long as such parties remain in full compliance. | ||||
|  | ||||
|   9. You are not required to accept this License, since you have not | ||||
| signed it.  However, nothing else grants you permission to modify or | ||||
| distribute the Library or its derivative works.  These actions are | ||||
| prohibited by law if you do not accept this License.  Therefore, by | ||||
| modifying or distributing the Library (or any work based on the | ||||
| Library), you indicate your acceptance of this License to do so, and | ||||
| all its terms and conditions for copying, distributing or modifying | ||||
| the Library or works based on it. | ||||
|  | ||||
|   10. Each time you redistribute the Library (or any work based on the | ||||
| Library), the recipient automatically receives a license from the | ||||
| original licensor to copy, distribute, link with or modify the Library | ||||
| subject to these terms and conditions.  You may not impose any further | ||||
| restrictions on the recipients' exercise of the rights granted herein. | ||||
| You are not responsible for enforcing compliance by third parties with | ||||
| this License. | ||||
|  | ||||
|   11. If, as a consequence of a court judgment or allegation of patent | ||||
| infringement or for any other reason (not limited to patent issues), | ||||
| conditions are imposed on you (whether by court order, agreement or | ||||
| otherwise) that contradict the conditions of this License, they do not | ||||
| excuse you from the conditions of this License.  If you cannot | ||||
| distribute so as to satisfy simultaneously your obligations under this | ||||
| License and any other pertinent obligations, then as a consequence you | ||||
| may not distribute the Library at all.  For example, if a patent | ||||
| license would not permit royalty-free redistribution of the Library by | ||||
| all those who receive copies directly or indirectly through you, then | ||||
| the only way you could satisfy both it and this License would be to | ||||
| refrain entirely from distribution of the Library. | ||||
|  | ||||
| If any portion of this section is held invalid or unenforceable under any | ||||
| particular circumstance, the balance of the section is intended to apply, | ||||
| and the section as a whole is intended to apply in other circumstances. | ||||
|  | ||||
| It is not the purpose of this section to induce you to infringe any | ||||
| patents or other property right claims or to contest validity of any | ||||
| such claims; this section has the sole purpose of protecting the | ||||
| integrity of the free software distribution system which is | ||||
| implemented by public license practices.  Many people have made | ||||
| generous contributions to the wide range of software distributed | ||||
| through that system in reliance on consistent application of that | ||||
| system; it is up to the author/donor to decide if he or she is willing | ||||
| to distribute software through any other system and a licensee cannot | ||||
| impose that choice. | ||||
|  | ||||
| This section is intended to make thoroughly clear what is believed to | ||||
| be a consequence of the rest of this License. | ||||
|  | ||||
|   12. If the distribution and/or use of the Library is restricted in | ||||
| certain countries either by patents or by copyrighted interfaces, the | ||||
| original copyright holder who places the Library under this License may add | ||||
| an explicit geographical distribution limitation excluding those countries, | ||||
| so that distribution is permitted only in or among countries not thus | ||||
| excluded.  In such case, this License incorporates the limitation as if | ||||
| written in the body of this License. | ||||
|  | ||||
|   13. The Free Software Foundation may publish revised and/or new | ||||
| versions of the Lesser General Public License from time to time. | ||||
| Such new versions will be similar in spirit to the present version, | ||||
| but may differ in detail to address new problems or concerns. | ||||
|  | ||||
| Each version is given a distinguishing version number.  If the Library | ||||
| specifies a version number of this License which applies to it and | ||||
| "any later version", you have the option of following the terms and | ||||
| conditions either of that version or of any later version published by | ||||
| the Free Software Foundation.  If the Library does not specify a | ||||
| license version number, you may choose any version ever published by | ||||
| the Free Software Foundation. | ||||
|  | ||||
|   14. If you wish to incorporate parts of the Library into other free | ||||
| programs whose distribution conditions are incompatible with these, | ||||
| write to the author to ask for permission.  For software which is | ||||
| copyrighted by the Free Software Foundation, write to the Free | ||||
| Software Foundation; we sometimes make exceptions for this.  Our | ||||
| decision will be guided by the two goals of preserving the free status | ||||
| of all derivatives of our free software and of promoting the sharing | ||||
| and reuse of software generally. | ||||
|  | ||||
|           NO WARRANTY | ||||
|  | ||||
|   15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO | ||||
| WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. | ||||
| EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR | ||||
| OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY | ||||
| KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE | ||||
| LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME | ||||
| THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||||
|  | ||||
|   16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN | ||||
| WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY | ||||
| AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU | ||||
| FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR | ||||
| CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE | ||||
| LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING | ||||
| RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A | ||||
| FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF | ||||
| SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | ||||
| DAMAGES. | ||||
|  | ||||
|          END OF TERMS AND CONDITIONS | ||||
|  | ||||
|            How to Apply These Terms to Your New Libraries | ||||
|  | ||||
|   If you develop a new library, and you want it to be of the greatest | ||||
| possible use to the public, we recommend making it free software that | ||||
| everyone can redistribute and change.  You can do so by permitting | ||||
| redistribution under these terms (or, alternatively, under the terms of the | ||||
| ordinary General Public License). | ||||
|  | ||||
|   To apply these terms, attach the following notices to the library.  It is | ||||
| safest to attach them to the start of each source file to most effectively | ||||
| convey the exclusion of warranty; and each file should have at least the | ||||
| "copyright" line and a pointer to where the full notice is found. | ||||
|  | ||||
|     <one line to give the library's name and a brief idea of what it does.> | ||||
|     Copyright (C) <year>  <name of author> | ||||
|  | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
|  | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
| You should also get your employer (if you work as a programmer) or your | ||||
| school, if any, to sign a "copyright disclaimer" for the library, if | ||||
| necessary.  Here is a sample; alter the names: | ||||
|  | ||||
|   Yoyodyne, Inc., hereby disclaims all copyright interest in the | ||||
|   library `Frob' (a library for tweaking knobs) written by James Random Hacker. | ||||
|  | ||||
|   <signature of Ty Coon>, 1 April 1990 | ||||
|   Ty Coon, President of Vice | ||||
|  | ||||
| That's all there is to it! | ||||
							
								
								
									
										5
									
								
								Modules/TinyMCE/JoplinLists/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								Modules/TinyMCE/JoplinLists/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # TinyMCE Joplin Lists Plugin | ||||
|  | ||||
| This is based on https://github.com/tinymce/tinymce/tree/59748a11303fb7cf00fdb8c9392dcb082ee9d965/modules/tinymce/src/plugins/lists | ||||
|  | ||||
| But with support for Joplin checkboxes. | ||||
							
								
								
									
										57
									
								
								Modules/TinyMCE/JoplinLists/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Modules/TinyMCE/JoplinLists/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| declare module 'tinymce/core/api/util/Tools' { | ||||
| 	const Tools:any; | ||||
|     export default Tools; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/Editor' { | ||||
|     export default interface Editor { | ||||
|     	on: Function, | ||||
|     	off: Function, | ||||
|     	execCommand: Function, | ||||
|     	getBody: Function, | ||||
|     	getParam: Function, | ||||
|     	fire: Function, | ||||
|     	nodeChanged: Function, | ||||
|     	selection: any, | ||||
|     	contentDocument: any, | ||||
|     	dom: any, | ||||
|     	schema: any, | ||||
|     	undoManager: any, | ||||
|     	ui: any, | ||||
|     } | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/dom/BookmarkManager' { | ||||
| 	const BookmarkManager:any; | ||||
| 	export default BookmarkManager; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/dom/DOMUtils' { | ||||
| 	const DOMUtils:any; | ||||
| 	export default DOMUtils; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/dom/RangeUtils' { | ||||
| 	const RangeUtils:any; | ||||
| 	export default RangeUtils; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/dom/TreeWalker' { | ||||
| 	const TreeWalker:any; | ||||
| 	export default TreeWalker; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/util/VK' { | ||||
| 	const VK:any; | ||||
| 	export default VK; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/dom/DomQuery' { | ||||
| 	const DomQuery:any; | ||||
| 	export default DomQuery; | ||||
| } | ||||
|  | ||||
| declare module 'tinymce/core/api/PluginManager' { | ||||
| 	const PluginManager:any; | ||||
| 	export default PluginManager; | ||||
| } | ||||
							
								
								
									
										54
									
								
								Modules/TinyMCE/JoplinLists/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Modules/TinyMCE/JoplinLists/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| { | ||||
|   "name": "joplin-tinymce-lists", | ||||
|   "version": "1.0.0", | ||||
|   "description": "", | ||||
|   "scripts": { | ||||
|     "prepublishOnly": "npm run lint && npm run build", | ||||
|     "lint": "tslint src/**/*.ts", | ||||
|     "build": "grunt", | ||||
|     "test": "bedrock-auto -b phantomjs -d src/test/ts/", | ||||
|     "test-manual": "bedrock -d src/test/ts/", | ||||
|     "start": "grunt webpack", | ||||
|     "buildAndStart": "yarn build && cd .. && cd .. && cd .. && cd ElectronClient && npm start" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": "Tiny Technologies Inc.", | ||||
|   "devDependencies": { | ||||
|     "@ephox/agar": "latest", | ||||
|     "@ephox/bedrock": "latest", | ||||
|     "@ephox/mcagar": "latest", | ||||
|     "@ephox/swag": "latest", | ||||
|     "@ephox/tslint-rules": "latest", | ||||
|     "awesome-typescript-loader": "^5.2.1", | ||||
|     "grunt": "^1.0.4", | ||||
|     "grunt-contrib-clean": "^2.0.0", | ||||
|     "grunt-contrib-concat": "^1.0.1", | ||||
|     "grunt-contrib-copy": "^1.0.0", | ||||
|     "grunt-contrib-uglify": "^4.0.0", | ||||
|     "grunt-shell": "^2.1.0", | ||||
|     "grunt-tslint": "^5.0.2", | ||||
|     "grunt-webpack": "^3.1.3", | ||||
|     "load-grunt-tasks": "^4.0.0", | ||||
|     "tinymce": "latest", | ||||
|     "ts-loader": "^5.3.0", | ||||
|     "tslib": "^1.9.3", | ||||
|     "tslint": "^5.11.0", | ||||
|     "typescript": "^3.1.6", | ||||
|     "webpack": "^4.25.1", | ||||
|     "webpack-livereload-plugin": "^2.1.1" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "lib/main", | ||||
|     "lib/demo", | ||||
|     "lib/test", | ||||
|     "src", | ||||
|     "tsconfig.json", | ||||
|     "readme.md", | ||||
|     "LEGAL.txt", | ||||
|     "LICENSE.txt" | ||||
|   ], | ||||
|   "main": "./lib/main/ts/api/Main.js", | ||||
|   "module": "./lib/main/ts/api/Main.js", | ||||
|   "types": "./lib/main/ts/api/Main.d.ts", | ||||
|   "license": "Apache-2.0" | ||||
| } | ||||
							
								
								
									
										16
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/Main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/Main.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import Plugin from './Plugin'; | ||||
|  | ||||
| Plugin(); | ||||
|  | ||||
| /******* | ||||
|  * DO NOT EXPORT ANYTHING | ||||
|  * | ||||
|  * IF YOU DO ROLLUP WILL LEAVE A GLOBAL ON THE PAGE | ||||
|  *******/ | ||||
							
								
								
									
										22
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/Plugin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/Plugin.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import PluginManager from 'tinymce/core/api/PluginManager'; | ||||
| import * as Api from './api/Api'; | ||||
| import * as Commands from './api/Commands'; | ||||
| import * as Keyboard from './core/Keyboard'; | ||||
| import * as Buttons from './ui/Buttons'; | ||||
|  | ||||
| export default function () { | ||||
|   PluginManager.add('joplinLists', function (editor) { | ||||
|     Keyboard.setup(editor); | ||||
|     Buttons.register(editor); | ||||
|     Commands.register(editor); | ||||
|  | ||||
|     return Api.get(editor); | ||||
|   }); | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Arr } from '@ephox/katamari'; | ||||
| import { Element} from '@ephox/sugar'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import { Indentation } from '../listModel/Indentation'; | ||||
| import { listIndentation } from '../listModel/ListsIndendation'; | ||||
| import { dlIndentation } from '../core/DlIndentation'; | ||||
| import * as Range from '../core/Range'; | ||||
| import * as Selection from '../core/Selection'; | ||||
|  | ||||
| const selectionIndentation = (editor: Editor, indentation: Indentation): boolean => { | ||||
|   const lists = Arr.map(Selection.getSelectedListRoots(editor), Element.fromDom); | ||||
|   const dlItems = Arr.map(Selection.getSelectedDlItems(editor), Element.fromDom); | ||||
|   let isHandled = false; | ||||
|  | ||||
|   if (lists.length || dlItems.length) { | ||||
|     const bookmark = editor.selection.getBookmark(); | ||||
|  | ||||
|     listIndentation(editor, lists, indentation); | ||||
|     dlIndentation(editor, indentation, dlItems); | ||||
|  | ||||
|     editor.selection.moveToBookmark(bookmark); | ||||
|     editor.selection.setRng(Range.normalizeRange(editor.selection.getRng())); | ||||
|     editor.nodeChanged(); | ||||
|     isHandled = true; | ||||
|   } | ||||
|  | ||||
|   return isHandled; | ||||
| }; | ||||
|  | ||||
| const indentListSelection = (editor: Editor): boolean => { | ||||
|   return selectionIndentation(editor, Indentation.Indent); | ||||
| }; | ||||
|  | ||||
| const outdentListSelection = (editor: Editor): boolean => { | ||||
|   return selectionIndentation(editor, Indentation.Outdent); | ||||
| }; | ||||
|  | ||||
| const flattenListSelection = (editor: Editor): boolean => { | ||||
|   return selectionIndentation(editor, Indentation.Flatten); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   indentListSelection, | ||||
|   outdentListSelection, | ||||
|   flattenListSelection | ||||
| }; | ||||
							
								
								
									
										307
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,307 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import BookmarkManager from 'tinymce/core/api/dom/BookmarkManager'; | ||||
| import Tools from 'tinymce/core/api/util/Tools'; | ||||
| import * as Bookmark from '../core/Bookmark'; | ||||
| import * as NodeType from '../core/NodeType'; | ||||
| import * as Selection from '../core/Selection'; | ||||
| import { HTMLElement } from '@ephox/dom-globals'; | ||||
| import { flattenListSelection } from './Indendation'; | ||||
| import { fireListEvent } from '../api/Events'; | ||||
| import { isCustomList } from '../core/Util'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import { listToggleActionFromListName } from '../core/ListAction'; | ||||
| import { findContainerListTypeFromElement } from '../listModel/JoplinListUtil'; | ||||
|  | ||||
| const updateListStyle = function (dom, el, detail) { | ||||
|   const type = detail['list-style-type'] ? detail['list-style-type'] : null; | ||||
|   dom.setStyle(el, 'list-style-type', type); | ||||
| }; | ||||
|  | ||||
| const setAttribs = function (elm, attrs) { | ||||
|   Tools.each(attrs, function (value, key) { | ||||
|     elm.setAttribute(key, value); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const updateListAttrs = function (dom, el, detail) { | ||||
|   setAttribs(el, detail['list-attributes']); | ||||
|   Tools.each(dom.select('li', el), function (li) { | ||||
|     setAttribs(li, detail['list-item-attributes']); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const updateListWithDetails = function (dom, el, detail) { | ||||
|   updateListStyle(dom, el, detail); | ||||
|   updateListAttrs(dom, el, detail); | ||||
|  | ||||
|   if (detail.listType === 'joplinChecklist') { | ||||
|     el.classList.add('joplin-checklist'); | ||||
|   } else { | ||||
|     el.classList.remove('joplin-checklist'); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const removeStyles = (dom, element: HTMLElement, styles: string[]) => { | ||||
|   Tools.each(styles, (style) => dom.setStyle(element, { [style]: '' })); | ||||
| }; | ||||
|  | ||||
| const getEndPointNode = function (editor, rng, start, root) { | ||||
|   let container, offset; | ||||
|  | ||||
|   container = rng[start ? 'startContainer' : 'endContainer']; | ||||
|   offset = rng[start ? 'startOffset' : 'endOffset']; | ||||
|  | ||||
|   // Resolve node index | ||||
|   if (container.nodeType === 1) { | ||||
|     container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; | ||||
|   } | ||||
|  | ||||
|   if (!start && NodeType.isBr(container.nextSibling)) { | ||||
|     container = container.nextSibling; | ||||
|   } | ||||
|  | ||||
|   while (container.parentNode !== root) { | ||||
|     if (NodeType.isTextBlock(editor, container)) { | ||||
|       return container; | ||||
|     } | ||||
|  | ||||
|     if (/^(TD|TH)$/.test(container.parentNode.nodeName)) { | ||||
|       return container; | ||||
|     } | ||||
|  | ||||
|     container = container.parentNode; | ||||
|   } | ||||
|  | ||||
|   return container; | ||||
| }; | ||||
|  | ||||
| const getSelectedTextBlocks = function (editor, rng, root) { | ||||
|   const textBlocks = [], dom = editor.dom; | ||||
|  | ||||
|   const startNode = getEndPointNode(editor, rng, true, root); | ||||
|   const endNode = getEndPointNode(editor, rng, false, root); | ||||
|   let block; | ||||
|   const siblings = []; | ||||
|  | ||||
|   for (let node = startNode; node; node = node.nextSibling) { | ||||
|     siblings.push(node); | ||||
|  | ||||
|     if (node === endNode) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Tools.each(siblings, function (node) { | ||||
|     if (NodeType.isTextBlock(editor, node)) { | ||||
|       textBlocks.push(node); | ||||
|       block = null; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (dom.isBlock(node) || NodeType.isBr(node)) { | ||||
|       if (NodeType.isBr(node)) { | ||||
|         dom.remove(node); | ||||
|       } | ||||
|  | ||||
|       block = null; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const nextSibling = node.nextSibling; | ||||
|     if (BookmarkManager.isBookmarkNode(node)) { | ||||
|       if (NodeType.isTextBlock(editor, nextSibling) || (!nextSibling && node.parentNode === root)) { | ||||
|         block = null; | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (!block) { | ||||
|       block = dom.create('p'); | ||||
|       node.parentNode.insertBefore(block, node); | ||||
|       textBlocks.push(block); | ||||
|     } | ||||
|  | ||||
|     block.appendChild(node); | ||||
|   }); | ||||
|  | ||||
|   return textBlocks; | ||||
| }; | ||||
|  | ||||
| const hasCompatibleStyle = function (dom, sib, detail) { | ||||
|   const sibStyle = dom.getStyle(sib, 'list-style-type'); | ||||
|   let detailStyle = detail ? detail['list-style-type'] : ''; | ||||
|  | ||||
|   detailStyle = detailStyle === null ? '' : detailStyle; | ||||
|  | ||||
|   return sibStyle === detailStyle; | ||||
| }; | ||||
|  | ||||
| const applyList = function (editor, listName: string, detail:any = {}) { | ||||
|   const rng = editor.selection.getRng(true); | ||||
|   let bookmark; | ||||
|   let listItemName = 'LI'; | ||||
|   const root = Selection.getClosestListRootElm(editor, editor.selection.getStart(true)); | ||||
|   const dom = editor.dom; | ||||
|  | ||||
|   if (dom.getContentEditable(editor.selection.getNode()) === 'false') { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   listName = listName.toUpperCase(); | ||||
|  | ||||
|   if (listName === 'DL') { | ||||
|     listItemName = 'DT'; | ||||
|   } | ||||
|  | ||||
|   bookmark = Bookmark.createBookmark(rng); | ||||
|  | ||||
|   Tools.each(getSelectedTextBlocks(editor, rng, root), function (block) { | ||||
|     let listBlock, sibling; | ||||
|  | ||||
|     sibling = block.previousSibling; | ||||
|     if (sibling && NodeType.isListNode(sibling) && sibling.nodeName === listName && hasCompatibleStyle(dom, sibling, detail)) { | ||||
|       listBlock = sibling; | ||||
|       block = dom.rename(block, listItemName); | ||||
|       sibling.appendChild(block); | ||||
|     } else { | ||||
|       listBlock = dom.create(listName); | ||||
|       if (detail.listType === 'joplinChecklist') { | ||||
|         listBlock.classList.add('joplin-checklist'); | ||||
|       } else { | ||||
|         listBlock.classList.remove('joplin-checklist'); | ||||
|       } | ||||
|       block.parentNode.insertBefore(listBlock, block); | ||||
|       listBlock.appendChild(block); | ||||
|       block = dom.rename(block, listItemName); | ||||
|     } | ||||
|  | ||||
|     removeStyles(dom, block, [ | ||||
|       'margin', 'margin-right', 'margin-bottom', 'margin-left', 'margin-top', | ||||
|       'padding', 'padding-right', 'padding-bottom', 'padding-left', 'padding-top', | ||||
|     ]); | ||||
|  | ||||
|     updateListWithDetails(dom, listBlock, detail); | ||||
|     mergeWithAdjacentLists(editor.dom, listBlock); | ||||
|   }); | ||||
|  | ||||
|   editor.selection.setRng(Bookmark.resolveBookmark(bookmark)); | ||||
| }; | ||||
|  | ||||
| const isValidLists = function (list1, list2) { | ||||
|   return list1 && list2 && NodeType.isListNode(list1) && list1.nodeName === list2.nodeName; | ||||
| }; | ||||
|  | ||||
| const hasSameListStyle = function (dom, list1, list2) { | ||||
|   const targetStyle = dom.getStyle(list1, 'list-style-type', true); | ||||
|   const style = dom.getStyle(list2, 'list-style-type', true); | ||||
|   return targetStyle === style; | ||||
| }; | ||||
|  | ||||
| const hasSameClasses = function (elm1, elm2) { | ||||
|   return elm1.className === elm2.className; | ||||
| }; | ||||
|  | ||||
| const shouldMerge = function (dom, list1, list2) { | ||||
|   return isValidLists(list1, list2) && hasSameListStyle(dom, list1, list2) && hasSameClasses(list1, list2); | ||||
| }; | ||||
|  | ||||
| const mergeWithAdjacentLists = function (dom, listBlock) { | ||||
|   let sibling, node; | ||||
|  | ||||
|   sibling = listBlock.nextSibling; | ||||
|   if (shouldMerge(dom, listBlock, sibling)) { | ||||
|     while ((node = sibling.firstChild)) { | ||||
|       listBlock.appendChild(node); | ||||
|     } | ||||
|  | ||||
|     dom.remove(sibling); | ||||
|   } | ||||
|  | ||||
|   sibling = listBlock.previousSibling; | ||||
|   if (shouldMerge(dom, listBlock, sibling)) { | ||||
|     while ((node = sibling.lastChild)) { | ||||
|       listBlock.insertBefore(node, listBlock.firstChild); | ||||
|     } | ||||
|  | ||||
|     dom.remove(sibling); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const updateList = function (editor: Editor, list, listName, detail) { | ||||
|   if (list.nodeName !== listName) { | ||||
|     const newList = editor.dom.rename(list, listName); | ||||
|     updateListWithDetails(editor.dom, newList, detail); | ||||
|     fireListEvent(editor, listToggleActionFromListName(listName), newList); | ||||
|   } else { | ||||
|     updateListWithDetails(editor.dom, list, detail); | ||||
|     fireListEvent(editor, listToggleActionFromListName(listName), list); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const toggleMultipleLists = function (editor, parentList, lists, listName, detail) { | ||||
|   if (parentList.nodeName === listName && !hasListStyleDetail(detail)) { | ||||
|     flattenListSelection(editor); | ||||
|   } else { | ||||
|     const bookmark = Bookmark.createBookmark(editor.selection.getRng(true)); | ||||
|  | ||||
|     Tools.each([parentList].concat(lists), function (elm) { | ||||
|       updateList(editor, elm, listName, detail); | ||||
|     }); | ||||
|  | ||||
|     editor.selection.setRng(Bookmark.resolveBookmark(bookmark)); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const hasListStyleDetail = function (detail) { | ||||
|   return 'list-style-type' in detail; | ||||
| }; | ||||
|  | ||||
| const toggleSingleList =  function (editor, parentList, listName, detail) { | ||||
|   if (parentList === editor.getBody()) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (parentList) { | ||||
|     const listType = findContainerListTypeFromElement(parentList); | ||||
|     if (parentList.nodeName === listName && !hasListStyleDetail(detail) && !isCustomList(parentList) && listType === detail.listType) { | ||||
|       flattenListSelection(editor); | ||||
|     } else { | ||||
|       const bookmark = Bookmark.createBookmark(editor.selection.getRng(true)); | ||||
|       updateListWithDetails(editor.dom, parentList, detail); | ||||
|       const newList = editor.dom.rename(parentList, listName); | ||||
|       mergeWithAdjacentLists(editor.dom, newList); | ||||
|       editor.selection.setRng(Bookmark.resolveBookmark(bookmark)); | ||||
|       fireListEvent(editor, listToggleActionFromListName(listName), newList); | ||||
|     } | ||||
|   } else { | ||||
|     applyList(editor, listName, detail); | ||||
|     fireListEvent(editor, listToggleActionFromListName(listName), parentList); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const toggleList = function (editor, listName, detail) { | ||||
|   const parentList = Selection.getParentList(editor); | ||||
|   const selectedSubLists = Selection.getSelectedSubLists(editor); | ||||
|  | ||||
|   detail = { | ||||
|     listType: 'regular', | ||||
|     ...detail, | ||||
|   } | ||||
|  | ||||
|   if (parentList && selectedSubLists.length > 0) { | ||||
|     toggleMultipleLists(editor, parentList, selectedSubLists, listName, detail); | ||||
|   } else { | ||||
|     toggleSingleList(editor, parentList, listName, detail); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   toggleList, | ||||
|   mergeWithAdjacentLists | ||||
| }; | ||||
							
								
								
									
										21
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Api.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import * as Delete from '../core/Delete'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
|  | ||||
| const get = function (editor: Editor) { | ||||
|   return { | ||||
|     backspaceDelete (isForward: boolean) { | ||||
|       Delete.backspaceDelete(editor, isForward); | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   get | ||||
| }; | ||||
							
								
								
									
										55
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Commands.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Commands.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import * as ToggleList from '../actions/ToggleList'; | ||||
| import { indentListSelection, outdentListSelection, flattenListSelection } from '../actions/Indendation'; | ||||
| import { addJoplinChecklistCommands } from '../listModel/JoplinListUtil'; | ||||
|  | ||||
| const queryListCommandState = function (editor, listName) { | ||||
|   return function () { | ||||
|     const parentList = editor.dom.getParent(editor.selection.getStart(), 'UL,OL,DL'); | ||||
|     return parentList && parentList.nodeName === listName; | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const register = function (editor) { | ||||
|   editor.on('BeforeExecCommand', function (e) { | ||||
|     const cmd = e.command.toLowerCase(); | ||||
|  | ||||
|     if (cmd === 'indent') { | ||||
|       indentListSelection(editor); | ||||
|     } else if (cmd === 'outdent') { | ||||
|       outdentListSelection(editor); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   editor.addCommand('InsertUnorderedList', function (ui, detail) { | ||||
|     ToggleList.toggleList(editor, 'UL', detail); | ||||
|   }); | ||||
|  | ||||
|   editor.addCommand('InsertOrderedList', function (ui, detail) { | ||||
|     ToggleList.toggleList(editor, 'OL', detail); | ||||
|   }); | ||||
|  | ||||
|   editor.addCommand('InsertDefinitionList', function (ui, detail) { | ||||
|     ToggleList.toggleList(editor, 'DL', detail); | ||||
|   }); | ||||
|  | ||||
|   editor.addCommand('RemoveList', () => { | ||||
|     flattenListSelection(editor); | ||||
|   }); | ||||
|  | ||||
|   editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState(editor, 'UL')); | ||||
|   editor.addQueryStateHandler('InsertOrderedList', queryListCommandState(editor, 'OL')); | ||||
|   editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState(editor, 'DL')); | ||||
|  | ||||
|   addJoplinChecklistCommands(editor, ToggleList); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   register | ||||
| }; | ||||
							
								
								
									
										4
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Events.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Events.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import { ListAction } from '../core/ListAction'; | ||||
|  | ||||
| export const fireListEvent = (editor: Editor, action: ListAction, element) => editor.fire('ListMutation', { action, element }); | ||||
							
								
								
									
										33
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/api/Settings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
|  | ||||
| const shouldIndentOnTab = function (editor: Editor) { | ||||
|   return editor.getParam('lists_indent_on_tab', true); | ||||
| }; | ||||
|  | ||||
| const getForcedRootBlock = (editor: Editor): string => { | ||||
|   const block = editor.getParam('forced_root_block', 'p'); | ||||
|   if (block === false) { | ||||
|     return ''; | ||||
|   } else if (block === true) { | ||||
|     return 'p'; | ||||
|   } else { | ||||
|     return block; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const getForcedRootBlockAttrs = (editor: Editor): Record<string, string> => { | ||||
|   return editor.getParam('forced_root_block_attrs', {}); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   shouldIndentOnTab, | ||||
|   getForcedRootBlock, | ||||
|   getForcedRootBlockAttrs | ||||
| }; | ||||
							
								
								
									
										126
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; | ||||
| import * as Range from './Range'; | ||||
|  | ||||
| const DOM = DOMUtils.DOM; | ||||
|  | ||||
| /** | ||||
|  * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with | ||||
|  * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans | ||||
|  * added to them since they can be restored after a dom operation. | ||||
|  * | ||||
|  * So this: <p><b>|</b><b>|</b></p> | ||||
|  * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p> | ||||
|  * | ||||
|  * @param  {DOMRange} rng DOM Range to get bookmark on. | ||||
|  * @return {Object} Bookmark object. | ||||
|  */ | ||||
| const createBookmark = function (rng) { | ||||
|   const bookmark = {}; | ||||
|  | ||||
|   const setupEndPoint = function (start?) { | ||||
|     let offsetNode, container, offset; | ||||
|  | ||||
|     container = rng[start ? 'startContainer' : 'endContainer']; | ||||
|     offset = rng[start ? 'startOffset' : 'endOffset']; | ||||
|  | ||||
|     if (container.nodeType === 1) { | ||||
|       offsetNode = DOM.create('span', { 'data-mce-type': 'bookmark' }); | ||||
|  | ||||
|       if (container.hasChildNodes()) { | ||||
|         offset = Math.min(offset, container.childNodes.length - 1); | ||||
|  | ||||
|         if (start) { | ||||
|           container.insertBefore(offsetNode, container.childNodes[offset]); | ||||
|         } else { | ||||
|           DOM.insertAfter(offsetNode, container.childNodes[offset]); | ||||
|         } | ||||
|       } else { | ||||
|         container.appendChild(offsetNode); | ||||
|       } | ||||
|  | ||||
|       container = offsetNode; | ||||
|       offset = 0; | ||||
|     } | ||||
|  | ||||
|     bookmark[start ? 'startContainer' : 'endContainer'] = container; | ||||
|     bookmark[start ? 'startOffset' : 'endOffset'] = offset; | ||||
|   }; | ||||
|  | ||||
|   setupEndPoint(true); | ||||
|  | ||||
|   if (!rng.collapsed) { | ||||
|     setupEndPoint(); | ||||
|   } | ||||
|  | ||||
|   return bookmark; | ||||
| }; | ||||
|  | ||||
| const resolveBookmark = function (bookmark) { | ||||
|   function restoreEndPoint(start?) { | ||||
|     let container, offset, node; | ||||
|  | ||||
|     const nodeIndex = function (container) { | ||||
|       let node = container.parentNode.firstChild, idx = 0; | ||||
|  | ||||
|       while (node) { | ||||
|         if (node === container) { | ||||
|           return idx; | ||||
|         } | ||||
|  | ||||
|         // Skip data-mce-type=bookmark nodes | ||||
|         if (node.nodeType !== 1 || node.getAttribute('data-mce-type') !== 'bookmark') { | ||||
|           idx++; | ||||
|         } | ||||
|  | ||||
|         node = node.nextSibling; | ||||
|       } | ||||
|  | ||||
|       return -1; | ||||
|     }; | ||||
|  | ||||
|     container = node = bookmark[start ? 'startContainer' : 'endContainer']; | ||||
|     offset = bookmark[start ? 'startOffset' : 'endOffset']; | ||||
|  | ||||
|     if (!container) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (container.nodeType === 1) { | ||||
|       offset = nodeIndex(container); | ||||
|       container = container.parentNode; | ||||
|       DOM.remove(node); | ||||
|  | ||||
|       if (!container.hasChildNodes() && DOM.isBlock(container)) { | ||||
|         container.appendChild(DOM.create('br')); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     bookmark[start ? 'startContainer' : 'endContainer'] = container; | ||||
|     bookmark[start ? 'startOffset' : 'endOffset'] = offset; | ||||
|   } | ||||
|  | ||||
|   restoreEndPoint(true); | ||||
|   restoreEndPoint(); | ||||
|  | ||||
|   const rng = DOM.createRng(); | ||||
|  | ||||
|   rng.setStart(bookmark.startContainer, bookmark.startOffset); | ||||
|  | ||||
|   if (bookmark.endContainer) { | ||||
|     rng.setEnd(bookmark.endContainer, bookmark.endOffset); | ||||
|   } | ||||
|  | ||||
|   return Range.normalizeRange(rng); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   createBookmark, | ||||
|   resolveBookmark | ||||
| }; | ||||
							
								
								
									
										285
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										285
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Delete.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,285 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| interface DOMUtils { | ||||
|   isBlock: Function, | ||||
|   remove: Function, | ||||
|   $: Function, | ||||
|   getParent: Function, | ||||
|   getParents: Function, | ||||
|   getRoot: Function, | ||||
|   isEmpty: Function, | ||||
| } | ||||
|  | ||||
| import { Element, HTMLLIElement, Node, Range as DomRange } from '@ephox/dom-globals'; | ||||
| import { Arr } from '@ephox/katamari'; | ||||
| import { Compare, Element as SugarElement } from '@ephox/sugar'; | ||||
| // import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; | ||||
| import RangeUtils from 'tinymce/core/api/dom/RangeUtils'; | ||||
| import TreeWalker from 'tinymce/core/api/dom/TreeWalker'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import VK from 'tinymce/core/api/util/VK'; | ||||
| import { flattenListSelection, outdentListSelection } from '../actions/Indendation'; | ||||
| import * as ToggleList from '../actions/ToggleList'; | ||||
| import * as Bookmark from './Bookmark'; | ||||
| import * as NodeType from './NodeType'; | ||||
| import * as NormalizeLists from './NormalizeLists'; | ||||
| import * as Range from './Range'; | ||||
| import * as Selection from './Selection'; | ||||
|  | ||||
| const findNextCaretContainer = function (editor: Editor, rng: DomRange, isForward: Boolean, root: Node): Node { | ||||
|   let node = rng.startContainer; | ||||
|   const offset = rng.startOffset; | ||||
|  | ||||
|   if (NodeType.isTextNode(node) && (isForward ? offset < node.data.length : offset > 0)) { | ||||
|     return node; | ||||
|   } | ||||
|  | ||||
|   const nonEmptyBlocks = editor.schema.getNonEmptyElements(); | ||||
|   if (node.nodeType === 1) { | ||||
|     node = RangeUtils.getNode(node, offset); | ||||
|   } | ||||
|  | ||||
|   const walker = new TreeWalker(node, root); | ||||
|  | ||||
|   // Delete at <li>|<br></li> then jump over the bogus br | ||||
|   if (isForward) { | ||||
|     if (NodeType.isBogusBr(editor.dom, node)) { | ||||
|       walker.next(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   while ((node = walker[isForward ? 'next' : 'prev2']())) { | ||||
|     if (node.nodeName === 'LI' && !node.hasChildNodes()) { | ||||
|       return node; | ||||
|     } | ||||
|  | ||||
|     if (nonEmptyBlocks[node.nodeName]) { | ||||
|       return node; | ||||
|     } | ||||
|  | ||||
|     if (NodeType.isTextNode(node) && node.data.length > 0) { | ||||
|       return node; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const hasOnlyOneBlockChild = function (dom: DOMUtils, elm: Element): boolean { | ||||
|   const childNodes = elm.childNodes; | ||||
|   return childNodes.length === 1 && !NodeType.isListNode(childNodes[0]) && dom.isBlock(childNodes[0]); | ||||
| }; | ||||
|  | ||||
| const unwrapSingleBlockChild = function (dom: DOMUtils, elm: Element) { | ||||
|   if (hasOnlyOneBlockChild(dom, elm)) { | ||||
|     dom.remove(elm.firstChild, true); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const moveChildren = function (dom: DOMUtils, fromElm: Element, toElm: Element) { | ||||
|   let node, targetElm; | ||||
|  | ||||
|   targetElm = hasOnlyOneBlockChild(dom, toElm) ? toElm.firstChild : toElm; | ||||
|   unwrapSingleBlockChild(dom, fromElm); | ||||
|  | ||||
|   if (!NodeType.isEmpty(dom, fromElm, true)) { | ||||
|     while ((node = fromElm.firstChild)) { | ||||
|       targetElm.appendChild(node); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const mergeLiElements = function (dom: DOMUtils, fromElm: Element, toElm: Element) { | ||||
|   let node, listNode; | ||||
|   const ul = fromElm.parentNode; | ||||
|  | ||||
|   if (!NodeType.isChildOfBody(dom, fromElm) || !NodeType.isChildOfBody(dom, toElm)) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (NodeType.isListNode(toElm.lastChild)) { | ||||
|     listNode = toElm.lastChild; | ||||
|   } | ||||
|  | ||||
|   if (ul === toElm.lastChild) { | ||||
|     if (NodeType.isBr(ul.previousSibling)) { | ||||
|       dom.remove(ul.previousSibling); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   node = toElm.lastChild; | ||||
|   if (node && NodeType.isBr(node) && fromElm.hasChildNodes()) { | ||||
|     dom.remove(node); | ||||
|   } | ||||
|  | ||||
|   if (NodeType.isEmpty(dom, toElm, true)) { | ||||
|     dom.$(toElm).empty(); | ||||
|   } | ||||
|  | ||||
|   moveChildren(dom, fromElm, toElm); | ||||
|  | ||||
|   if (listNode) { | ||||
|     toElm.appendChild(listNode); | ||||
|   } | ||||
|  | ||||
|   const contains = Compare.contains(SugarElement.fromDom(toElm), SugarElement.fromDom(fromElm)); | ||||
|  | ||||
|   const nestedLists = contains ? dom.getParents(fromElm, NodeType.isListNode, toElm) : []; | ||||
|  | ||||
|   dom.remove(fromElm); | ||||
|  | ||||
|   Arr.each(nestedLists, (list) => { | ||||
|     if (NodeType.isEmpty(dom, list) && list !== dom.getRoot()) { | ||||
|       dom.remove(list); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const mergeIntoEmptyLi = function (editor: Editor, fromLi: HTMLLIElement, toLi: HTMLLIElement) { | ||||
|   editor.dom.$(toLi).empty(); | ||||
|   mergeLiElements(editor.dom, fromLi, toLi); | ||||
|   editor.selection.setCursorLocation(toLi); | ||||
| }; | ||||
|  | ||||
| const mergeForward = function (editor: Editor, rng: DomRange, fromLi: HTMLLIElement, toLi: HTMLLIElement) { | ||||
|   const dom = editor.dom; | ||||
|  | ||||
|   if (dom.isEmpty(toLi)) { | ||||
|     mergeIntoEmptyLi(editor, fromLi, toLi); | ||||
|   } else { | ||||
|     const bookmark = Bookmark.createBookmark(rng); | ||||
|     mergeLiElements(dom, fromLi, toLi); | ||||
|     editor.selection.setRng(Bookmark.resolveBookmark(bookmark)); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const mergeBackward = function (editor: Editor, rng: DomRange, fromLi: HTMLLIElement, toLi: HTMLLIElement) { | ||||
|   const bookmark = Bookmark.createBookmark(rng); | ||||
|   mergeLiElements(editor.dom, fromLi, toLi); | ||||
|   const resolvedBookmark = Bookmark.resolveBookmark(bookmark); | ||||
|   editor.selection.setRng(resolvedBookmark); | ||||
| }; | ||||
|  | ||||
| const backspaceDeleteFromListToListCaret = function (editor: Editor, isForward: boolean) { | ||||
|   const dom = editor.dom, selection = editor.selection; | ||||
|   const selectionStartElm = selection.getStart(); | ||||
|   const root = Selection.getClosestListRootElm(editor, selectionStartElm); | ||||
|   const li = dom.getParent(selection.getStart(), 'LI', root) as HTMLLIElement; | ||||
|  | ||||
|   if (li) { | ||||
|     const ul = li.parentNode; | ||||
|     if (ul === editor.getBody() && NodeType.isEmpty(dom, ul)) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     const rng = Range.normalizeRange(selection.getRng()); | ||||
|     const otherLi = dom.getParent(findNextCaretContainer(editor, rng, isForward, root), 'LI', root) as HTMLLIElement; | ||||
|  | ||||
|     if (otherLi && otherLi !== li) { | ||||
|       editor.undoManager.transact(() => { | ||||
|         if (isForward) { | ||||
|           mergeForward(editor, rng, otherLi, li); | ||||
|         } else { | ||||
|           if (NodeType.isFirstChild(li)) { | ||||
|             outdentListSelection(editor); | ||||
|           } else { | ||||
|             mergeBackward(editor, rng, li, otherLi); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       return true; | ||||
|     } else if (!otherLi) { | ||||
|       if (!isForward && rng.startOffset === 0 && rng.endOffset === 0) { | ||||
|         editor.undoManager.transact(() => { | ||||
|           flattenListSelection(editor); | ||||
|         }); | ||||
|  | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| }; | ||||
|  | ||||
| const removeBlock = function (dom: DOMUtils, block: Element, root: Node) { | ||||
|   const parentBlock = dom.getParent(block.parentNode, dom.isBlock, root); | ||||
|  | ||||
|   dom.remove(block); | ||||
|   if (parentBlock && dom.isEmpty(parentBlock)) { | ||||
|     dom.remove(parentBlock); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const backspaceDeleteIntoListCaret = function (editor: Editor, isForward: boolean) { | ||||
|   const dom = editor.dom; | ||||
|   const selectionStartElm = editor.selection.getStart(); | ||||
|   const root = Selection.getClosestListRootElm(editor, selectionStartElm); | ||||
|   const block = dom.getParent(selectionStartElm, dom.isBlock, root); | ||||
|  | ||||
|   if (block && dom.isEmpty(block)) { | ||||
|     const rng = Range.normalizeRange(editor.selection.getRng()); | ||||
|     const otherLi = dom.getParent(findNextCaretContainer(editor, rng, isForward, root), 'LI', root); | ||||
|  | ||||
|     if (otherLi) { | ||||
|       editor.undoManager.transact(function () { | ||||
|         removeBlock(dom, block, root); | ||||
|         ToggleList.mergeWithAdjacentLists(dom, otherLi.parentNode); | ||||
|         editor.selection.select(otherLi, true); | ||||
|         editor.selection.collapse(isForward); | ||||
|       }); | ||||
|  | ||||
|       return true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| }; | ||||
|  | ||||
| const backspaceDeleteCaret = function (editor: Editor, isForward: boolean): boolean { | ||||
|   return backspaceDeleteFromListToListCaret(editor, isForward) || backspaceDeleteIntoListCaret(editor, isForward); | ||||
| }; | ||||
|  | ||||
| const backspaceDeleteRange = function (editor: Editor): boolean { | ||||
|   const selectionStartElm = editor.selection.getStart(); | ||||
|   const root = Selection.getClosestListRootElm(editor, selectionStartElm); | ||||
|   const startListParent = editor.dom.getParent(selectionStartElm, 'LI,DT,DD', root); | ||||
|  | ||||
|   if (startListParent || Selection.getSelectedListItems(editor).length > 0) { | ||||
|     editor.undoManager.transact(function () { | ||||
|       editor.execCommand('Delete'); | ||||
|       NormalizeLists.normalizeLists(editor.dom, editor.getBody()); | ||||
|     }); | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| }; | ||||
|  | ||||
| const backspaceDelete = function (editor: Editor, isForward: boolean): boolean { | ||||
|   return editor.selection.isCollapsed() ? backspaceDeleteCaret(editor, isForward) : backspaceDeleteRange(editor); | ||||
| }; | ||||
|  | ||||
| const setup = function (editor: Editor) { | ||||
|   editor.on('keydown', function (e) { | ||||
|     if (e.keyCode === VK.BACKSPACE) { | ||||
|       if (backspaceDelete(editor, false)) { | ||||
|         e.preventDefault(); | ||||
|       } | ||||
|     } else if (e.keyCode === VK.DELETE) { | ||||
|       if (backspaceDelete(editor, true)) { | ||||
|         e.preventDefault(); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   setup, | ||||
|   backspaceDelete | ||||
| }; | ||||
| @@ -0,0 +1,38 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import { Compare, Replication, Element, Traverse } from '@ephox/sugar'; | ||||
| import * as SplitList from './SplitList'; | ||||
| import { Indentation } from '../listModel/Indentation'; | ||||
| import { Arr } from '@ephox/katamari'; | ||||
|  | ||||
| const outdentDlItem = (editor: Editor, item: Element): void => { | ||||
|   if (Compare.is(item, 'dd')) { | ||||
|     Replication.mutate(item, 'dt'); | ||||
|   } else if (Compare.is(item, 'dt')) { | ||||
|     Traverse.parent(item).each((dl) => SplitList.splitList(editor, dl.dom(), item.dom())); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const indentDlItem = (item: Element): void => { | ||||
|   if (Compare.is(item, 'dt')) { | ||||
|     Replication.mutate(item, 'dd'); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const dlIndentation = (editor: Editor, indentation: Indentation, dlItems: Element[]) => { | ||||
|   if (indentation === Indentation.Indent) { | ||||
|     Arr.each(dlItems, indentDlItem); | ||||
|   } else { | ||||
|     Arr.each(dlItems, (item) => outdentDlItem(editor, item)); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   dlIndentation | ||||
| }; | ||||
							
								
								
									
										38
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import VK from 'tinymce/core/api/util/VK'; | ||||
| import * as Settings from '../api/Settings'; | ||||
| import * as Delete from './Delete'; | ||||
| import { outdentListSelection, indentListSelection } from '../actions/Indendation'; | ||||
|  | ||||
| const setupTabKey = function (editor) { | ||||
|   editor.on('keydown', function (e) { | ||||
|     // Check for tab but not ctrl/cmd+tab since it switches browser tabs | ||||
|     if (e.keyCode !== VK.TAB || VK.metaKeyPressed(e)) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     editor.undoManager.transact(() => { | ||||
|       if (e.shiftKey ? outdentListSelection(editor) : indentListSelection(editor)) { | ||||
|         e.preventDefault(); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const setup = function (editor) { | ||||
|   if (Settings.shouldIndentOnTab(editor)) { | ||||
|     setupTabKey(editor); | ||||
|   } | ||||
|  | ||||
|   Delete.setup(editor); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   setup | ||||
| }; | ||||
							
								
								
									
										15
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/ListAction.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/ListAction.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| export const enum ListAction { | ||||
|   ToggleUlList = 'ToggleUlList', | ||||
|   ToggleOlList = 'ToggleOlList', | ||||
|   ToggleDLList = 'ToggleDLList', | ||||
|   IndentList = 'IndentList', | ||||
|   OutdentList = 'OutdentList' | ||||
| } | ||||
|  | ||||
| export const listToggleActionFromListName = (listName: 'UL' | 'OL' | 'DL'): ListAction => { | ||||
|   switch (listName) { | ||||
|     case 'UL': return ListAction.ToggleUlList; | ||||
|     case 'OL': return ListAction.ToggleOlList; | ||||
|     case 'DL': return ListAction.ToggleDLList; | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										95
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/NodeType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/NodeType.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Node, Text } from '@ephox/dom-globals'; | ||||
|  | ||||
| const isTextNode = function (node: Node): node is Text { | ||||
|   return node && node.nodeType === 3; | ||||
| }; | ||||
|  | ||||
| const isListNode = function (node: Node) { | ||||
|   return node && (/^(OL|UL|DL)$/).test(node.nodeName); | ||||
| }; | ||||
|  | ||||
| const isOlUlNode = function (node: Node) { | ||||
|   return node && (/^(OL|UL)$/).test(node.nodeName); | ||||
| }; | ||||
|  | ||||
| const isListItemNode = function (node: Node) { | ||||
|   return node && /^(LI|DT|DD)$/.test(node.nodeName); | ||||
| }; | ||||
|  | ||||
| const isDlItemNode = function (node: Node) { | ||||
|   return node && /^(DT|DD)$/.test(node.nodeName); | ||||
| }; | ||||
|  | ||||
| const isTableCellNode = function (node: Node) { | ||||
|   return node && /^(TH|TD)$/.test(node.nodeName); | ||||
| }; | ||||
|  | ||||
| const isBr = function (node: Node) { | ||||
|   return node && node.nodeName === 'BR'; | ||||
| }; | ||||
|  | ||||
| const isFirstChild = function (node: Node) { | ||||
|   return node.parentNode.firstChild === node; | ||||
| }; | ||||
|  | ||||
| const isLastChild = function (node: Node) { | ||||
|   return node.parentNode.lastChild === node; | ||||
| }; | ||||
|  | ||||
| const isTextBlock = function (editor, node: Node) { | ||||
|   return node && !!editor.schema.getTextBlockElements()[node.nodeName]; | ||||
| }; | ||||
|  | ||||
| const isBlock = function (node: Node, blockElements) { | ||||
|   return node && node.nodeName in blockElements; | ||||
| }; | ||||
|  | ||||
| const isBogusBr = function (dom, node: Node) { | ||||
|   if (!isBr(node)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (dom.isBlock(node.nextSibling) && !isBr(node.previousSibling)) { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| }; | ||||
|  | ||||
| const isEmpty = function (dom, elm, keepBookmarks?) { | ||||
|   const empty = dom.isEmpty(elm); | ||||
|  | ||||
|   if (keepBookmarks && dom.select('span[data-mce-type=bookmark]', elm).length > 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return empty; | ||||
| }; | ||||
|  | ||||
| const isChildOfBody = function (dom, elm) { | ||||
|   return dom.isChildOf(elm, dom.getRoot()); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   isTextNode, | ||||
|   isListNode, | ||||
|   isOlUlNode, | ||||
|   isDlItemNode, | ||||
|   isListItemNode, | ||||
|   isTableCellNode, | ||||
|   isBr, | ||||
|   isFirstChild, | ||||
|   isLastChild, | ||||
|   isTextBlock, | ||||
|   isBlock, | ||||
|   isBogusBr, | ||||
|   isEmpty, | ||||
|   isChildOfBody | ||||
| }; | ||||
| @@ -0,0 +1,50 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; | ||||
| import Tools from 'tinymce/core/api/util/Tools'; | ||||
| import * as NodeType from './NodeType'; | ||||
|  | ||||
| const DOM = DOMUtils.DOM; | ||||
|  | ||||
| const normalizeList = function (dom, ul) { | ||||
|   let sibling; | ||||
|   const parentNode = ul.parentNode; | ||||
|  | ||||
|   // Move UL/OL to previous LI if it's the only child of a LI | ||||
|   if (parentNode.nodeName === 'LI' && parentNode.firstChild === ul) { | ||||
|     sibling = parentNode.previousSibling; | ||||
|     if (sibling && sibling.nodeName === 'LI') { | ||||
|       sibling.appendChild(ul); | ||||
|  | ||||
|       if (NodeType.isEmpty(dom, parentNode)) { | ||||
|         DOM.remove(parentNode); | ||||
|       } | ||||
|     } else { | ||||
|       DOM.setStyle(parentNode, 'listStyleType', 'none'); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4 | ||||
|   if (NodeType.isListNode(parentNode)) { | ||||
|     sibling = parentNode.previousSibling; | ||||
|     if (sibling && sibling.nodeName === 'LI') { | ||||
|       sibling.appendChild(ul); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const normalizeLists = function (dom, element) { | ||||
|   Tools.each(Tools.grep(dom.select('ol,ul', element)), function (ul) { | ||||
|     normalizeList(dom, ul); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   normalizeList, | ||||
|   normalizeLists | ||||
| }; | ||||
							
								
								
									
										58
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Range.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Range.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import RangeUtils from 'tinymce/core/api/dom/RangeUtils'; | ||||
| import * as NodeType from './NodeType'; | ||||
| import { Range, Node } from '@ephox/dom-globals'; | ||||
|  | ||||
| interface Point { | ||||
|   container: Node; | ||||
|   offset: number; | ||||
| } | ||||
|  | ||||
| const getNormalizedPoint = (container: Node, offset: number): Point => { | ||||
|   if (NodeType.isTextNode(container)) { | ||||
|     return { container, offset }; | ||||
|   } | ||||
|  | ||||
|   const node = RangeUtils.getNode(container, offset); | ||||
|   if (NodeType.isTextNode(node)) { | ||||
|     return { | ||||
|       container: node, | ||||
|       offset: offset >= container.childNodes.length ? node.data.length : 0 | ||||
|     }; | ||||
|   } else if (node.previousSibling && NodeType.isTextNode(node.previousSibling)) { | ||||
|     return { | ||||
|       container: node.previousSibling, | ||||
|       offset: node.previousSibling.data.length | ||||
|     }; | ||||
|   } else if (node.nextSibling && NodeType.isTextNode(node.nextSibling)) { | ||||
|     return { | ||||
|       container: node.nextSibling, | ||||
|       offset: 0 | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   return { container, offset }; | ||||
| }; | ||||
|  | ||||
| const normalizeRange = (rng: Range): Range => { | ||||
|   const outRng = rng.cloneRange(); | ||||
|  | ||||
|   const rangeStart = getNormalizedPoint(rng.startContainer, rng.startOffset); | ||||
|   outRng.setStart(rangeStart.container, rangeStart.offset); | ||||
|  | ||||
|   const rangeEnd = getNormalizedPoint(rng.endContainer, rng.endOffset); | ||||
|   outRng.setEnd(rangeEnd.container, rangeEnd.offset); | ||||
|  | ||||
|   return outRng; | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   getNormalizedPoint, | ||||
|   normalizeRange | ||||
| }; | ||||
							
								
								
									
										108
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Selection.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Selection.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Node } from '@ephox/dom-globals'; | ||||
| import { Arr, Option } from '@ephox/katamari'; | ||||
| import { HTMLElement } from '@ephox/sand'; | ||||
| import DomQuery from 'tinymce/core/api/dom/DomQuery'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import Tools from 'tinymce/core/api/util/Tools'; | ||||
| import * as NodeType from './NodeType'; | ||||
|  | ||||
| const getParentList = function (editor) { | ||||
|   const selectionStart = editor.selection.getStart(true); | ||||
|  | ||||
|   return editor.dom.getParent(selectionStart, 'OL,UL,DL', getClosestListRootElm(editor, selectionStart)); | ||||
| }; | ||||
|  | ||||
| const isParentListSelected = function (parentList, selectedBlocks) { | ||||
|   return parentList && selectedBlocks.length === 1 && selectedBlocks[0] === parentList; | ||||
| }; | ||||
|  | ||||
| const findSubLists = function (parentList) { | ||||
|   return Tools.grep(parentList.querySelectorAll('ol,ul,dl'), function (elm: Node) { | ||||
|     return NodeType.isListNode(elm); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const getSelectedSubLists = function (editor) { | ||||
|   const parentList = getParentList(editor); | ||||
|   const selectedBlocks = editor.selection.getSelectedBlocks(); | ||||
|  | ||||
|   if (isParentListSelected(parentList, selectedBlocks)) { | ||||
|     return findSubLists(parentList); | ||||
|   } else { | ||||
|     return Tools.grep(selectedBlocks, function (elm: Node) { | ||||
|       return NodeType.isListNode(elm) && parentList !== elm; | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const findParentListItemsNodes = function (editor, elms) { | ||||
|   const listItemsElms = Tools.map(elms, function (elm) { | ||||
|     const parentLi = editor.dom.getParent(elm, 'li,dd,dt', getClosestListRootElm(editor, elm)); | ||||
|  | ||||
|     return parentLi ? parentLi : elm; | ||||
|   }); | ||||
|  | ||||
|   return DomQuery.unique(listItemsElms); | ||||
| }; | ||||
|  | ||||
| const getSelectedListItems = function (editor) { | ||||
|   const selectedBlocks = editor.selection.getSelectedBlocks(); | ||||
|   return Tools.grep(findParentListItemsNodes(editor, selectedBlocks), function (block) { | ||||
|     return NodeType.isListItemNode(block); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const getSelectedDlItems = (editor: Editor): Node[] => { | ||||
|   return Arr.filter(getSelectedListItems(editor), NodeType.isDlItemNode); | ||||
| }; | ||||
|  | ||||
| const getClosestListRootElm = function (editor, elm) { | ||||
|   const parentTableCell = editor.dom.getParents(elm, 'TD,TH'); | ||||
|   const root = parentTableCell.length > 0 ? parentTableCell[0] : editor.getBody(); | ||||
|  | ||||
|   return root; | ||||
| }; | ||||
|  | ||||
| const findLastParentListNode = (editor: Editor, elm: Node): Option<Node> => { | ||||
|   const parentLists = editor.dom.getParents(elm, 'ol,ul', getClosestListRootElm(editor, elm)); | ||||
|   return Arr.last(parentLists); | ||||
| }; | ||||
|  | ||||
| const getSelectedLists = (editor: Editor): Node[] => { | ||||
|   const firstList = findLastParentListNode(editor, editor.selection.getStart()); | ||||
|   const subsequentLists = Arr.filter(editor.selection.getSelectedBlocks(), NodeType.isOlUlNode); | ||||
|  | ||||
|   return firstList.toArray().concat(subsequentLists); | ||||
| }; | ||||
|  | ||||
| const getSelectedListRoots = (editor: Editor): Node[] => { | ||||
|   const selectedLists = getSelectedLists(editor); | ||||
|   return getUniqueListRoots(editor, selectedLists); | ||||
| }; | ||||
|  | ||||
| const getUniqueListRoots = (editor: Editor, lists: Node[]): Node[] => { | ||||
|   const listRoots = Arr.map(lists, (list) => findLastParentListNode(editor, list).getOr(list)); | ||||
|   return DomQuery.unique(listRoots); | ||||
| }; | ||||
|  | ||||
| const isList = (editor: Editor): boolean => { | ||||
|   const list = getParentList(editor); | ||||
|   return HTMLElement.isPrototypeOf(list); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   isList, | ||||
|   getParentList, | ||||
|   getSelectedSubLists, | ||||
|   getSelectedListItems, | ||||
|   getClosestListRootElm, | ||||
|   getSelectedDlItems, | ||||
|   getSelectedListRoots | ||||
| }; | ||||
							
								
								
									
										59
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/SplitList.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/SplitList.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; | ||||
| import * as NodeType from './NodeType'; | ||||
| import { createTextBlock } from './TextBlock'; | ||||
| import Tools from 'tinymce/core/api/util/Tools'; | ||||
|  | ||||
| const DOM = DOMUtils.DOM; | ||||
|  | ||||
| const splitList = function (editor, ul, li) { | ||||
|   let tmpRng, fragment, bookmarks, node, newBlock; | ||||
|  | ||||
|   const removeAndKeepBookmarks = function (targetNode) { | ||||
|     Tools.each(bookmarks, function (node) { | ||||
|       targetNode.parentNode.insertBefore(node, li.parentNode); | ||||
|     }); | ||||
|  | ||||
|     DOM.remove(targetNode); | ||||
|   }; | ||||
|  | ||||
|   bookmarks = DOM.select('span[data-mce-type="bookmark"]', ul); | ||||
|   newBlock = createTextBlock(editor, li); | ||||
|   tmpRng = DOM.createRng(); | ||||
|   tmpRng.setStartAfter(li); | ||||
|   tmpRng.setEndAfter(ul); | ||||
|   fragment = tmpRng.extractContents(); | ||||
|  | ||||
|   for (node = fragment.firstChild; node; node = node.firstChild) { | ||||
|     if (node.nodeName === 'LI' && editor.dom.isEmpty(node)) { | ||||
|       DOM.remove(node); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!editor.dom.isEmpty(fragment)) { | ||||
|     DOM.insertAfter(fragment, ul); | ||||
|   } | ||||
|  | ||||
|   DOM.insertAfter(newBlock, ul); | ||||
|  | ||||
|   if (NodeType.isEmpty(editor.dom, li.parentNode)) { | ||||
|     removeAndKeepBookmarks(li.parentNode); | ||||
|   } | ||||
|  | ||||
|   DOM.remove(li); | ||||
|  | ||||
|   if (NodeType.isEmpty(editor.dom, ul)) { | ||||
|     DOM.remove(ul); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   splitList | ||||
| }; | ||||
							
								
								
									
										72
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import * as NodeType from './NodeType'; | ||||
| import { DocumentFragment, Node } from '@ephox/dom-globals'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import * as Settings from '../api/Settings'; | ||||
|  | ||||
| const createTextBlock = (editor: Editor, contentNode: Node): DocumentFragment => { | ||||
|   const dom = editor.dom; | ||||
|   const blockElements = editor.schema.getBlockElements(); | ||||
|   const fragment = dom.createFragment(); | ||||
|   const blockName = Settings.getForcedRootBlock(editor); | ||||
|   let node, textBlock, hasContentNode; | ||||
|  | ||||
|   if (blockName) { | ||||
|     textBlock = dom.create(blockName); | ||||
|  | ||||
|     if (textBlock.tagName === blockName.toUpperCase()) { | ||||
|       dom.setAttribs(textBlock, Settings.getForcedRootBlockAttrs(editor)); | ||||
|     } | ||||
|  | ||||
|     if (!NodeType.isBlock(contentNode.firstChild, blockElements)) { | ||||
|       fragment.appendChild(textBlock); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (contentNode) { | ||||
|     while ((node = contentNode.firstChild)) { | ||||
|       const nodeName = node.nodeName; | ||||
|  | ||||
|       if (!hasContentNode && (nodeName !== 'SPAN' || node.getAttribute('data-mce-type') !== 'bookmark')) { | ||||
|         hasContentNode = true; | ||||
|       } | ||||
|  | ||||
|       if (NodeType.isBlock(node, blockElements)) { | ||||
|         fragment.appendChild(node); | ||||
|         textBlock = null; | ||||
|       } else { | ||||
|         if (blockName) { | ||||
|           if (!textBlock) { | ||||
|             textBlock = dom.create(blockName); | ||||
|             fragment.appendChild(textBlock); | ||||
|           } | ||||
|  | ||||
|           textBlock.appendChild(node); | ||||
|         } else { | ||||
|           fragment.appendChild(node); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!blockName) { | ||||
|     fragment.appendChild(dom.create('br')); | ||||
|   } else { | ||||
|     // BR is needed in empty blocks | ||||
|     if (!hasContentNode) { | ||||
|       textBlock.appendChild(dom.create('br', { 'data-mce-bogus': '1' })); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return fragment; | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   createTextBlock | ||||
| }; | ||||
							
								
								
									
										3
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/core/Util.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| import { HTMLElement } from '@ephox/dom-globals'; | ||||
|  | ||||
| export const isCustomList = (list: HTMLElement) => /\btox\-/.test(list.className); | ||||
							
								
								
									
										109
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Document } from '@ephox/dom-globals'; | ||||
| import { Arr, Option, Options } from '@ephox/katamari'; | ||||
| import { Attr, Css, Element, Insert, InsertAll, Node, Replication } from '@ephox/sugar'; | ||||
| import { Entry } from './Entry'; | ||||
| import { ListType } from './Util'; | ||||
|  | ||||
| interface Segment { | ||||
|   list: Element; | ||||
|   item: Element; | ||||
| } | ||||
|  | ||||
| const joinSegment = (parent: Segment, child: Segment): void => { | ||||
|   Insert.append(parent.item, child.list); | ||||
| }; | ||||
|  | ||||
| const joinSegments = (segments: Segment[]): void => { | ||||
|   for (let i = 1; i < segments.length; i++) { | ||||
|     joinSegment(segments[i - 1], segments[i]); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const appendSegments = (head: Segment[], tail: Segment[]): void => { | ||||
|   Options.lift2(Arr.last(head), Arr.head(tail), joinSegment); | ||||
| }; | ||||
|  | ||||
| const createSegment = (scope: Document, listType: ListType): Segment => { | ||||
|   const segment: Segment = { | ||||
|     list: Element.fromTag(listType, scope), | ||||
|     item: Element.fromTag('li', scope) | ||||
|   }; | ||||
|   Insert.append(segment.list, segment.item); | ||||
|   return segment; | ||||
| }; | ||||
|  | ||||
| const createSegments = (scope: Document, entry: Entry, size: number): Segment[] => { | ||||
|   const segments: Segment[] = []; | ||||
|   for (let i = 0; i < size; i++) { | ||||
|     segments.push(createSegment(scope, entry.listType)); | ||||
|   } | ||||
|   return segments; | ||||
| }; | ||||
|  | ||||
| const populateSegments = (segments: Segment[], entry: Entry): void => { | ||||
|   for (let i = 0; i < segments.length - 1; i++) { | ||||
|     Css.set(segments[i].item, 'list-style-type', 'none'); | ||||
|   } | ||||
|   Arr.last(segments).each((segment) => { | ||||
|     Attr.setAll(segment.list, entry.listAttributes); | ||||
|     Attr.setAll(segment.item, entry.itemAttributes); | ||||
|     InsertAll.append(segment.item, entry.content); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const normalizeSegment = (segment: Segment, entry: Entry): void => { | ||||
|   if (Node.name(segment.list) !== entry.listType) { | ||||
|     segment.list = Replication.mutate(segment.list, entry.listType); | ||||
|   } | ||||
|   Attr.setAll(segment.list, entry.listAttributes); | ||||
| }; | ||||
|  | ||||
| const createItem = (scope: Document, attr: Record<string, any>, content: Element[]): Element => { | ||||
|   const item = Element.fromTag('li', scope); | ||||
|   Attr.setAll(item, attr); | ||||
|   InsertAll.append(item, content); | ||||
|   return item; | ||||
| }; | ||||
|  | ||||
| const appendItem = (segment: Segment, item: Element): void => { | ||||
|   Insert.append(segment.list, item); | ||||
|   segment.item = item; | ||||
| }; | ||||
|  | ||||
| const writeShallow = (scope: Document, cast: Segment[], entry: Entry): Segment[] => { | ||||
|   const newCast = cast.slice(0, entry.depth); | ||||
|  | ||||
|   Arr.last(newCast).each((segment) => { | ||||
|     const item = createItem(scope, entry.itemAttributes, entry.content); | ||||
|     appendItem(segment, item); | ||||
|     normalizeSegment(segment, entry); | ||||
|   }); | ||||
|  | ||||
|   return newCast; | ||||
| }; | ||||
|  | ||||
| const writeDeep = (scope: Document, cast: Segment[], entry: Entry): Segment[] => { | ||||
|   const segments = createSegments(scope, entry, entry.depth - cast.length); | ||||
|   joinSegments(segments); | ||||
|   populateSegments(segments, entry); | ||||
|   appendSegments(cast, segments); | ||||
|  | ||||
|   return cast.concat(segments); | ||||
| }; | ||||
|  | ||||
| const composeList = (scope: Document, entries: Entry[]): Option<Element> => { | ||||
|   const cast: Segment[] = Arr.foldl(entries, (cast, entry) => { | ||||
|     return entry.depth > cast.length ? writeDeep(scope, cast, entry) : writeShallow(scope, cast, entry); | ||||
|   }, []); | ||||
|  | ||||
|   return Arr.head(cast).map((segment) => segment.list); | ||||
| }; | ||||
|  | ||||
| export { composeList }; | ||||
							
								
								
									
										67
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Element, Traverse, Replication, Attr, Node } from '@ephox/sugar'; | ||||
| import { Arr, Option } from '@ephox/katamari'; | ||||
| import { hasLastChildList, ListType } from './Util'; | ||||
|  | ||||
| /* | ||||
| General workflow: Parse lists to entries -> Manipulate entries -> Compose entries to lists | ||||
|  | ||||
| 0-------1---2--------->Depth | ||||
|   <ol>                | | ||||
|     <li>a</li>        | Entry { depth: 1, content: [a], listType: ListType.OL, ... } | ||||
|     <li>b             | Entry { depth: 1, content: [b], listType: ListType.OL, ... } | ||||
|       <ul>            | | ||||
|         <li>c</li>    | Entry { depth: 2, content: [c], listType: ListType.UL, ... } | ||||
|       </ul>           | | ||||
|     </li>             | | ||||
|   </ol>               | | ||||
| 0-------1---2--------->Depth | ||||
| */ | ||||
|  | ||||
| export interface Entry { | ||||
|   depth: number; | ||||
|   content: Element[]; | ||||
|   isSelected: boolean; | ||||
|   listType: ListType; | ||||
|   listAttributes: Record<string, any>; | ||||
|   itemAttributes: Record<string, any>; | ||||
| } | ||||
|  | ||||
| const isIndented = (entry: Entry) => { | ||||
|   return entry.depth > 0; | ||||
| }; | ||||
|  | ||||
| const isSelected = (entry: Entry) => { | ||||
|   return entry.isSelected; | ||||
| }; | ||||
|  | ||||
| const cloneItemContent = (li: Element): Element[] => { | ||||
|   const children = Traverse.children(li); | ||||
|   const content = hasLastChildList(li) ? children.slice(0, -1) : children; | ||||
|   return Arr.map(content, Replication.deep); | ||||
| }; | ||||
|  | ||||
| const createEntry = (li: Element, depth: number, isSelected: boolean): Option<Entry> => { | ||||
|   return Traverse.parent(li).filter(Node.isElement).map((list) => { | ||||
|     return { | ||||
|       depth, | ||||
|       isSelected, | ||||
|       content: cloneItemContent(li), | ||||
|       itemAttributes: Attr.clone(li), | ||||
|       listAttributes: Attr.clone(list), | ||||
|       listType: Node.name(list) as ListType | ||||
|     }; | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   createEntry, | ||||
|   isIndented, | ||||
|   isSelected | ||||
| }; | ||||
| @@ -0,0 +1,29 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Entry } from './Entry'; | ||||
|  | ||||
| export const enum Indentation { | ||||
|   Indent = 'Indent', | ||||
|   Outdent = 'Outdent', | ||||
|   Flatten = 'Flatten' | ||||
| } | ||||
|  | ||||
| export const indentEntry = (indentation: Indentation, entry: Entry): void => { | ||||
|   switch (indentation) { | ||||
|     case Indentation.Indent: | ||||
|       entry.depth ++; | ||||
|       break; | ||||
|  | ||||
|     case Indentation.Outdent: | ||||
|       entry.depth --; | ||||
|       break; | ||||
|  | ||||
|     case Indentation.Flatten: | ||||
|       entry.depth = 0; | ||||
|   } | ||||
| }; | ||||
| @@ -0,0 +1,44 @@ | ||||
| export function isCheckboxListItem(element) { | ||||
|   return element.classList && element.classList.contains('joplin-checklist'); | ||||
| } | ||||
|  | ||||
| export function findContainerListTypeFromEvent(event) { | ||||
|   if (isCheckboxListItem(event.element)) return 'joplinChecklist'; | ||||
|  | ||||
|   for (const parent of event.parents) { | ||||
|     if (isCheckboxListItem(parent)) return 'joplinChecklist'; | ||||
|   } | ||||
|  | ||||
|   return 'regular'; | ||||
| } | ||||
|  | ||||
| export function findContainerListTypeFromElement(element) { | ||||
|   while (element) { | ||||
|     if (element.nodeName === 'UL' || element.nodName === 'OL') { | ||||
|       return isCheckboxListItem(element) ? 'joplinChecklist' : 'regular'; | ||||
|     } | ||||
|     element = element.parentNode; | ||||
|   } | ||||
|  | ||||
|   return 'regular'; | ||||
| } | ||||
|  | ||||
| export function addJoplinChecklistCommands(editor, ToggleList) { | ||||
|   editor.addCommand('ToggleJoplinChecklistItem', function (ui, detail) { | ||||
|     const element = detail.element; | ||||
|     if (element.nodeName !== 'LI') return; | ||||
|     const listType = findContainerListTypeFromElement(element); | ||||
|     if (listType === 'joplinChecklist') { | ||||
|       if (!element.classList || !element.classList.contains('checked')) { | ||||
|         element.classList.add('checked'); | ||||
|       } else { | ||||
|         element.classList.remove('checked'); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   editor.addCommand('InsertJoplinChecklist', function (ui, detail) { | ||||
|     detail = Object.assign({}, detail, { listType: 'joplinChecklist' }); | ||||
|     ToggleList.toggleList(editor, 'UL', detail); | ||||
|   }); | ||||
| } | ||||
| @@ -0,0 +1,68 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Arr, Fun, Option, Options } from '@ephox/katamari'; | ||||
| import { Element, Fragment, InsertAll, Remove } from '@ephox/sugar'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import { fireListEvent } from '../api/Events'; | ||||
| import { ListAction } from '../core/ListAction'; | ||||
| import * as Selection from '../core/Selection'; | ||||
| import { createTextBlock } from '../core/TextBlock'; | ||||
| import { composeList } from './ComposeList'; | ||||
| import { Entry, isIndented, isSelected } from './Entry'; | ||||
| import { Indentation, indentEntry } from './Indentation'; | ||||
| import { normalizeEntries } from './NormalizeEntries'; | ||||
| import { EntrySet, ItemSelection, parseLists } from './ParseLists'; | ||||
| import { hasFirstChildList } from './Util'; | ||||
|  | ||||
| const outdentedComposer = (editor: Editor, entries: Entry[]): Element[] => { | ||||
|   return Arr.map(entries, (entry) => { | ||||
|     const content = Fragment.fromElements(entry.content); | ||||
|     return Element.fromDom(createTextBlock(editor, content.dom())); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const indentedComposer = (editor: Editor, entries: Entry[]): Element[] => { | ||||
|   normalizeEntries(entries); | ||||
|   return composeList(editor.contentDocument, entries).toArray(); | ||||
| }; | ||||
|  | ||||
| const composeEntries = (editor, entries: Entry[]): Element[] => { | ||||
|   return Arr.bind(Arr.groupBy(entries, isIndented), (entries) => { | ||||
|     const groupIsIndented = Arr.head(entries).map(isIndented).getOr(false); | ||||
|     return groupIsIndented ? indentedComposer(editor, entries) : outdentedComposer(editor, entries); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const indentSelectedEntries = (entries: Entry[], indentation: Indentation): void => { | ||||
|   Arr.each(Arr.filter(entries, isSelected), (entry) => indentEntry(indentation, entry)); | ||||
| }; | ||||
|  | ||||
| const getItemSelection = (editor: Editor): Option<ItemSelection> => { | ||||
|   const selectedListItems = Arr.map(Selection.getSelectedListItems(editor), Element.fromDom); | ||||
|  | ||||
|   return Options.lift2( | ||||
|     Arr.find(selectedListItems, Fun.not(hasFirstChildList)), | ||||
|     Arr.find(Arr.reverse(selectedListItems), Fun.not(hasFirstChildList)), | ||||
|     (start, end) => ({ start, end })); | ||||
| }; | ||||
|  | ||||
| const listIndentation = (editor: Editor, lists: Element[], indentation: Indentation) => { | ||||
|   const entrySets: EntrySet[] = parseLists(lists, getItemSelection(editor)); | ||||
|  | ||||
|   Arr.each(entrySets, (entrySet) => { | ||||
|     indentSelectedEntries(entrySet.entries, indentation); | ||||
|     const composedLists = composeEntries(editor, entrySet.entries); | ||||
|     Arr.each(composedLists, (composedList) => { | ||||
|       fireListEvent(editor, indentation === Indentation.Indent ? ListAction.IndentList : ListAction.OutdentList, composedList.dom()); | ||||
|     }); | ||||
|     InsertAll.before(entrySet.sourceList, composedLists); | ||||
|     Remove.remove(entrySet.sourceList); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export { listIndentation }; | ||||
| @@ -0,0 +1,40 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Arr, Option } from '@ephox/katamari'; | ||||
| import { Entry } from './Entry'; | ||||
|  | ||||
| const cloneListProperties = (target: Entry, source: Entry): void => { | ||||
|   target.listType = source.listType; | ||||
|   target.listAttributes = { ...source.listAttributes }; | ||||
| }; | ||||
|  | ||||
| // Closest entry above in the same list | ||||
| const previousSiblingEntry = (entries: Entry[], start: number): Option<Entry> => { | ||||
|   const depth = entries[start].depth; | ||||
|   for (let i = start - 1; i >= 0; i--) { | ||||
|     if (entries[i].depth === depth) { | ||||
|       return Option.some(entries[i]); | ||||
|     } | ||||
|     if (entries[i].depth < depth) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   return Option.none(); | ||||
| }; | ||||
|  | ||||
| const normalizeEntries = (entries: Entry[]): void => { | ||||
|   Arr.each(entries, (entry, i) => { | ||||
|     previousSiblingEntry(entries, i).each((matchingEntry) => { | ||||
|       cloneListProperties(entry, matchingEntry); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   normalizeEntries | ||||
| }; | ||||
| @@ -0,0 +1,71 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Arr, Cell, Option } from '@ephox/katamari'; | ||||
| import { Compare, Element, Traverse } from '@ephox/sugar'; | ||||
| import { createEntry, Entry } from './Entry'; | ||||
| import { isList } from './Util'; | ||||
|  | ||||
| type Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, element: Element) => Entry[]; | ||||
|  | ||||
| export interface ItemSelection { | ||||
|   start: Element; | ||||
|   end: Element; | ||||
| } | ||||
|  | ||||
| export interface EntrySet { | ||||
|   entries: Entry[]; | ||||
|   sourceList: Element; | ||||
| } | ||||
|  | ||||
| const parseItem: Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, item: Element): Entry[] => { | ||||
|   return Traverse.firstChild(item).filter(isList).fold(() => { | ||||
|  | ||||
|     // Update selectionState (start) | ||||
|     itemSelection.each((selection) => { | ||||
|       if (Compare.eq(selection.start, item)) { | ||||
|         selectionState.set(true); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     const currentItemEntry = createEntry(item, depth, selectionState.get()); | ||||
|  | ||||
|     // Update selectionState (end) | ||||
|     itemSelection.each((selection) => { | ||||
|       if (Compare.eq(selection.end, item)) { | ||||
|         selectionState.set(false); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     const childListEntries: Entry[] = Traverse.lastChild(item) | ||||
|       .filter(isList) | ||||
|       .map((list) => parseList(depth, itemSelection, selectionState, list)) | ||||
|       .getOr([]); | ||||
|  | ||||
|     return currentItemEntry.toArray().concat(childListEntries); | ||||
|   }, (list) => parseList(depth, itemSelection, selectionState, list)); | ||||
| }; | ||||
|  | ||||
| const parseList: Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, list: Element): Entry[] => { | ||||
|   return Arr.bind(Traverse.children(list), (element) => { | ||||
|     const parser = isList(element) ? parseList : parseItem; | ||||
|     const newDepth = depth + 1; | ||||
|     return parser(newDepth, itemSelection, selectionState, element); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const parseLists = (lists: Element[], itemSelection: Option<ItemSelection>): EntrySet[] => { | ||||
|   const selectionState = Cell(false); | ||||
|   const initialDepth = 0; | ||||
|  | ||||
|   return Arr.map(lists, (list) => ({ | ||||
|     sourceList: list, | ||||
|     entries: parseList(initialDepth, itemSelection, selectionState, list) | ||||
|   })); | ||||
| }; | ||||
|  | ||||
| export { parseLists }; | ||||
							
								
								
									
										31
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/listModel/Util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/listModel/Util.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import { Element, Traverse, Compare } from '@ephox/sugar'; | ||||
|  | ||||
| export const enum ListType { | ||||
|   OL = 'ol', | ||||
|   UL = 'ul' | ||||
| } | ||||
|  | ||||
| const isList = (el: Element) => { | ||||
|   return Compare.is(el, 'OL,UL'); | ||||
| }; | ||||
|  | ||||
| const hasFirstChildList = (el: Element) => { | ||||
|   return Traverse.firstChild(el).map(isList).getOr(false); | ||||
| }; | ||||
|  | ||||
| const hasLastChildList = (el: Element) => { | ||||
|   return Traverse.lastChild(el).map(isList).getOr(false); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   isList, | ||||
|   hasFirstChildList, | ||||
|   hasLastChildList | ||||
| }; | ||||
							
								
								
									
										14
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
|     "compilerOptions": { | ||||
| 		"module": "amd", | ||||
| 		"target": "es6", | ||||
| 		"sourceMap": true, | ||||
| 		"outFile": "list.js" | ||||
| 	}, | ||||
|     "include": [ | ||||
|         "./**/*.ts" | ||||
| 	], | ||||
| 	"exclude": [ | ||||
| 		"**/node_modules" | ||||
| 	] | ||||
| } | ||||
							
								
								
									
										96
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Modules/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| /** | ||||
|  * Copyright (c) Tiny Technologies, Inc. All rights reserved. | ||||
|  * Licensed under the LGPL or a commercial license. | ||||
|  * For LGPL see License.txt in the project root for license information. | ||||
|  * For commercial licenses see https://www.tiny.cloud/ | ||||
|  */ | ||||
|  | ||||
| import Tools from 'tinymce/core/api/util/Tools'; | ||||
| import * as NodeType from '../core/NodeType'; | ||||
| import Editor from 'tinymce/core/api/Editor'; | ||||
| import { isCustomList } from '../core/Util'; | ||||
| import { findContainerListTypeFromEvent } from '../listModel/JoplinListUtil'; | ||||
|  | ||||
| const findIndex = function (list, predicate) { | ||||
|   for (let index = 0; index < list.length; index++) { | ||||
|     const element = list[index]; | ||||
|  | ||||
|     if (predicate(element)) { | ||||
|       return index; | ||||
|     } | ||||
|   } | ||||
|   return -1; | ||||
| }; | ||||
|  | ||||
| const listState = function (editor: Editor, listName, options:any = {}) { | ||||
|   options = { | ||||
|     listType: 'regular', | ||||
|     ...options, | ||||
|   }; | ||||
|  | ||||
|   return function (buttonApi) { | ||||
|     const nodeChangeHandler = (e) => { | ||||
|       const tableCellIndex = findIndex(e.parents, NodeType.isTableCellNode); | ||||
|       const parents = tableCellIndex !== -1 ? e.parents.slice(0, tableCellIndex) : e.parents; | ||||
|       const lists = Tools.grep(parents, NodeType.isListNode); | ||||
|       const listType = findContainerListTypeFromEvent(e); | ||||
|       buttonApi.setActive(listType === options.listType && lists.length > 0 && lists[0].nodeName === listName && !isCustomList(lists[0])); | ||||
|     }; | ||||
|  | ||||
|     const editorClickHandler = (event) => { | ||||
|       editor.execCommand('ToggleJoplinChecklistItem', false, { element: event.target }); | ||||
|     } | ||||
|  | ||||
|     if (options.listType === 'joplinChecklist') { | ||||
|       editor.on('click', editorClickHandler); | ||||
|     } | ||||
|  | ||||
|     editor.on('NodeChange', nodeChangeHandler); | ||||
|  | ||||
|     return () => { | ||||
|       if (options.listType === 'joplinChecklist') { | ||||
|         editor.off('click', editorClickHandler); | ||||
|       } | ||||
|       editor.off('NodeChange', nodeChangeHandler); | ||||
|     }  | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const register = function (editor: Editor) { | ||||
|   const hasPlugin = function (editor, plugin) { | ||||
|     const plugins = editor.settings.plugins ? editor.settings.plugins : ''; | ||||
|     return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1; | ||||
|   }; | ||||
|  | ||||
|   const exec = (command) => () => editor.execCommand(command); | ||||
|  | ||||
|   if (!hasPlugin(editor, 'advlist')) { | ||||
|     editor.ui.registry.addToggleButton('numlist', { | ||||
|       icon: 'ordered-list', | ||||
|       active: false, | ||||
|       tooltip: 'Numbered list', | ||||
|       onAction: exec('InsertOrderedList'), | ||||
|       onSetup: listState(editor, 'OL') | ||||
|     }); | ||||
|  | ||||
|     editor.ui.registry.addToggleButton('bullist', { | ||||
|       icon: 'unordered-list', | ||||
|       active: false, | ||||
|       tooltip: 'Bullet list', | ||||
|       onAction: exec('InsertUnorderedList'), | ||||
|       onSetup: listState(editor, 'UL') | ||||
|     }); | ||||
|  | ||||
|     editor.ui.registry.addToggleButton('joplinChecklist', { | ||||
|       icon: 'checklist', | ||||
|       active: false, | ||||
|       tooltip: 'Checkbox list', | ||||
|       onAction: exec('InsertJoplinChecklist'), | ||||
|       onSetup: listState(editor, 'UL', { listType: 'joplinChecklist' }) | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   register | ||||
| }; | ||||
							
								
								
									
										23
									
								
								Modules/TinyMCE/JoplinLists/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Modules/TinyMCE/JoplinLists/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "moduleResolution": "node", | ||||
|     "target": "es5", | ||||
|     "module": "es2015", | ||||
|     "importHelpers": true, | ||||
|     "lib": ["es2015"], | ||||
|     "declaration": true, | ||||
|     "noUnusedLocals": true, | ||||
|     "sourceMap": true, | ||||
|     "outDir": "lib", | ||||
|     "baseUrl": "." | ||||
|   }, | ||||
|   "include": [ | ||||
|     "src/demo/ts", | ||||
|     "src/main/ts", | ||||
|     "src/test/ts", | ||||
|     "node_modules/@ephox/**/*/api/Main.d.ts" | ||||
|   ], | ||||
|   "files": [ | ||||
|     "global.d.ts", | ||||
|   ], | ||||
| } | ||||
							
								
								
									
										9
									
								
								Modules/TinyMCE/JoplinLists/tslint.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Modules/TinyMCE/JoplinLists/tslint.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| { | ||||
|   "defaultSeverity": "error", | ||||
|   "extends": [ | ||||
|       "@ephox/tslint-rules/tslint.json" | ||||
|   ], | ||||
|   "jsRules": {}, | ||||
|   "rules": {}, | ||||
|   "rulesDirectory": [] | ||||
| } | ||||
							
								
								
									
										6018
									
								
								Modules/TinyMCE/JoplinLists/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6018
									
								
								Modules/TinyMCE/JoplinLists/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -40,6 +40,7 @@ const BaseService = require('lib/services/BaseService'); | ||||
| const SearchEngine = require('lib/services/SearchEngine'); | ||||
| const KvStore = require('lib/services/KvStore'); | ||||
| const MigrationService = require('lib/services/MigrationService'); | ||||
| const { toSystemSlashes } = require('lib/path-utils.js'); | ||||
|  | ||||
| class BaseApplication { | ||||
| 	constructor() { | ||||
| @@ -580,7 +581,7 @@ class BaseApplication { | ||||
|  | ||||
| 		if (process && process.env && process.env.PORTABLE_EXECUTABLE_DIR) return `${process.env.PORTABLE_EXECUTABLE_DIR}/JoplinProfile`; | ||||
|  | ||||
| 		return `${os.homedir()}/.config/${Setting.value('appName')}`; | ||||
| 		return toSystemSlashes(`${os.homedir()}/.config/${Setting.value('appName')}`, 'linux'); | ||||
| 	} | ||||
|  | ||||
| 	async start(argv) { | ||||
|   | ||||
| @@ -9,6 +9,9 @@ class HtmlToMd { | ||||
| 			anchorNames: options.anchorNames ? options.anchorNames.map(n => n.trim().toLowerCase()) : [], | ||||
| 			codeBlockStyle: 'fenced', | ||||
| 			preserveImageTagsWithSize: !!options.preserveImageTagsWithSize, | ||||
| 			bulletListMarker: '-', | ||||
| 			emDelimiter: '*', | ||||
| 			strongDelimiter: '**', | ||||
| 		}); | ||||
| 		turndown.use(turndownPluginGfm); | ||||
| 		turndown.remove('script'); | ||||
|   | ||||
| @@ -55,14 +55,21 @@ class NoteBodyViewer extends Component { | ||||
| 					this.forceUpdate(); | ||||
| 				}, 100); | ||||
| 			}, | ||||
| 			paddingBottom: '3.8em', // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text) | ||||
| 			highlightedKeywords: this.props.highlightedKeywords, | ||||
| 			resources: this.props.noteResources, // await shared.attachedResources(bodyToRender), | ||||
| 			codeTheme: theme.codeThemeCss, | ||||
| 			postMessageSyntax: 'window.ReactNativeWebView.postMessage', | ||||
| 		}; | ||||
|  | ||||
| 		const result = await this.markupToHtml_.render(note.markup_language, bodyToRender, this.props.webViewStyle, mdOptions); | ||||
| 		const result = await this.markupToHtml_.render( | ||||
| 			note.markup_language, | ||||
| 			bodyToRender, | ||||
| 			{ | ||||
| 				bodyPaddingBottom: '3.8em', // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text) | ||||
| 				...this.props.webViewStyle, | ||||
| 			}, | ||||
| 			mdOptions | ||||
| 		); | ||||
| 		let html = result.html; | ||||
|  | ||||
| 		const resourceDownloadMode = Setting.value('sync.resourceDownloadMode'); | ||||
|   | ||||
| @@ -73,7 +73,7 @@ class FsDriverBase { | ||||
| 	// TODO: move out of here and make it part of joplin-renderer | ||||
| 	// or assign to option using .bind(fsDriver()) | ||||
| 	async cacheCssToFile(cssStrings) { | ||||
| 		const cssString = cssStrings.join('\n'); | ||||
| 		const cssString = Array.isArray(cssStrings) ? cssStrings.join('\n') : cssStrings; | ||||
| 		const cssFilePath = `${Setting.value('tempDir')}/${md5(escape(cssString))}.css`; | ||||
| 		if (!(await this.exists(cssFilePath))) { | ||||
| 			await this.writeFile(cssFilePath, cssString, 'utf8'); | ||||
|   | ||||
| @@ -40,6 +40,10 @@ class HtmlToHtml { | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	async allAssets(/* theme*/) { | ||||
| 		return []; // TODO | ||||
| 	} | ||||
|  | ||||
| 	async render(markup, theme, options) { | ||||
| 		options = Object.assign({}, { | ||||
| 			splitted: false, | ||||
| @@ -80,7 +84,7 @@ class HtmlToHtml { | ||||
| 			}; | ||||
| 		} | ||||
|  | ||||
| 		let cssStrings = noteStyle(theme, options); | ||||
| 		let cssStrings = noteStyle(theme); | ||||
|  | ||||
| 		if (options.splitted) { | ||||
| 			const splitted = this.splitHtml(html); | ||||
|   | ||||
| @@ -36,6 +36,10 @@ class MarkupToHtml { | ||||
| 	async render(markupLanguage, markup, theme, options) { | ||||
| 		return this.renderer(markupLanguage).render(markup, theme, options); | ||||
| 	} | ||||
|  | ||||
| 	async allAssets(markupLanguage, theme) { | ||||
| 		return this.renderer(markupLanguage).allAssets(theme); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN = 1; | ||||
|   | ||||
| @@ -3,19 +3,22 @@ const md5 = require('md5'); | ||||
| const noteStyle = require('./noteStyle'); | ||||
| const { fileExtension } = require('./pathUtils'); | ||||
| const memoryCache = require('memory-cache'); | ||||
|  | ||||
| // /!\/!\ Note: the order of rules is important!! /!\/!\ | ||||
| const rules = { | ||||
| 	fence: require('./MdToHtml/rules/fence').default, | ||||
| 	sanitize_html: require('./MdToHtml/rules/sanitize_html').default, | ||||
| 	image: require('./MdToHtml/rules/image'), | ||||
| 	checkbox: require('./MdToHtml/rules/checkbox'), | ||||
| 	checkbox: require('./MdToHtml/rules/checkbox').default, | ||||
| 	katex: require('./MdToHtml/rules/katex'), | ||||
| 	link_open: require('./MdToHtml/rules/link_open'), | ||||
| 	html_image: require('./MdToHtml/rules/html_image'), | ||||
| 	highlight_keywords: require('./MdToHtml/rules/highlight_keywords'), | ||||
| 	code_inline: require('./MdToHtml/rules/code_inline'), | ||||
| 	fence: require('./MdToHtml/rules/fence').default, | ||||
| 	fountain: require('./MdToHtml/rules/fountain'), | ||||
| 	mermaid: require('./MdToHtml/rules/mermaid').default, | ||||
| 	sanitize_html: require('./MdToHtml/rules/sanitize_html').default, | ||||
| }; | ||||
|  | ||||
| const setupLinkify = require('./MdToHtml/setupLinkify'); | ||||
| const hljs = require('highlight.js'); | ||||
| const uslug = require('uslug'); | ||||
| @@ -77,6 +80,13 @@ class MdToHtml { | ||||
| 		return this.tempDir_; | ||||
| 	} | ||||
|  | ||||
| 	static pluginNames() { | ||||
| 		const output = []; | ||||
| 		for (const n in rules) output.push(n); | ||||
| 		for (const n in plugins) output.push(n); | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	pluginOptions(name) { | ||||
| 		let o = this.pluginOptions_[name] ? this.pluginOptions_[name] : {}; | ||||
| 		o = Object.assign({ | ||||
| @@ -115,8 +125,10 @@ class MdToHtml { | ||||
| 						throw new Error(`Unsupported inline mime type: ${mime}`); | ||||
| 					} | ||||
| 				} else { | ||||
| 					const name = `${pluginName}/${asset.name}`; | ||||
| 					files.push(Object.assign({}, asset, { | ||||
| 						name: `${pluginName}/${asset.name}`, | ||||
| 						name: name, | ||||
| 						path: `pluginAssets/${name}`, | ||||
| 						mime: mime, | ||||
| 					})); | ||||
| 				} | ||||
| @@ -124,21 +136,52 @@ class MdToHtml { | ||||
| 		} | ||||
|  | ||||
| 		return { | ||||
| 			files: files, | ||||
| 			pluginAssets: files, | ||||
| 			cssStrings: cssStrings, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	async render(body, style = null, options = null) { | ||||
| 	async allAssets(theme) { | ||||
| 		const assets = {}; | ||||
| 		for (const key in rules) { | ||||
| 			if (!this.pluginEnabled(key)) continue; | ||||
| 			const rule = rules[key]; | ||||
|  | ||||
| 			if (rule.style) { | ||||
| 				assets[key] = rule.style(theme); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		const processedAssets = this.processPluginAssets(assets); | ||||
| 		processedAssets.cssStrings.splice(0, 0, noteStyle(theme)); | ||||
| 		const output = await this.outputAssetsToExternalAssets_(processedAssets); | ||||
| 		return output.pluginAssets; | ||||
| 	} | ||||
|  | ||||
| 	async outputAssetsToExternalAssets_(output) { | ||||
| 		for (const cssString of output.cssStrings) { | ||||
| 			output.pluginAssets.push(await this.fsDriver().cacheCssToFile(cssString)); | ||||
| 		} | ||||
| 		delete output.cssStrings; | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	// "style" here is really the theme, as returned by themeStyle() | ||||
| 	async render(body, theme = null, options = null) { | ||||
| 		options = Object.assign({}, { | ||||
| 			// In bodyOnly mode, the rendered Markdown is returned without the wrapper DIV | ||||
| 			bodyOnly: false, | ||||
| 			// In splitted mode, the CSS and HTML will be returned in separate properties. | ||||
| 			// In non-splitted mode, CSS and HTML will be merged in the same document. | ||||
| 			splitted: false, | ||||
| 			// When this is true, all assets such as CSS or JS are returned as external | ||||
| 			// files. Otherwise some of them might be in the cssStrings property. | ||||
| 			externalAssetsOnly: false, | ||||
| 			postMessageSyntax: 'postMessage', | ||||
| 			paddingBottom: '0', | ||||
| 			highlightedKeywords: [], | ||||
| 			codeTheme: 'atom-one-light.css', | ||||
| 			style: Object.assign({}, defaultNoteStyle), | ||||
| 			theme: Object.assign({}, defaultNoteStyle, theme), | ||||
| 			plugins: {}, | ||||
| 		}, options); | ||||
|  | ||||
| 		// The "codeHighlightCacheKey" option indicates what set of cached object should be | ||||
| @@ -150,7 +193,7 @@ class MdToHtml { | ||||
| 			this.lastCodeHighlightCacheKey_ = options.codeHighlightCacheKey; | ||||
| 		} | ||||
|  | ||||
| 		const cacheKey = md5(escape(body + JSON.stringify(options) + JSON.stringify(style))); | ||||
| 		const cacheKey = md5(escape(body + JSON.stringify(options) + JSON.stringify(options.theme))); | ||||
| 		const cachedOutput = this.cachedOutputs_[cacheKey]; | ||||
| 		if (cachedOutput) return cachedOutput; | ||||
|  | ||||
| @@ -237,19 +280,13 @@ class MdToHtml { | ||||
| 		// Using the `context` object, a plugin can define what additional assets they need (css, fonts, etc.) using context.pluginAssets. | ||||
| 		// The calling application will need to handle loading these assets. | ||||
|  | ||||
| 		// /!\/!\ Note: the order of rules is important!! /!\/!\ | ||||
| 		for (const key in rules) { | ||||
| 			if (!this.pluginEnabled(key)) continue; | ||||
| 			const rule = rules[key]; | ||||
| 			const ruleInstall = rule.install ? rule.install : rule; | ||||
| 			markdownIt.use(ruleInstall(context, { ...ruleOptions })); | ||||
| 		} | ||||
|  | ||||
| 		markdownIt.use(rules.fence(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.sanitize_html(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.image(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.checkbox(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.link_open(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.html_image(context, ruleOptions)); | ||||
| 		if (this.pluginEnabled('katex')) markdownIt.use(rules.katex(context, ruleOptions)); | ||||
| 		if (this.pluginEnabled('fountain')) markdownIt.use(rules.fountain(context, ruleOptions)); | ||||
| 		if (this.pluginEnabled('mermaid')) markdownIt.use(rules.mermaid(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.highlight_keywords(context, ruleOptions)); | ||||
| 		markdownIt.use(rules.code_inline(context, ruleOptions)); | ||||
| 		markdownIt.use(markdownItAnchor, { slugify: uslugify }); | ||||
|  | ||||
| 		for (const key in plugins) { | ||||
| @@ -260,41 +297,28 @@ class MdToHtml { | ||||
|  | ||||
| 		const renderedBody = markdownIt.render(body); | ||||
|  | ||||
| 		let cssStrings = noteStyle(style, options); | ||||
| 		let cssStrings = noteStyle(options.theme); | ||||
|  | ||||
| 		const pluginAssets = this.processPluginAssets(context.pluginAssets); | ||||
| 		cssStrings = cssStrings.concat(pluginAssets.cssStrings); | ||||
|  | ||||
| 		const output = { | ||||
| 			pluginAssets: pluginAssets.files.map(f => { | ||||
| 				return Object.assign({}, f, { | ||||
| 					path: `pluginAssets/${f.name}`, | ||||
| 				}); | ||||
| 			}), | ||||
| 		}; | ||||
|  | ||||
| 		if (options.bodyOnly) { | ||||
| 			output.html = renderedBody; | ||||
| 			return output; | ||||
| 		} | ||||
| 		let output = this.processPluginAssets(context.pluginAssets); | ||||
| 		cssStrings = cssStrings.concat(output.cssStrings); | ||||
|  | ||||
| 		if (options.userCss) cssStrings.push(options.userCss); | ||||
|  | ||||
| 		const styleHtml = `<style>${cssStrings.join('\n')}</style>`; | ||||
|  | ||||
| 		const html = `${styleHtml}<div id="rendered-md">${renderedBody}</div>`; | ||||
|  | ||||
| 		output.html = html; | ||||
|  | ||||
| 		if (options.splitted) { | ||||
| 		if (options.bodyOnly) { | ||||
| 			output.html = renderedBody; | ||||
| 			output.cssStrings = cssStrings; | ||||
| 			output.html = `<div id="rendered-md">${renderedBody}</div>`; | ||||
| 		} else { | ||||
| 			const styleHtml = `<style>${cssStrings.join('\n')}</style>`; | ||||
| 			output.html = `${styleHtml}<div id="rendered-md">${renderedBody}</div>`; | ||||
|  | ||||
| 			if (options.externalAssetsOnly) { | ||||
| 				output.pluginAssets.push(await this.fsDriver().cacheCssToFile(cssStrings)); | ||||
| 			if (options.splitted) { | ||||
| 				output.cssStrings = cssStrings; | ||||
| 				output.html = `<div id="rendered-md">${renderedBody}</div>`; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (options.externalAssetsOnly) output = await this.outputAssetsToExternalAssets_(output); | ||||
|  | ||||
| 		// Fow now, we keep only the last entry in the cache | ||||
| 		this.cachedOutputs_ = {}; | ||||
| 		this.cachedOutputs_[cacheKey] = output; | ||||
|   | ||||
| @@ -1,153 +0,0 @@ | ||||
| let checkboxIndex_ = -1; | ||||
|  | ||||
| const checkboxStyle = ` | ||||
| 	/* Remove the indentation from the checkboxes at the root of the document | ||||
| 	   (otherwise they are too far right), but keep it for their children to allow | ||||
| 	   nested lists. Make sure this value matches the UL margin. */ | ||||
|  | ||||
| 	.md-checkbox .checkbox-wrapper { | ||||
| 		display: flex; | ||||
| 		align-items: center; | ||||
| 	} | ||||
|  | ||||
| 	li.md-checkbox { | ||||
| 		list-style-type: none; | ||||
| 	} | ||||
|  | ||||
| 	li.md-checkbox input[type=checkbox] { | ||||
| 		margin-left: -1.71em; | ||||
| 		margin-right: 0.7em; | ||||
| 	} | ||||
| `; | ||||
|  | ||||
| function createPrefixTokens(Token, id, checked, label, postMessageSyntax, sourceToken) { | ||||
| 	let token = null; | ||||
| 	const tokens = []; | ||||
|  | ||||
| 	// A bit hard to handle errors here and it's unlikely that the token won't have a valid | ||||
| 	// map parameter, but if it does set it to a very high value, which will be more easy to notice | ||||
| 	// in calling code. | ||||
| 	const lineIndex = sourceToken.map && sourceToken.map.length ? sourceToken.map[0] : 99999999; | ||||
| 	const checkedString = checked ? 'checked' : 'unchecked'; | ||||
|  | ||||
| 	const labelId = `cb-label-${id}`; | ||||
|  | ||||
| 	const js = ` | ||||
| 		try { | ||||
| 			if (this.checked) { | ||||
| 				this.setAttribute('checked', 'checked'); | ||||
| 			} else { | ||||
| 				this.removeAttribute('checked'); | ||||
| 			} | ||||
|  | ||||
| 			${postMessageSyntax}('checkboxclick:${checkedString}:${lineIndex}'); | ||||
| 			const label = document.getElementById("${labelId}"); | ||||
| 			label.classList.remove(this.checked ? 'checkbox-label-unchecked' : 'checkbox-label-checked'); | ||||
| 			label.classList.add(this.checked ? 'checkbox-label-checked' : 'checkbox-label-unchecked'); | ||||
| 		} catch (error) { | ||||
| 			console.warn('Checkbox ${checkedString}:${lineIndex} error', error); | ||||
| 		} | ||||
| 		return true; | ||||
| 	`; | ||||
|  | ||||
| 	token = new Token('checkbox_wrapper_open', 'div', 1); | ||||
| 	token.attrs = [['class', 'checkbox-wrapper']]; | ||||
| 	tokens.push(token); | ||||
|  | ||||
| 	token = new Token('checkbox_input', 'input', 0); | ||||
| 	token.attrs = [['type', 'checkbox'], ['id', id], ['onclick', js]]; | ||||
| 	if (checked) token.attrs.push(['checked', 'checked']); | ||||
| 	tokens.push(token); | ||||
|  | ||||
| 	token = new Token('label_open', 'label', 1); | ||||
| 	token.attrs = [['id', labelId], ['for', id], ['class', `checkbox-label-${checkedString}`]]; | ||||
| 	tokens.push(token); | ||||
|  | ||||
| 	if (label) { | ||||
| 		token = new Token('text', '', 0); | ||||
| 		token.content = label; | ||||
| 		tokens.push(token); | ||||
| 	} | ||||
|  | ||||
| 	return tokens; | ||||
| } | ||||
|  | ||||
| function createSuffixTokens(Token) { | ||||
| 	return [ | ||||
| 		new Token('label_close', 'label', -1), | ||||
| 		new Token('checkbox_wrapper_close', 'div', -1), | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| function installRule(markdownIt, mdOptions, ruleOptions, context) { | ||||
| 	markdownIt.core.ruler.push('checkbox', state => { | ||||
| 		const tokens = state.tokens; | ||||
| 		const Token = state.Token; | ||||
|  | ||||
| 		const checkboxPattern = /^\[([x|X| ])\] (.*)$/; | ||||
| 		let currentListItem = null; | ||||
| 		let processedFirstInline = false; | ||||
| 		for (let i = 0; i < tokens.length; i++) { | ||||
| 			const token = tokens[i]; | ||||
|  | ||||
| 			if (token.type === 'list_item_open') { | ||||
| 				currentListItem = token; | ||||
| 				processedFirstInline = false; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (token.type === 'list_item_close') { | ||||
| 				currentListItem = null; | ||||
| 				processedFirstInline = false; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			// Note that we only support list items that start with "-" (not with "*") | ||||
| 			if (currentListItem && currentListItem.markup === '-' && !processedFirstInline && token.type === 'inline') { | ||||
| 				processedFirstInline = true; | ||||
| 				const firstChild = token.children && token.children.length ? token.children[0] : null; | ||||
| 				if (!firstChild) continue; | ||||
|  | ||||
| 				const matches = checkboxPattern.exec(firstChild.content); | ||||
| 				if (!matches || matches.length < 2) continue; | ||||
|  | ||||
| 				checkboxIndex_++; | ||||
| 				const checked = matches[1] !== ' '; | ||||
| 				const id = `md-checkbox-${checkboxIndex_}`; | ||||
| 				const label = matches.length >= 3 ? matches[2] : ''; | ||||
|  | ||||
| 				// Prepend the text content with the checkbox markup and the opening <label> tag | ||||
| 				// then append the </label> tag at the end of the text content. | ||||
|  | ||||
| 				const prefix = createPrefixTokens(Token, id, checked, label, ruleOptions.postMessageSyntax, token); | ||||
| 				const suffix = createSuffixTokens(Token); | ||||
|  | ||||
| 				token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, prefix); | ||||
| 				token.children = token.children.concat(suffix); | ||||
|  | ||||
| 				// Add a class to the <li> container so that it can be targetted with CSS. | ||||
|  | ||||
| 				let itemClass = currentListItem.attrGet('class'); | ||||
| 				if (!itemClass) itemClass = ''; | ||||
| 				itemClass += ' md-checkbox joplin-checkbox'; | ||||
| 				currentListItem.attrSet('class', itemClass.trim()); | ||||
|  | ||||
| 				if (!('checkbox' in context.pluginAssets)) { | ||||
| 					context.pluginAssets['checkbox'] = [ | ||||
| 						{ | ||||
| 							inline: true, | ||||
| 							text: checkboxStyle, | ||||
| 							mime: 'text/css', | ||||
| 						}, | ||||
| 					]; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| module.exports = function(context, ruleOptions) { | ||||
| 	return function(md, mdOptions) { | ||||
| 		installRule(md, mdOptions, ruleOptions, context); | ||||
| 	}; | ||||
| }; | ||||
							
								
								
									
										228
									
								
								ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| let checkboxIndex_ = -1; | ||||
|  | ||||
| const pluginAssets:Function[] = []; | ||||
|  | ||||
| pluginAssets[1] = function() { | ||||
| 	return [ | ||||
| 		{ | ||||
| 			inline: true, | ||||
| 			mime: 'text/css', | ||||
| 			text: ` | ||||
| 				/* Remove the indentation from the checkboxes at the root of the document | ||||
| 				   (otherwise they are too far right), but keep it for their children to allow | ||||
| 				   nested lists. Make sure this value matches the UL margin. */ | ||||
|  | ||||
| 				.md-checkbox .checkbox-wrapper { | ||||
| 					display: flex; | ||||
| 					align-items: center; | ||||
| 				} | ||||
|  | ||||
| 				li.md-checkbox { | ||||
| 					list-style-type: none; | ||||
| 				} | ||||
|  | ||||
| 				li.md-checkbox input[type=checkbox] { | ||||
| 					margin-left: -1.71em; | ||||
| 					margin-right: 0.7em; | ||||
| 				}`, | ||||
| 		}, | ||||
| 	]; | ||||
| }; | ||||
|  | ||||
| pluginAssets[2] = function(theme:any) { | ||||
| 	return [ | ||||
| 		{ | ||||
| 			inline: true, | ||||
| 			mime: 'text/css', | ||||
| 			text: ` | ||||
| 				/* https://stackoverflow.com/questions/7478336/only-detect-click-event-on-pseudo-element#comment39751366_7478344 */ | ||||
|  | ||||
| 				ul.joplin-checklist li { | ||||
| 					pointer-events: none; | ||||
| 				} | ||||
|  | ||||
| 				ul.joplin-checklist { | ||||
| 					list-style:none; | ||||
| 				} | ||||
|  | ||||
| 				ul.joplin-checklist li::before { | ||||
| 					content:"\\f14a"; | ||||
| 					font-family:ForkAwesome; | ||||
| 					background-size: 16px 16px; | ||||
| 					pointer-events: all; | ||||
| 					cursor: pointer; | ||||
| 					width: 1em; | ||||
| 					height: 1em; | ||||
| 					margin-left: -1.3em; | ||||
| 					position: absolute; | ||||
| 					color: ${theme.htmlColor}; | ||||
| 				} | ||||
|  | ||||
| 				.joplin-checklist li:not(.checked)::before { | ||||
| 					content:"\\f0c8"; | ||||
| 				}`, | ||||
| 		}, | ||||
| 	]; | ||||
| }; | ||||
|  | ||||
| function createPrefixTokens(Token:any, id:string, checked:boolean, label:string, postMessageSyntax:string, sourceToken:any):any[] { | ||||
| 	let token = null; | ||||
| 	const tokens = []; | ||||
|  | ||||
| 	// A bit hard to handle errors here and it's unlikely that the token won't have a valid | ||||
| 	// map parameter, but if it does set it to a very high value, which will be more easy to notice | ||||
| 	// in calling code. | ||||
| 	const lineIndex = sourceToken.map && sourceToken.map.length ? sourceToken.map[0] : 99999999; | ||||
| 	const checkedString = checked ? 'checked' : 'unchecked'; | ||||
|  | ||||
| 	const labelId = `cb-label-${id}`; | ||||
|  | ||||
| 	const js = ` | ||||
| 		try { | ||||
| 			if (this.checked) { | ||||
| 				this.setAttribute('checked', 'checked'); | ||||
| 			} else { | ||||
| 				this.removeAttribute('checked'); | ||||
| 			} | ||||
|  | ||||
| 			${postMessageSyntax}('checkboxclick:${checkedString}:${lineIndex}'); | ||||
| 			const label = document.getElementById("${labelId}"); | ||||
| 			label.classList.remove(this.checked ? 'checkbox-label-unchecked' : 'checkbox-label-checked'); | ||||
| 			label.classList.add(this.checked ? 'checkbox-label-checked' : 'checkbox-label-unchecked'); | ||||
| 		} catch (error) { | ||||
| 			console.warn('Checkbox ${checkedString}:${lineIndex} error', error); | ||||
| 		} | ||||
| 		return true; | ||||
| 	`; | ||||
|  | ||||
| 	token = new Token('checkbox_wrapper_open', 'div', 1); | ||||
| 	token.attrs = [['class', 'checkbox-wrapper']]; | ||||
| 	tokens.push(token); | ||||
|  | ||||
| 	token = new Token('checkbox_input', 'input', 0); | ||||
| 	token.attrs = [['type', 'checkbox'], ['id', id], ['onclick', js]]; | ||||
| 	if (checked) token.attrs.push(['checked', 'checked']); | ||||
| 	tokens.push(token); | ||||
|  | ||||
| 	token = new Token('label_open', 'label', 1); | ||||
| 	token.attrs = [['id', labelId], ['for', id], ['class', `checkbox-label-${checkedString}`]]; | ||||
| 	tokens.push(token); | ||||
|  | ||||
| 	if (label) { | ||||
| 		token = new Token('text', '', 0); | ||||
| 		token.content = label; | ||||
| 		tokens.push(token); | ||||
| 	} | ||||
|  | ||||
| 	return tokens; | ||||
| } | ||||
|  | ||||
| function createSuffixTokens(Token:any):any[] { | ||||
| 	return [ | ||||
| 		new Token('label_close', 'label', -1), | ||||
| 		new Token('checkbox_wrapper_close', 'div', -1), | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| // @ts-ignore: Keep the function signature as-is despite unusued arguments | ||||
| function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) { | ||||
| 	const pluginOptions = { renderingType: 1, ...ruleOptions.plugins['checkbox'] }; | ||||
|  | ||||
| 	markdownIt.core.ruler.push('checkbox', (state:any) => { | ||||
| 		const tokens = state.tokens; | ||||
| 		const Token = state.Token; | ||||
|  | ||||
| 		const checkboxPattern = /^\[([x|X| ])\] (.*)$/; | ||||
| 		let currentListItem = null; | ||||
| 		let processedFirstInline = false; | ||||
| 		const lists = []; | ||||
| 		for (let i = 0; i < tokens.length; i++) { | ||||
| 			const token = tokens[i]; | ||||
|  | ||||
| 			if (token.type === 'bullet_list_open') { | ||||
| 				lists.push(token); | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (token.type === 'bullet_list_close') { | ||||
| 				lists.pop(); | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (token.type === 'list_item_open') { | ||||
| 				currentListItem = token; | ||||
| 				processedFirstInline = false; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (token.type === 'list_item_close') { | ||||
| 				currentListItem = null; | ||||
| 				processedFirstInline = false; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			// Note that we only support list items that start with "-" (not with "*") | ||||
| 			if (currentListItem && currentListItem.markup === '-' && !processedFirstInline && token.type === 'inline') { | ||||
| 				processedFirstInline = true; | ||||
| 				const firstChild = token.children && token.children.length ? token.children[0] : null; | ||||
| 				if (!firstChild) continue; | ||||
|  | ||||
| 				const matches = checkboxPattern.exec(firstChild.content); | ||||
| 				if (!matches || matches.length < 2) continue; | ||||
|  | ||||
| 				const checked = matches[1] !== ' '; | ||||
| 				const label = matches.length >= 3 ? matches[2] : ''; | ||||
|  | ||||
| 				const currentList = lists[lists.length - 1]; | ||||
|  | ||||
| 				if (pluginOptions.renderingType === 1) { | ||||
| 					checkboxIndex_++; | ||||
| 					const id = `md-checkbox-${checkboxIndex_}`; | ||||
|  | ||||
| 					// Prepend the text content with the checkbox markup and the opening <label> tag | ||||
| 					// then append the </label> tag at the end of the text content. | ||||
|  | ||||
| 					const prefix = createPrefixTokens(Token, id, checked, label, ruleOptions.postMessageSyntax, token); | ||||
| 					const suffix = createSuffixTokens(Token); | ||||
|  | ||||
| 					token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, prefix); | ||||
| 					token.children = token.children.concat(suffix); | ||||
|  | ||||
| 					// Add a class to the <li> container so that it can be targetted with CSS. | ||||
|  | ||||
| 					let itemClass = currentListItem.attrGet('class'); | ||||
| 					if (!itemClass) itemClass = ''; | ||||
| 					itemClass += ' md-checkbox joplin-checkbox'; | ||||
| 					currentListItem.attrSet('class', itemClass.trim()); | ||||
| 				} else { | ||||
| 					const textToken = new Token('text', '', 0); | ||||
| 					textToken.content = label; | ||||
| 					const tokens = []; | ||||
| 					tokens.push(textToken); | ||||
|  | ||||
| 					token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, tokens); | ||||
|  | ||||
| 					const listClass = currentList.attrGet('class') || ''; | ||||
| 					if (listClass.indexOf('joplin-') < 0) currentList.attrSet('class', (`${listClass} joplin-checklist`).trim()); | ||||
|  | ||||
| 					if (checked) { | ||||
| 						currentListItem.attrSet('class', (`${currentListItem.attrGet('class') || ''} checked`).trim()); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if (!('checkbox' in context.pluginAssets)) { | ||||
| 					context.pluginAssets['checkbox'] = pluginAssets[pluginOptions.renderingType](ruleOptions.theme); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export default { | ||||
| 	install: function(context:any, ruleOptions:any) { | ||||
| 		return function(md:any, mdOptions:any) { | ||||
| 			installRule(md, mdOptions, ruleOptions, context); | ||||
| 		}; | ||||
| 	}, | ||||
| 	style: pluginAssets[2], | ||||
| }; | ||||
| @@ -1,99 +1,106 @@ | ||||
| const fountain = require('../../vendor/fountain.min.js'); | ||||
|  | ||||
| const fountainCss = ` | ||||
| .fountain { | ||||
| 	font-family: monospace; | ||||
| 	line-height: 107%; | ||||
| 	max-width: 1000px; | ||||
| 	margin-left: auto; | ||||
| 	margin-right: auto; | ||||
| } | ||||
| const fountainCss = function() { | ||||
| 	return [ | ||||
| 		{ | ||||
| 			inline: true, | ||||
| 			mime: 'text/css', | ||||
| 			text: ` | ||||
| 				.fountain { | ||||
| 					font-family: monospace; | ||||
| 					line-height: 107%; | ||||
| 					max-width: 1000px; | ||||
| 					margin-left: auto; | ||||
| 					margin-right: auto; | ||||
| 				} | ||||
|  | ||||
| .fountain .title-page, | ||||
| .fountain .page {  | ||||
| 	box-shadow: 0 0 5px rgba(0,0,0,0.1); | ||||
| 	border: 1px solid #d2d2d2; | ||||
| 	padding: 10%; | ||||
| 	margin-bottom: 2em; | ||||
| } | ||||
| 				.fountain .title-page, | ||||
| 				.fountain .page {  | ||||
| 					box-shadow: 0 0 5px rgba(0,0,0,0.1); | ||||
| 					border: 1px solid #d2d2d2; | ||||
| 					padding: 10%; | ||||
| 					margin-bottom: 2em; | ||||
| 				} | ||||
|  | ||||
| .fountain h1, | ||||
| .fountain h2, | ||||
| .fountain h3, | ||||
| .fountain h4, | ||||
| .fountain p { | ||||
| 	font-weight: normal; | ||||
| 	line-height: 107%; | ||||
| 	margin: 1em 0; | ||||
| 	border: none; | ||||
| 	font-size: 1em; | ||||
| } | ||||
| 				.fountain h1, | ||||
| 				.fountain h2, | ||||
| 				.fountain h3, | ||||
| 				.fountain h4, | ||||
| 				.fountain p { | ||||
| 					font-weight: normal; | ||||
| 					line-height: 107%; | ||||
| 					margin: 1em 0; | ||||
| 					border: none; | ||||
| 					font-size: 1em; | ||||
| 				} | ||||
|  | ||||
| .fountain .bold { | ||||
| 	font-weight: bold; | ||||
| } | ||||
| 				.fountain .bold { | ||||
| 					font-weight: bold; | ||||
| 				} | ||||
|  | ||||
| .fountain .underline { | ||||
| 	text-decoration: underline; | ||||
| } | ||||
| 				.fountain .underline { | ||||
| 					text-decoration: underline; | ||||
| 				} | ||||
|  | ||||
| .fountain .centered { | ||||
| 	text-align: center; | ||||
| } | ||||
| 				.fountain .centered { | ||||
| 					text-align: center; | ||||
| 				} | ||||
|  | ||||
| .fountain h2 { | ||||
| 	text-align: right; | ||||
| } | ||||
| 				.fountain h2 { | ||||
| 					text-align: right; | ||||
| 				} | ||||
|  | ||||
| .fountain .dialogue p.parenthetical { | ||||
| 	margin-left: 11%; | ||||
| } | ||||
| 				.fountain .dialogue p.parenthetical { | ||||
| 					margin-left: 11%; | ||||
| 				} | ||||
|  | ||||
| .fountain .title-page .credit, | ||||
| .fountain .title-page .authors, | ||||
| .fountain .title-page .source { | ||||
| 	text-align: center; | ||||
| } | ||||
| 				.fountain .title-page .credit, | ||||
| 				.fountain .title-page .authors, | ||||
| 				.fountain .title-page .source { | ||||
| 					text-align: center; | ||||
| 				} | ||||
|  | ||||
| .fountain .title-page h1 { | ||||
| 	margin-bottom: 1.5em; | ||||
| 	text-align: center; | ||||
| } | ||||
| 				.fountain .title-page h1 { | ||||
| 					margin-bottom: 1.5em; | ||||
| 					text-align: center; | ||||
| 				} | ||||
|  | ||||
| .fountain .title-page .source { | ||||
| 	margin-top: 1.5em; | ||||
| } | ||||
| 				.fountain .title-page .source { | ||||
| 					margin-top: 1.5em; | ||||
| 				} | ||||
|  | ||||
| .fountain .title-page .notes { | ||||
| 	text-align: right; | ||||
| 	margin: 3em 0; | ||||
| } | ||||
| 				.fountain .title-page .notes { | ||||
| 					text-align: right; | ||||
| 					margin: 3em 0; | ||||
| 				} | ||||
|  | ||||
| .fountain .title-page h1 { | ||||
| 	margin-bottom: 1.5em; | ||||
| 	text-align: center; | ||||
| } | ||||
| 				.fountain .title-page h1 { | ||||
| 					margin-bottom: 1.5em; | ||||
| 					text-align: center; | ||||
| 				} | ||||
|  | ||||
| .fountain .dialogue { | ||||
| 	margin-left: 3em; | ||||
| 	margin-right: 3em; | ||||
| } | ||||
| 				.fountain .dialogue { | ||||
| 					margin-left: 3em; | ||||
| 					margin-right: 3em; | ||||
| 				} | ||||
|  | ||||
| .fountain .dialogue p, | ||||
| .fountain .dialogue h1, | ||||
| .fountain .dialogue h2, | ||||
| .fountain .dialogue h3, | ||||
| .fountain .dialogue h4 { | ||||
| 	margin: 0; | ||||
| } | ||||
| 				.fountain .dialogue p, | ||||
| 				.fountain .dialogue h1, | ||||
| 				.fountain .dialogue h2, | ||||
| 				.fountain .dialogue h3, | ||||
| 				.fountain .dialogue h4 { | ||||
| 					margin: 0; | ||||
| 				} | ||||
|  | ||||
| .fountain .dialogue h1, | ||||
| .fountain .dialogue h2, | ||||
| .fountain .dialogue h3, | ||||
| .fountain .dialogue h4 { | ||||
| 	text-align: center; | ||||
| } | ||||
| `; | ||||
| 				.fountain .dialogue h1, | ||||
| 				.fountain .dialogue h2, | ||||
| 				.fountain .dialogue h3, | ||||
| 				.fountain .dialogue h4 { | ||||
| 					text-align: center; | ||||
| 				}`, | ||||
| 		}, | ||||
| 	]; | ||||
| }; | ||||
|  | ||||
| function renderFountainScript(markdownIt, content) { | ||||
| 	const result = fountain.parse(content); | ||||
| @@ -114,13 +121,7 @@ function renderFountainScript(markdownIt, content) { | ||||
| function addContextAssets(context) { | ||||
| 	if ('fountain' in context.pluginAssets) return; | ||||
|  | ||||
| 	context.pluginAssets['fountain'] = [ | ||||
| 		{ | ||||
| 			inline: true, | ||||
| 			text: fountainCss, | ||||
| 			mime: 'text/css', | ||||
| 		}, | ||||
| 	]; | ||||
| 	context.pluginAssets['fountain'] = fountainCss(); | ||||
| } | ||||
|  | ||||
| function installRule(markdownIt, mdOptions, ruleOptions, context) { | ||||
| @@ -136,8 +137,11 @@ function installRule(markdownIt, mdOptions, ruleOptions, context) { | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| module.exports = function(context, ruleOptions) { | ||||
| 	return function(md, mdOptions) { | ||||
| 		installRule(md, mdOptions, ruleOptions, context); | ||||
| 	}; | ||||
| module.exports = { | ||||
| 	install: function(context, ruleOptions) { | ||||
| 		return function(md, mdOptions) { | ||||
| 			installRule(md, mdOptions, ruleOptions, context); | ||||
| 		}; | ||||
| 	}, | ||||
| 	style: fountainCss, | ||||
| }; | ||||
|   | ||||
| @@ -14,6 +14,14 @@ const stringifySafe = require('json-stringify-safe'); | ||||
|  | ||||
| katex = mhchemModule(katex); | ||||
|  | ||||
| function katexStyle() { | ||||
| 	return [ | ||||
| 		{ name: 'katex.css' }, | ||||
| 		// Note: Katex also requires a number of fonts but they don't need to be specified here | ||||
| 		// since they will be loaded as needed from the CSS. | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| // Test if potential opening or closing delimieter | ||||
| // Assumes that there is a "$" at state.src[pos] | ||||
| function isValidDelim(state, pos) { | ||||
| @@ -184,97 +192,78 @@ function math_block(state, start, end, silent) { | ||||
|  | ||||
| const cache_ = {}; | ||||
|  | ||||
| module.exports = function(context) { | ||||
| 	// Keep macros that persist across Katex blocks to allow defining a macro | ||||
| 	// in one block and re-using it later in other blocks. | ||||
| 	// https://github.com/laurent22/joplin/issues/1105 | ||||
| 	context.__katex = { macros: {} }; | ||||
| module.exports = { | ||||
| 	install: function(context) { | ||||
| 		// Keep macros that persist across Katex blocks to allow defining a macro | ||||
| 		// in one block and re-using it later in other blocks. | ||||
| 		// https://github.com/laurent22/joplin/issues/1105 | ||||
| 		context.__katex = { macros: {} }; | ||||
|  | ||||
| 	const addContextAssets = () => { | ||||
| 		context.pluginAssets['katex'] = [ | ||||
| 			{ name: 'katex.css' }, | ||||
| 			{ name: 'fonts/KaTeX_Main-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Main-Bold.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Main-BoldItalic.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Main-Italic.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Math-Italic.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Math-BoldItalic.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Size1-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Size2-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Size3-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Size4-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_AMS-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Caligraphic-Bold.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Caligraphic-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Fraktur-Bold.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Fraktur-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_SansSerif-Bold.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_SansSerif-Italic.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_SansSerif-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Script-Regular.woff2' }, | ||||
| 			{ name: 'fonts/KaTeX_Typewriter-Regular.woff2' }, | ||||
| 		]; | ||||
| 	}; | ||||
| 		const addContextAssets = () => { | ||||
| 			context.pluginAssets['katex'] = katexStyle(); | ||||
| 		}; | ||||
|  | ||||
| 	function renderToStringWithCache(latex, options) { | ||||
| 		const cacheKey = md5(escape(latex) + escape(stringifySafe(options))); | ||||
| 		if (cacheKey in cache_) { | ||||
| 			return cache_[cacheKey]; | ||||
| 		} else { | ||||
| 			const beforeMacros = stringifySafe(options.macros); | ||||
| 			const output = katex.renderToString(latex, options); | ||||
| 			const afterMacros = stringifySafe(options.macros); | ||||
| 		function renderToStringWithCache(latex, options) { | ||||
| 			const cacheKey = md5(escape(latex) + escape(stringifySafe(options))); | ||||
| 			if (cacheKey in cache_) { | ||||
| 				return cache_[cacheKey]; | ||||
| 			} else { | ||||
| 				const beforeMacros = stringifySafe(options.macros); | ||||
| 				const output = katex.renderToString(latex, options); | ||||
| 				const afterMacros = stringifySafe(options.macros); | ||||
|  | ||||
| 			// Don't cache the formulas that add macros, otherwise | ||||
| 			// they won't be added on second run. | ||||
| 			if (beforeMacros === afterMacros) cache_[cacheKey] = output; | ||||
| 			return output; | ||||
| 				// Don't cache the formulas that add macros, otherwise | ||||
| 				// they won't be added on second run. | ||||
| 				if (beforeMacros === afterMacros) cache_[cacheKey] = output; | ||||
| 				return output; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return function(md, options) { | ||||
| 		// Default options | ||||
| 		return function(md, options) { | ||||
| 			// Default options | ||||
|  | ||||
| 		options = options || {}; | ||||
| 		options.macros = context.__katex.macros; | ||||
| 		options.trust = true; | ||||
| 			options = options || {}; | ||||
| 			options.macros = context.__katex.macros; | ||||
| 			options.trust = true; | ||||
|  | ||||
| 		// set KaTeX as the renderer for markdown-it-simplemath | ||||
| 		const katexInline = function(latex) { | ||||
| 			options.displayMode = false; | ||||
| 			try { | ||||
| 				return `<span class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="$" data-joplin-source-close="$">${latex}</pre>${renderToStringWithCache(latex, options)}</span>`; | ||||
| 			} catch (error) { | ||||
| 				console.error('Katex error for:', latex, error); | ||||
| 				return latex; | ||||
| 			} | ||||
| 			// set KaTeX as the renderer for markdown-it-simplemath | ||||
| 			const katexInline = function(latex) { | ||||
| 				options.displayMode = false; | ||||
| 				try { | ||||
| 					return `<span class="joplin-editable"><span class="joplin-source" data-joplin-source-open="$" data-joplin-source-close="$">${latex}</span>${renderToStringWithCache(latex, options)}</span>`; | ||||
| 				} catch (error) { | ||||
| 					console.error('Katex error for:', latex, error); | ||||
| 					return latex; | ||||
| 				} | ||||
| 			}; | ||||
|  | ||||
| 			const inlineRenderer = function(tokens, idx) { | ||||
| 				addContextAssets(); | ||||
| 				return katexInline(tokens[idx].content); | ||||
| 			}; | ||||
|  | ||||
| 			const katexBlock = function(latex) { | ||||
| 				options.displayMode = true; | ||||
| 				try { | ||||
| 					return `<div class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="$$
" data-joplin-source-close="
$$
">${latex}</pre>${renderToStringWithCache(latex, options)}</div>`; | ||||
| 				} catch (error) { | ||||
| 					console.error('Katex error for:', latex, error); | ||||
| 					return latex; | ||||
| 				} | ||||
| 			}; | ||||
|  | ||||
| 			const blockRenderer = function(tokens, idx) { | ||||
| 				addContextAssets(); | ||||
| 				return `${katexBlock(tokens[idx].content)}\n`; | ||||
| 			}; | ||||
|  | ||||
| 			md.inline.ruler.after('escape', 'math_inline', math_inline); | ||||
| 			md.block.ruler.after('blockquote', 'math_block', math_block, { | ||||
| 				alt: ['paragraph', 'reference', 'blockquote', 'list'], | ||||
| 			}); | ||||
| 			md.renderer.rules.math_inline = inlineRenderer; | ||||
| 			md.renderer.rules.math_block = blockRenderer; | ||||
| 		}; | ||||
|  | ||||
| 		const inlineRenderer = function(tokens, idx) { | ||||
| 			addContextAssets(); | ||||
| 			return katexInline(tokens[idx].content); | ||||
| 		}; | ||||
|  | ||||
| 		const katexBlock = function(latex) { | ||||
| 			options.displayMode = true; | ||||
| 			try { | ||||
| 				return `<div class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="$$
" data-joplin-source-close="
$$
">${latex}</pre>${renderToStringWithCache(latex, options)}</div>`; | ||||
| 			} catch (error) { | ||||
| 				console.error('Katex error for:', latex, error); | ||||
| 				return latex; | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		const blockRenderer = function(tokens, idx) { | ||||
| 			addContextAssets(); | ||||
| 			return `${katexBlock(tokens[idx].content)}\n`; | ||||
| 		}; | ||||
|  | ||||
| 		md.inline.ruler.after('escape', 'math_inline', math_inline); | ||||
| 		md.block.ruler.after('blockquote', 'math_block', math_block, { | ||||
| 			alt: ['paragraph', 'reference', 'blockquote', 'list'], | ||||
| 		}); | ||||
| 		md.renderer.rules.math_inline = inlineRenderer; | ||||
| 		md.renderer.rules.math_block = blockRenderer; | ||||
| 	}; | ||||
| 	}, | ||||
| 	style: katexStyle, | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| function addContextAssets(context:any) { | ||||
| 	if ('mermaid' in context.pluginAssets) return; | ||||
|  | ||||
| 	context.pluginAssets['mermaid'] = [ | ||||
| function style() { | ||||
| 	return [ | ||||
| 		{ name: 'mermaid.min.js' }, | ||||
| 		{ name: 'mermaid_render.js' }, | ||||
| 		{ | ||||
| @@ -15,6 +13,12 @@ function addContextAssets(context:any) { | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| function addContextAssets(context:any) { | ||||
| 	if ('mermaid' in context.pluginAssets) return; | ||||
|  | ||||
| 	context.pluginAssets['mermaid'] = style(); | ||||
| } | ||||
|  | ||||
| // @ts-ignore: Keep the function signature as-is despite unusued arguments | ||||
| function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) { | ||||
| 	const defaultRender:Function = markdownIt.renderer.rules.fence || function(tokens:any[], idx:number, options:any, env:any, self:any) { | ||||
| @@ -35,8 +39,11 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| export default function(context:any, ruleOptions:any) { | ||||
| 	return function(md:any, mdOptions:any) { | ||||
| 		installRule(md, mdOptions, ruleOptions, context); | ||||
| 	}; | ||||
| } | ||||
| export default { | ||||
| 	install: function(context:any, ruleOptions:any) { | ||||
| 		return function(md:any, mdOptions:any) { | ||||
| 			installRule(md, mdOptions, ruleOptions, context); | ||||
| 		}; | ||||
| 	}, | ||||
| 	style: style, | ||||
| }; | ||||
|   | ||||
| @@ -13,6 +13,7 @@ module.exports = { | ||||
| 	raisedBackgroundColor: '#e5e5e5', | ||||
| 	htmlCodeColor: 'rgb(0,0,0)', | ||||
| 	htmlCodeFontSize: '.9em', | ||||
| 	bodyPaddingBottom: '0', | ||||
|  | ||||
| 	editorTheme: 'chrome', | ||||
| 	codeThemeCss: 'atom-one-light.css', | ||||
|   | ||||
| @@ -1,36 +1,34 @@ | ||||
| module.exports = function(style, options) { | ||||
| 	style = style ? style : {}; | ||||
|  | ||||
| 	// https://necolas.github.io/normalize.css/ | ||||
| 	const normalizeCss = ` | ||||
| 		html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| 		article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible} | ||||
| 		pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects} | ||||
| 		b,strong{font-weight:bolder}small{font-size:80%}img{border-style:none} | ||||
| 	`; | ||||
| module.exports = function(theme) { | ||||
| 	theme = theme ? theme : {}; | ||||
|  | ||||
| 	const fontFamily = '\'Avenir\', \'Arial\', sans-serif'; | ||||
|  | ||||
| 	const css = | ||||
| 		` | ||||
| 		/* https://necolas.github.io/normalize.css/ */ | ||||
| 		html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| 		article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible} | ||||
| 		pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects} | ||||
| 		b,strong{font-weight:bolder}small{font-size:80%}img{border-style:none} | ||||
|  | ||||
| 		body { | ||||
| 			font-size: ${style.htmlFontSize}; | ||||
| 			color: ${style.htmlColor}; | ||||
| 			font-size: ${theme.htmlFontSize}; | ||||
| 			color: ${theme.htmlColor}; | ||||
| 			word-wrap: break-word; | ||||
| 			line-height: ${style.htmlLineHeight}; | ||||
| 			background-color: ${style.htmlBackgroundColor}; | ||||
| 			line-height: ${theme.htmlLineHeight}; | ||||
| 			background-color: ${theme.htmlBackgroundColor}; | ||||
| 			font-family: ${fontFamily}; | ||||
| 			padding-bottom: ${options.paddingBottom}; | ||||
| 			padding-bottom: ${theme.bodyPaddingBottom}; | ||||
| 		} | ||||
| 		strong { | ||||
| 			color: ${style.colorBright}; | ||||
| 			color: ${theme.colorBright}; | ||||
| 		} | ||||
| 		kbd { | ||||
| 			border: 1px solid ${style.htmlCodeBorderColor}; | ||||
| 			box-shadow: inset 0 -1px 0 ${style.htmlCodeBorderColor}; | ||||
| 			border: 1px solid ${theme.htmlCodeBorderColor}; | ||||
| 			box-shadow: inset 0 -1px 0 ${theme.htmlCodeBorderColor}; | ||||
| 			padding: 2px 4px; | ||||
| 			border-radius: 3px; | ||||
| 			background-color: ${style.htmlCodeBackgroundColor}; | ||||
| 			background-color: ${theme.htmlCodeBackgroundColor}; | ||||
| 		} | ||||
| 		::-webkit-scrollbar { | ||||
| 			width: 7px; | ||||
| @@ -79,7 +77,7 @@ module.exports = function(style, options) { | ||||
| 		h1 { | ||||
| 			font-size: 1.5em; | ||||
| 			font-weight: bold; | ||||
| 			border-bottom: 1px solid ${style.htmlDividerColor}; | ||||
| 			border-bottom: 1px solid ${theme.htmlDividerColor}; | ||||
| 			padding-bottom: .3em; | ||||
| 		} | ||||
| 		h2 { | ||||
| @@ -95,7 +93,7 @@ module.exports = function(style, options) { | ||||
| 			font-weight: bold; | ||||
| 		} | ||||
| 		a { | ||||
| 			color: ${style.htmlLinkColor}; | ||||
| 			color: ${theme.htmlLinkColor}; | ||||
| 		} | ||||
| 		ul, ol { | ||||
| 			padding-left: 0; | ||||
| @@ -116,7 +114,7 @@ module.exports = function(style, options) { | ||||
| 			width: 1.2em; | ||||
| 			height: 1.4em; | ||||
| 			margin-right: 0.4em; | ||||
| 			background-color:  ${style.htmlLinkColor}; | ||||
| 			background-color:  ${theme.htmlLinkColor}; | ||||
| 		} | ||||
|     /* These icons are obtained from the wonderful ForkAwesome project by copying the src svgs  | ||||
|      * into the css classes below. | ||||
| @@ -177,7 +175,7 @@ module.exports = function(style, options) { | ||||
|       -webkit-mask-repeat: no-repeat; | ||||
| 		} | ||||
| 		blockquote { | ||||
| 			border-left: 4px solid ${style.htmlCodeBorderColor}; | ||||
| 			border-left: 4px solid ${theme.htmlCodeBorderColor}; | ||||
| 			padding-left: 1.2em; | ||||
| 			margin-left: 0; | ||||
| 			opacity: .7; | ||||
| @@ -185,45 +183,45 @@ module.exports = function(style, options) { | ||||
| 		table { | ||||
| 			text-align: left-align; | ||||
| 			border-collapse: collapse; | ||||
| 			border: 1px solid ${style.htmlCodeBorderColor}; | ||||
| 			background-color: ${style.htmlBackgroundColor}; | ||||
| 			border: 1px solid ${theme.htmlCodeBorderColor}; | ||||
| 			background-color: ${theme.htmlBackgroundColor}; | ||||
| 		} | ||||
| 		td, th { | ||||
| 			padding: .5em 1em .5em 1em; | ||||
| 			font-size: ${style.htmlFontSize}; | ||||
| 			color: ${style.htmlColor}; | ||||
| 			font-size: ${theme.htmlFontSize}; | ||||
| 			color: ${theme.htmlColor}; | ||||
| 			font-family: ${fontFamily}; | ||||
| 		} | ||||
| 		td { | ||||
| 			border: 1px solid ${style.htmlCodeBorderColor}; | ||||
| 			border: 1px solid ${theme.htmlCodeBorderColor}; | ||||
| 		} | ||||
| 		th { | ||||
| 			border: 1px solid ${style.htmlCodeBorderColor}; | ||||
| 			border-bottom: 2px solid ${style.htmlCodeBorderColor}; | ||||
| 			background-color: ${style.htmlTableBackgroundColor}; | ||||
| 			border: 1px solid ${theme.htmlCodeBorderColor}; | ||||
| 			border-bottom: 2px solid ${theme.htmlCodeBorderColor}; | ||||
| 			background-color: ${theme.htmlTableBackgroundColor}; | ||||
| 		} | ||||
| 		tr:nth-child(even) { | ||||
| 			background-color: ${style.htmlTableBackgroundColor}; | ||||
| 			background-color: ${theme.htmlTableBackgroundColor}; | ||||
| 		} | ||||
| 		tr:hover { | ||||
| 			background-color: ${style.raisedBackgroundColor}; | ||||
| 			background-color: ${theme.raisedBackgroundColor}; | ||||
| 		} | ||||
| 		hr { | ||||
| 			border: none; | ||||
| 			border-bottom: 2px solid ${style.htmlDividerColor}; | ||||
| 			border-bottom: 2px solid ${theme.htmlDividerColor}; | ||||
| 		} | ||||
| 		img { | ||||
| 			max-width: 100%; | ||||
| 			height: auto; | ||||
| 		} | ||||
| 		.inline-code { | ||||
| 			border: 1px solid ${style.htmlCodeBorderColor}; | ||||
| 			background-color: ${style.htmlCodeBackgroundColor}; | ||||
| 			border: 1px solid ${theme.htmlCodeBorderColor}; | ||||
| 			background-color: ${theme.htmlCodeBackgroundColor}; | ||||
| 			padding-right: .2em; | ||||
| 			padding-left: .2em; | ||||
| 			border-radius: .25em; | ||||
| 			color: ${style.htmlCodeColor}; | ||||
| 			font-size: ${style.htmlCodeFontSize}; | ||||
| 			color: ${theme.htmlCodeColor}; | ||||
| 			font-size: ${theme.htmlCodeFontSize}; | ||||
| 		} | ||||
|  | ||||
| 		.highlighted-keyword { | ||||
| @@ -316,5 +314,5 @@ module.exports = function(style, options) { | ||||
| 		} | ||||
| 	`; | ||||
|  | ||||
| 	return [normalizeCss, css]; | ||||
| 	return [css]; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										20
									
								
								Tools/gulp/tasks/tsc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Tools/gulp/tasks/tsc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| const execa = require('execa'); | ||||
|  | ||||
| const rootDir = `${__dirname}/../../../`; | ||||
| process.chdir(rootDir); | ||||
|  | ||||
| module.exports = { | ||||
| 	src: [ | ||||
| 		'ReactNativeClient/**/*.tsx', | ||||
| 		'ReactNativeClient/**/*.ts', | ||||
| 		'ElectronClient/**/*.tsx', | ||||
| 		'ElectronClient/**/*.ts', | ||||
| 		'CliClient/**/*.tsx', | ||||
| 		'CliClient/**/*.ts', | ||||
| 	], | ||||
| 	fn: async function() { | ||||
| 		const promise = execa('node', ['node_modules/typescript/bin/tsc', '--project', 'tsconfig.json'], { cwd: rootDir }); | ||||
| 		promise.stdout.pipe(process.stdout); | ||||
| 		return promise; | ||||
| 	}, | ||||
| }; | ||||
| @@ -99,7 +99,7 @@ utils.copyDir = async function(src, dest, options) { | ||||
|  | ||||
| 		// TODO: add support for delete flag | ||||
|  | ||||
| 		await utils.execCommand(`xcopy /C /I /H /R /Y /S ${excludedFlag} "${src}" ${dest}`); | ||||
| 		await utils.execCommand(`xcopy /C /I /H /R /Y /S ${excludedFlag} "${src}" "${dest}"`); | ||||
|  | ||||
| 		if (tempFile) await fs.remove(tempFile); | ||||
| 	} else { | ||||
|   | ||||
							
								
								
									
										26
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								gulpfile.js
									
									
									
									
									
								
							| @@ -1,28 +1,11 @@ | ||||
| const gulp = require('gulp'); | ||||
| const glob = require('glob'); | ||||
| const ts = require('gulp-typescript'); | ||||
| const execa = require('execa'); | ||||
| const utils = require('./Tools/gulp/utils'); | ||||
|  | ||||
| const tasks = { | ||||
| 	copyLib: require('./Tools/gulp/tasks/copyLib'), | ||||
| }; | ||||
|  | ||||
| const tsProject = ts.createProject('tsconfig.json'); | ||||
|  | ||||
| const tscTaskSrc = [ | ||||
| 	'ReactNativeClient/**/*.tsx', | ||||
| 	'ReactNativeClient/**/*.ts', | ||||
| 	'ElectronClient/**/*.tsx', | ||||
| 	'ElectronClient/**/*.ts', | ||||
| 	'CliClient/**/*.tsx', | ||||
| 	'CliClient/**/*.ts', | ||||
| ]; | ||||
|  | ||||
| const tscTask = function() { | ||||
| 	return tsProject.src() | ||||
| 		.pipe(tsProject()) | ||||
| 		.js.pipe(gulp.dest('./')); | ||||
| 	tsc: require('./Tools/gulp/tasks/tsc'), | ||||
| }; | ||||
|  | ||||
| const updateIgnoredTypeScriptBuildTask = async function() { | ||||
| @@ -34,6 +17,7 @@ const updateIgnoredTypeScriptBuildTask = async function() { | ||||
| 			'**/CliClient/build/lib/**', | ||||
| 			'**/CliClient/tests-build/lib/**', | ||||
| 			'**/ElectronClient/dist/**', | ||||
| 			'**/Modules/TinyMCE/JoplinLists/**', | ||||
| 		], | ||||
| 	}).map(f => f.substr(__dirname.length + 1)); | ||||
|  | ||||
| @@ -50,17 +34,17 @@ const updateIgnoredTypeScriptBuildTask = async function() { | ||||
| 	await utils.replaceFileText(`${__dirname}/.eslintignore`, regex, replacement); | ||||
| }; | ||||
|  | ||||
| gulp.task('tsc', tscTask); | ||||
| gulp.task('tsc', tasks.tsc.fn); | ||||
| gulp.task('copyLib', tasks.copyLib.fn); | ||||
| gulp.task('updateIgnoredTypeScriptBuild', updateIgnoredTypeScriptBuildTask); | ||||
|  | ||||
| gulp.task('watch', function() { | ||||
| 	gulp.watch(tasks.copyLib.src, tasks.copyLib.fn); | ||||
| 	gulp.watch(tscTaskSrc, updateIgnoredTypeScriptBuildTask); | ||||
| 	gulp.watch(tasks.tsc.src, updateIgnoredTypeScriptBuildTask); | ||||
|  | ||||
| 	// For watching, we use the actual tsc tool because it's more robust and | ||||
| 	// doesn't crash when there's an error | ||||
| 	const promise = execa('npx', ['tsc', '--watch', '--project', 'tsconfig.json'], { cwd: `${__dirname}` }); | ||||
| 	const promise = execa('node', ['node_modules/typescript/bin/tsc', '--project', 'tsconfig.json'], { cwd: `${__dirname}` }); | ||||
| 	promise.stdout.pipe(process.stdout); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,10 @@ | ||||
| 				"ElectronClient/gui/editors/PlainEditor.js", | ||||
| 				"ElectronClient/gui/MultiNoteActions.js", | ||||
| 				"ElectronClient/gui/NoteContentPropertiesDialog.js", | ||||
| 				"ElectronClient/gui/utils/NoteText.js" | ||||
| 				"ElectronClient/gui/utils/NoteText.js", | ||||
| 				"ElectronClient/gui/editors/TinyMCE/plugins/lists.js", | ||||
| 				"ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js", | ||||
| 				"ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js" | ||||
| 			], | ||||
| 			"folder_exclude_patterns": | ||||
| 			[ | ||||
| @@ -89,7 +92,10 @@ | ||||
| 				"ReactNativeClient/ios/Joplin.xcodeproj/project.xcworkspace", | ||||
| 				"ReactNativeClient/ios/Joplin.xcworkspace/xcuserdata", | ||||
| 				"ReactNativeClient/ios/Joplin.xcodeproj/xcuserdata", | ||||
| 				"ElectronClient/pluginAssets" | ||||
| 				"ElectronClient/pluginAssets", | ||||
| 				"Modules/TinyMCE/JoplinLists/dist", | ||||
| 				"Modules/TinyMCE/JoplinLists/lib", | ||||
| 				"Modules/TinyMCE/JoplinLists/scratch" | ||||
| 			], | ||||
| 			"path": "." | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										49
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										49
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -3071,43 +3071,6 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "gulp-typescript": { | ||||
|       "version": "6.0.0-alpha.1", | ||||
|       "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", | ||||
|       "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ansi-colors": "^4.1.1", | ||||
|         "plugin-error": "^1.0.1", | ||||
|         "source-map": "^0.7.3", | ||||
|         "through2": "^3.0.1", | ||||
|         "vinyl": "^2.2.0", | ||||
|         "vinyl-fs": "^3.0.3" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ansi-colors": { | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", | ||||
|           "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "source-map": { | ||||
|           "version": "0.7.3", | ||||
|           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", | ||||
|           "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "through2": { | ||||
|           "version": "3.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", | ||||
|           "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "readable-stream": "2 || 3" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "gulplog": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", | ||||
| @@ -4806,18 +4769,6 @@ | ||||
|         "semver-compare": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "plugin-error": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", | ||||
|       "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ansi-colors": "^1.0.1", | ||||
|         "arr-diff": "^4.0.0", | ||||
|         "arr-union": "^3.1.0", | ||||
|         "extend-shallow": "^3.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "posix-character-classes": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", | ||||
|   | ||||
| @@ -37,7 +37,6 @@ | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "glob": "^7.1.6", | ||||
|     "gulp": "^4.0.2", | ||||
|     "gulp-typescript": "^6.0.0-alpha.1", | ||||
|     "husky": "^3.0.2", | ||||
|     "lint-staged": "^9.2.1", | ||||
|     "typescript": "^3.7.3" | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		"target": "es2015", | ||||
| 		"alwaysStrict": true, | ||||
| 		"forceConsistentCasingInFileNames": true, | ||||
| 		"listEmittedFiles": true, | ||||
| 		"listEmittedFiles": false, | ||||
| 		"noFallthroughCasesInSwitch": true, | ||||
| 		"noImplicitAny": true, | ||||
| 		"noImplicitReturns": true, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user