From ac5d669f22ce00982f64a75a5edbc3f6083ed071 Mon Sep 17 00:00:00 2001 From: shellyln Date: Tue, 15 Dec 2020 22:49:53 +0900 Subject: [PATCH 01/11] [WIP] Use the File System Access API (for Browser backend (chrome)) --- src.renderer/assets/script/components/app.js | 7 ++ .../script/components/filedropdialog.js | 3 + .../script/components/fileopendialog.js | 1 + .../script/components/filesavedialog.js | 1 + .../assets/script/libs/backends/browser.js | 68 +++++++++++++++++-- 5 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src.renderer/assets/script/components/app.js b/src.renderer/assets/script/components/app.js index d19dc79..eed77f5 100644 --- a/src.renderer/assets/script/components/app.js +++ b/src.renderer/assets/script/components/app.js @@ -373,6 +373,7 @@ export default class App extends React.Component { // eslint-disable-next-line require-atomic-updates AppState.inputFormat = getInputFormat(AppState.filePath); + editor.session.setMode(getAceEditorMode(AppState.inputFormat)); editor.session.getUndoManager().markClean(); notifyEditorDirty(false); updateAppIndicatorBar(); @@ -391,14 +392,17 @@ export default class App extends React.Component { value: 'md', text: 'Markdown (*.md, *.markdown)', exts: ['.md', '.markdown'], + mime: 'text/markdown', },{ value: 'html', text: 'HTML (*.html, *.htm)', exts: ['.html', '.htm'], + mime: 'text/html', },{ value: '*', text: 'All files (*.*)', exts: [], + mime: '*/*', }], }, async (currentDir, fileName) => { try { @@ -455,15 +459,18 @@ export default class App extends React.Component { value: 'pdf', text: 'PDF (*.pdf)', exts: ['.pdf'], + mime: 'application/pdf', }]), [{ value: 'html', text: 'HTML (*.html, *.htm)', exts: ['.html', '.htm'], + mime: 'text/html', },{ value: '*', text: 'All files (*.*)', exts: [], + mime: '*/*', }] ), }, async (currentDir, fileName) => { diff --git a/src.renderer/assets/script/components/filedropdialog.js b/src.renderer/assets/script/components/filedropdialog.js index fea801a..a70bcef 100644 --- a/src.renderer/assets/script/components/filedropdialog.js +++ b/src.renderer/assets/script/components/filedropdialog.js @@ -92,14 +92,17 @@ export default class FileDropDialog extends React.Component { value: 'md', text: 'Markdown (*.md, *.markdown)', exts: ['.md', '.markdown'], + mime: 'text/markdown', },{ value: 'html', text: 'HTML (*.html, *.htm)', exts: ['.html', '.htm'], + mime: 'text/html', },{ value: '*', text: 'All files (*.*)', exts: [], + mime: '*/*', }], }, async (currentDir, fileName) => { const path = await pathJoin(currentDir, fileName); diff --git a/src.renderer/assets/script/components/fileopendialog.js b/src.renderer/assets/script/components/fileopendialog.js index c6e23c6..e24828c 100644 --- a/src.renderer/assets/script/components/fileopendialog.js +++ b/src.renderer/assets/script/components/fileopendialog.js @@ -55,6 +55,7 @@ export default class FileOpenDialog extends React.Component { options.fileTypes.map(x => ({ name: x.text, extensions: x.exts && x.exts.length > 0 ? x.exts.map(t => t.slice(1)) : ['*'], + mime: x.mime, }))); if (filePaths) { this.handler(await getDirName(filePaths[0]), await getBaseName(filePaths[0])); diff --git a/src.renderer/assets/script/components/filesavedialog.js b/src.renderer/assets/script/components/filesavedialog.js index fa9c81d..7d75688 100644 --- a/src.renderer/assets/script/components/filesavedialog.js +++ b/src.renderer/assets/script/components/filesavedialog.js @@ -64,6 +64,7 @@ export default class FileSaveDialog extends React.Component { options.fileTypes.map(x => ({ name: x.text, extensions: x.exts && x.exts.length > 0 ? x.exts.map(t => t.slice(1)) : ['*'], + mime: x.mime, }))); if (fileName) { this.handler(await getDirName(fileName), await getBaseName(fileName)); diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index ea71750..b0f704a 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -59,6 +59,47 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI const welcomeFile = 'assets/data/welcome.md'; + let nativeSaveFileHandle = null; + + if (window.showOpenFilePicker) { + nativeFileOpenDialog_ = (async (title, defaultPath, filters) => { + try { + const [fileHandle] = await window.showOpenFilePicker({ + types: filters.map(x => ({ + description: x.name, + accept: { + [x.mime]: x.extensions.map(ext => `.${ext}`), + }, + })), + }); + nativeSaveFileHandle = fileHandle; + const file = await nativeSaveFileHandle.getFile(); + return [file.name]; + } catch (e) { + return void 0; + } + }); + } + + if (window.showSaveFilePicker) { + nativeFileSaveDialog_ = (async (title, defaultPath, filters) => { + try { + nativeSaveFileHandle = await window.showSaveFilePicker({ + types: filters.map(x => ({ + description: x.name, + accept: { + [x.mime]: x.extensions.map(ext => `.${ext}`), + }, + })), + }); + const file = await nativeSaveFileHandle.getFile(); + return file.name; + } catch (e) { + return void 0; + } + }); + } + // eslint-disable-next-line no-unused-vars renderByMenneu_ = (async (source, data, options, srcPath, ...exportPath) => { const opts = Object.assign({}, options, { @@ -102,18 +143,31 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI // eslint-disable-next-line no-unused-vars loadFile_ = (async (...filePath) => { - // eslint-disable-next-line no-undef - const response = await fetch(welcomeFile); - return await response.text(); + if (nativeSaveFileHandle) { + const file = await nativeSaveFileHandle.getFile(); + return await file.text(); + } else { + // eslint-disable-next-line no-undef + const response = await fetch(welcomeFile); + return await response.text(); + } }); // eslint-disable-next-line no-inner-declarations async function internalSaveFileEx(forExport, text, ...filePath) { const p = await pathJoin(...filePath); const b = await getBaseName(p); + // eslint-disable-next-line no-undef const util = menneu.getAppEnv().RedAgateUtil; - await util.FileSaver.saveTextAs(b, text); + + if (nativeSaveFileHandle) { + const writable = await nativeSaveFileHandle.createWritable(); + await writable.write(text); + await writable.close(); + } else { + await util.FileSaver.saveTextAs(b, text); + } if (!forExport) { try { @@ -198,8 +252,10 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI let dir = filePath; if (dir.lastIndexOf('/') !== -1) { dir = dir.substring(0, dir.lastIndexOf('/')); - } - if (dir === '') { + if (dir === '') { + dir = '/'; + } + } else { dir = '/'; } return dir; From cfeeb06f52d170a01c0ea7c25290802727c9d5c5 Mon Sep 17 00:00:00 2001 From: shellyln Date: Tue, 15 Dec 2020 23:33:40 +0900 Subject: [PATCH 02/11] [WIP] Fix opening/saving native file (browser backend) --- .../assets/script/libs/backends/browser.js | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index b0f704a..e274045 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -65,7 +65,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI nativeFileOpenDialog_ = (async (title, defaultPath, filters) => { try { const [fileHandle] = await window.showOpenFilePicker({ - types: filters.map(x => ({ + types: filters.filter(x => x.extensions.length && x.extensions[0] !== '*').map(x => ({ description: x.name, accept: { [x.mime]: x.extensions.map(ext => `.${ext}`), @@ -85,7 +85,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI nativeFileSaveDialog_ = (async (title, defaultPath, filters) => { try { nativeSaveFileHandle = await window.showSaveFilePicker({ - types: filters.map(x => ({ + types: filters.filter(x => x.extensions.length && x.extensions[0] !== '*').map(x => ({ description: x.name, accept: { [x.mime]: x.extensions.map(ext => `.${ext}`), @@ -156,20 +156,32 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI // eslint-disable-next-line no-inner-declarations async function internalSaveFileEx(forExport, text, ...filePath) { const p = await pathJoin(...filePath); - const b = await getBaseName(p); + let b = await getBaseName(p); // eslint-disable-next-line no-undef const util = menneu.getAppEnv().RedAgateUtil; - if (nativeSaveFileHandle) { - const writable = await nativeSaveFileHandle.createWritable(); - await writable.write(text); - await writable.close(); - } else { + let saved = false; + if (window.showSaveFilePicker) { + if (! nativeSaveFileHandle) { + const fileName = await nativeFileSaveDialog('', '', []); + if (nativeSaveFileHandle && fileName) { + b = fileName; + } + } + if (nativeSaveFileHandle) { + const writable = await nativeSaveFileHandle.createWritable(); + await writable.write(text); + await writable.close(); + saved = true; + } + } + if (! saved) { + // Fallback await util.FileSaver.saveTextAs(b, text); } - if (!forExport) { + if (! forExport) { try { // eslint-disable-next-line require-atomic-updates, no-undef window.location.hash = `filename=${encodeURIComponent(b)}&open.d=${util.Base64.encode(pako.deflate( From 20b1ec4ad547c4384740bb7cdefc4b67a16a2e18 Mon Sep 17 00:00:00 2001 From: shellyln Date: Tue, 15 Dec 2020 23:53:31 +0900 Subject: [PATCH 03/11] [WIP] Fix opening/saving path operations (browser backend) --- src.renderer/assets/script/libs/backends/browser.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index e274045..57711d5 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -155,7 +155,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI // eslint-disable-next-line no-inner-declarations async function internalSaveFileEx(forExport, text, ...filePath) { - const p = await pathJoin(...filePath); + let p = await pathJoin(...filePath); let b = await getBaseName(p); // eslint-disable-next-line no-undef @@ -166,7 +166,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI if (! nativeSaveFileHandle) { const fileName = await nativeFileSaveDialog('', '', []); if (nativeSaveFileHandle && fileName) { - b = fileName; + p = b = fileName; } } if (nativeSaveFileHandle) { @@ -264,11 +264,8 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI let dir = filePath; if (dir.lastIndexOf('/') !== -1) { dir = dir.substring(0, dir.lastIndexOf('/')); - if (dir === '') { - dir = '/'; - } } else { - dir = '/'; + dir = ''; } return dir; }); From 3460f9e9654959b2916e84b06751288a0a917493 Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 05:54:51 +0900 Subject: [PATCH 04/11] [FIX] In the browser backend, the filepath and filename may change on the first save. --- src.renderer/assets/script/components/aceeditor.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src.renderer/assets/script/components/aceeditor.js b/src.renderer/assets/script/components/aceeditor.js index 9b53488..0a8973e 100644 --- a/src.renderer/assets/script/components/aceeditor.js +++ b/src.renderer/assets/script/components/aceeditor.js @@ -8,6 +8,8 @@ import { notifyEditorDirty, alertWrap } from '../libs/backend-wrap.js'; import AppState, { updateAppIndicatorBar } from '../libs/appstate.js'; +import { getInputFormat, + getAceEditorMode } from '../libs/modes.js'; @@ -32,7 +34,15 @@ export default class AceEditor extends React.Component { exec: async (editor) => { if (AppState.filePath) { try { - await saveFile(editor.getValue(), AppState.filePath); + // NOTE: In the browser backend, the filepath and filename may change on the first save. + + const fileInfo = await saveFile(editor.getValue(), AppState.filePath); + // eslint-disable-next-line require-atomic-updates + AppState.filePath = fileInfo.path; + // eslint-disable-next-line require-atomic-updates + AppState.inputFormat = getInputFormat(AppState.filePath); + + editor.session.setMode(getAceEditorMode(AppState.inputFormat)); editor.session.getUndoManager().markClean(); notifyEditorDirty(false); updateAppIndicatorBar(); From e8625c258ab54ef37e2443b9e797e65d42cd10cb Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 06:33:53 +0900 Subject: [PATCH 05/11] Extract file filters --- src.renderer/assets/script/components/app.js | 39 ++---------- .../script/components/filedropdialog.js | 18 +----- .../assets/script/libs/backends/browser.js | 30 +++++---- .../assets/script/libs/filefilters.js | 61 +++++++++++++++++++ 4 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 src.renderer/assets/script/libs/filefilters.js diff --git a/src.renderer/assets/script/components/app.js b/src.renderer/assets/script/components/app.js index eed77f5..adb76b2 100644 --- a/src.renderer/assets/script/components/app.js +++ b/src.renderer/assets/script/components/app.js @@ -19,6 +19,8 @@ import { getInputFormat, getAceEditorMode } from '../libs/modes.js'; import { escapeHtml } from '../libs/escape.js'; import commandRunner from '../libs/cmdrunner.js'; +import { saveAsFilter, + exportFilter } from '../libs/filefilters'; import { getSuggests as getAppSuggests, getOperators as getAppOperators } from '../libs/commands/app.js'; @@ -388,22 +390,7 @@ export default class App extends React.Component { currentAceId: this.state.currentAceId, currentFilePath: AppState.filePath, forExport: false, - fileTypes: [{ - value: 'md', - text: 'Markdown (*.md, *.markdown)', - exts: ['.md', '.markdown'], - mime: 'text/markdown', - },{ - value: 'html', - text: 'HTML (*.html, *.htm)', - exts: ['.html', '.htm'], - mime: 'text/html', - },{ - value: '*', - text: 'All files (*.*)', - exts: [], - mime: '*/*', - }], + fileTypes: saveAsFilter, }, async (currentDir, fileName) => { try { await this.fileSaveAs(currentDir, fileName); @@ -454,25 +441,7 @@ export default class App extends React.Component { currentAceId: this.state.currentAceId, currentFilePath: AppState.filePath, forExport: true, - fileTypes: [].concat( - (window._MDNE_BACKEND_CAPS_NO_PDF_RENDERER ? [] : [{ - value: 'pdf', - text: 'PDF (*.pdf)', - exts: ['.pdf'], - mime: 'application/pdf', - }]), - [{ - value: 'html', - text: 'HTML (*.html, *.htm)', - exts: ['.html', '.htm'], - mime: 'text/html', - },{ - value: '*', - text: 'All files (*.*)', - exts: [], - mime: '*/*', - }] - ), + fileTypes: exportFilter, }, async (currentDir, fileName) => { try { await this.fileExport(currentDir, fileName); diff --git a/src.renderer/assets/script/components/filedropdialog.js b/src.renderer/assets/script/components/filedropdialog.js index a70bcef..957280a 100644 --- a/src.renderer/assets/script/components/filedropdialog.js +++ b/src.renderer/assets/script/components/filedropdialog.js @@ -12,6 +12,7 @@ import AppState, { updateAppIndicatorBar } from '../libs/appstate.js'; import { getInputFormat, getAceEditorMode } from '../libs/modes.js'; +import { openFilter } from '../libs/filefilters'; @@ -88,22 +89,7 @@ export default class FileDropDialog extends React.Component { title: 'Open', currentAceId: this.options.aceId, currentFilePath: AppState.filePath, - fileTypes: [{ - value: 'md', - text: 'Markdown (*.md, *.markdown)', - exts: ['.md', '.markdown'], - mime: 'text/markdown', - },{ - value: 'html', - text: 'HTML (*.html, *.htm)', - exts: ['.html', '.htm'], - mime: 'text/html', - },{ - value: '*', - text: 'All files (*.*)', - exts: [], - mime: '*/*', - }], + fileTypes: openFilter, }, async (currentDir, fileName) => { const path = await pathJoin(currentDir, fileName); const text = await loadFile(path); diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index 57711d5..04065ce 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -50,6 +50,18 @@ const additionalContentStyles = ` `; +function convertFileFilters(filters) { + return (filters + .filter(x => x.extensions.length && x.extensions[0] !== '*') + .map(x => ({ + description: x.name, + accept: { + [x.mime]: x.extensions.map(ext => `.${ext}`), + }, + }))); +} + + if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATION') { // Fallback (for Browser) @@ -65,12 +77,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI nativeFileOpenDialog_ = (async (title, defaultPath, filters) => { try { const [fileHandle] = await window.showOpenFilePicker({ - types: filters.filter(x => x.extensions.length && x.extensions[0] !== '*').map(x => ({ - description: x.name, - accept: { - [x.mime]: x.extensions.map(ext => `.${ext}`), - }, - })), + types: convertFileFilters(filters), }); nativeSaveFileHandle = fileHandle; const file = await nativeSaveFileHandle.getFile(); @@ -85,12 +92,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI nativeFileSaveDialog_ = (async (title, defaultPath, filters) => { try { nativeSaveFileHandle = await window.showSaveFilePicker({ - types: filters.filter(x => x.extensions.length && x.extensions[0] !== '*').map(x => ({ - description: x.name, - accept: { - [x.mime]: x.extensions.map(ext => `.${ext}`), - }, - })), + types: convertFileFilters(filters), }); const file = await nativeSaveFileHandle.getFile(); return file.name; @@ -161,10 +163,12 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI // eslint-disable-next-line no-undef const util = menneu.getAppEnv().RedAgateUtil; + const modFilters = await import('../filefilters') + let saved = false; if (window.showSaveFilePicker) { if (! nativeSaveFileHandle) { - const fileName = await nativeFileSaveDialog('', '', []); + const fileName = await nativeFileSaveDialog('', '', modFilters.saveAsFilter); if (nativeSaveFileHandle && fileName) { p = b = fileName; } diff --git a/src.renderer/assets/script/libs/filefilters.js b/src.renderer/assets/script/libs/filefilters.js new file mode 100644 index 0000000..8b9e539 --- /dev/null +++ b/src.renderer/assets/script/libs/filefilters.js @@ -0,0 +1,61 @@ +// Copyright (c) 2020 Shellyl_N and Authors +// license: ISC +// https://github.com/shellyln + + + +export const openFilter = [{ + value: 'md', + text: 'Markdown (*.md, *.markdown)', + exts: ['.md', '.markdown'], + mime: 'text/markdown', +},{ + value: 'html', + text: 'HTML (*.html, *.htm)', + exts: ['.html', '.htm'], + mime: 'text/html', +},{ + value: '*', + text: 'All Files (*.*)', + exts: [], + mime: '*/*', +}]; + + +export const saveAsFilter = [{ + value: 'md', + text: 'Markdown (*.md, *.markdown)', + exts: ['.md', '.markdown'], + mime: 'text/markdown', +},{ + value: 'html', + text: 'HTML (*.html, *.htm)', + exts: ['.html', '.htm'], + mime: 'text/html', +},{ + value: '*', + text: 'All Files (*.*)', + exts: [], + mime: '*/*', +}]; + + +export const exportFilter = [].concat( + (window._MDNE_BACKEND_CAPS_NO_PDF_RENDERER ? [] : [{ + value: 'pdf', + text: 'PDF (*.pdf)', + exts: ['.pdf'], + mime: 'application/pdf', + }]), + [{ + value: 'html', + text: 'HTML (*.html, *.htm)', + exts: ['.html', '.htm'], + mime: 'text/html', + },{ + value: '*', + text: 'All Files (*.*)', + exts: [], + mime: '*/*', + }] +); From 4869854f44f3a3a1c9bd4d909abab4ec784dad54 Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 19:11:55 +0900 Subject: [PATCH 06/11] [FIX] Fix nativeFileSaveDialog parameter --- src.renderer/assets/script/libs/backends/browser.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index 04065ce..390dda8 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -168,7 +168,11 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI let saved = false; if (window.showSaveFilePicker) { if (! nativeSaveFileHandle) { - const fileName = await nativeFileSaveDialog('', '', modFilters.saveAsFilter); + const fileName = await nativeFileSaveDialog('', '', modFilters.saveAsFilter.map(x => ({ + name: x.text, + extensions: x.exts && x.exts.length > 0 ? x.exts.map(t => t.slice(1)) : ['*'], + mime: x.mime, + }))); if (nativeSaveFileHandle && fileName) { p = b = fileName; } From 9fc1b4e6a688b054664236e9ca58979eaf6216cc Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 19:34:05 +0900 Subject: [PATCH 07/11] Edit comments --- src.preload/preload-isolated.js | 1 + src.renderer/.eslintrc | 9 +++------ src.renderer/assets/script/components/aceeditor.js | 2 -- src.renderer/assets/script/components/app.js | 2 -- src.renderer/assets/script/components/filedropdialog.js | 1 - src.renderer/assets/script/libs/backends/browser.js | 9 --------- src.renderer/assets/script/libs/remote.js | 1 - 7 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src.preload/preload-isolated.js b/src.preload/preload-isolated.js index 7138071..d838325 100644 --- a/src.preload/preload-isolated.js +++ b/src.preload/preload-isolated.js @@ -7,6 +7,7 @@ contextBridge.exposeInMainWorld( 'mdneApi', { getKey: () => { + // NOTE: Protect from iframe user contents. `parent.window.mdneApi.*` are callable from iframe. const k = apiKeyCopy; apiKeyCopy = null; return k; diff --git a/src.renderer/.eslintrc b/src.renderer/.eslintrc index f33c7ea..158b756 100644 --- a/src.renderer/.eslintrc +++ b/src.renderer/.eslintrc @@ -11,18 +11,15 @@ "es6": true }, "globals": { - "SharedArrayBuffer": true, - "Atomics": true, - "window": true, - "document": true, "M": true, "ace": true, + "dialogPolyfill": true, "React": true, "ReactDOM": true, "liyad": true, "lsx": true, - "alert": true, - "confirm": true, + "menneu": true, + "pako": true, "mdneApi": true }, "rules": { diff --git a/src.renderer/assets/script/components/aceeditor.js b/src.renderer/assets/script/components/aceeditor.js index 0a8973e..704ac9e 100644 --- a/src.renderer/assets/script/components/aceeditor.js +++ b/src.renderer/assets/script/components/aceeditor.js @@ -37,9 +37,7 @@ export default class AceEditor extends React.Component { // NOTE: In the browser backend, the filepath and filename may change on the first save. const fileInfo = await saveFile(editor.getValue(), AppState.filePath); - // eslint-disable-next-line require-atomic-updates AppState.filePath = fileInfo.path; - // eslint-disable-next-line require-atomic-updates AppState.inputFormat = getInputFormat(AppState.filePath); editor.session.setMode(getAceEditorMode(AppState.inputFormat)); diff --git a/src.renderer/assets/script/components/app.js b/src.renderer/assets/script/components/app.js index adb76b2..4a8ff0e 100644 --- a/src.renderer/assets/script/components/app.js +++ b/src.renderer/assets/script/components/app.js @@ -122,13 +122,11 @@ export default class App extends React.Component { ), }); } - // eslint-disable-next-line no-undef if (window.dialogPolyfill) { // initialize polyfill emulated elements const dialogs = document.querySelectorAll('dialog'); for (let i = 0; i < dialogs.length; i++) { const dialog = dialogs[i]; - // eslint-disable-next-line no-undef dialogPolyfill.registerDialog(dialog); } } diff --git a/src.renderer/assets/script/components/filedropdialog.js b/src.renderer/assets/script/components/filedropdialog.js index 957280a..e5e23f7 100644 --- a/src.renderer/assets/script/components/filedropdialog.js +++ b/src.renderer/assets/script/components/filedropdialog.js @@ -98,7 +98,6 @@ export default class FileDropDialog extends React.Component { }); } catch (e) { await alertWrap(e); - // eslint-disable-next-line require-atomic-updates AppState.filePath = null; notifyEditorDirty(false); updateAppIndicatorBar(); diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index 390dda8..d569d19 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -120,7 +120,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI throw new Error(errText); } - // eslint-disable-next-line no-undef const buf = await menneu.render(source, {}, opts); let bufStr = buf.toString(); if (exportPath.length === 0) { @@ -131,7 +130,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI // To avoid cross-origin, use Blob URLs instead. // const resultUrl = 'data:text/html;base64,' + menneu.getAppEnv().RedAgateUtil.Base64.encode(buf); - // eslint-disable-next-line no-undef const resultUrl = URL.createObjectURL(new Blob([bufStr], { type: 'text/html' })); if (exportPath.length > 0) { @@ -149,7 +147,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI const file = await nativeSaveFileHandle.getFile(); return await file.text(); } else { - // eslint-disable-next-line no-undef const response = await fetch(welcomeFile); return await response.text(); } @@ -160,7 +157,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI let p = await pathJoin(...filePath); let b = await getBaseName(p); - // eslint-disable-next-line no-undef const util = menneu.getAppEnv().RedAgateUtil; const modFilters = await import('../filefilters') @@ -191,7 +187,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI if (! forExport) { try { - // eslint-disable-next-line require-atomic-updates, no-undef window.location.hash = `filename=${encodeURIComponent(b)}&open.d=${util.Base64.encode(pako.deflate( util.TextEncoding.encodeToUtf8(text))) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')}`; @@ -286,7 +281,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI getStartupFile_ = (async () => { let targetPath = 'Welcome.md'; let targetUrl = welcomeFile; - // eslint-disable-next-line no-undef const util = menneu.getAppEnv().RedAgateUtil; if (window.location.hash) { @@ -299,7 +293,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI if (result['open.d']) { targetPath = result['filename'] || 'Untitled.md'; try { - // eslint-disable-next-line no-undef targetUrl = `data:text/plain;base64,${util.Base64.encode(pako.inflate( util.Base64.decode( result['open.d'].replace(/-/g, '+').replace(/_/g, '/'))))}`; @@ -318,7 +311,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI targetUrl = `data:text/plain,`; } } - // eslint-disable-next-line no-undef const response = await fetch(targetUrl, {}); if (response.ok) { return { @@ -404,7 +396,6 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI }), fileInfo: (async (file) => { const promise = new Promise((resolve, reject) => { - // eslint-disable-next-line no-undef const reader = new FileReader(); // eslint-disable-next-line no-unused-vars reader.onload = ev => { diff --git a/src.renderer/assets/script/libs/remote.js b/src.renderer/assets/script/libs/remote.js index 50e50df..224a737 100644 --- a/src.renderer/assets/script/libs/remote.js +++ b/src.renderer/assets/script/libs/remote.js @@ -24,7 +24,6 @@ export async function initBackend() { if (backend_) { return backend_; } - // eslint-disable-next-line require-atomic-updates backend_ = (await carlo.loadParams())[0]; await backend_.setFrontend(rpc.handle(frontend_)); } From 318d9c38ed66654b022036ec81404202952d2a5a Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 20:00:54 +0900 Subject: [PATCH 08/11] Fix lint errors --- src.mainproc/.eslintrc | 2 +- src.mainproc/ipc/app.ts | 44 ++++++++++++++++++------------ src.mainproc/windows/MainWindow.ts | 6 ++-- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src.mainproc/.eslintrc b/src.mainproc/.eslintrc index 9852516..0d979c8 100644 --- a/src.mainproc/.eslintrc +++ b/src.mainproc/.eslintrc @@ -9,7 +9,7 @@ "rules": { "@typescript-eslint/no-unsafe-assignment": 0, "@typescript-eslint/no-unsafe-member-access": 0, - "@typescript-eslint/no-unsafe-call": 0, + "@typescript-eslint/no-unsafe-call": 1, "@typescript-eslint/no-unsafe-return": 0, "@typescript-eslint/restrict-template-expressions": 0, "@typescript-eslint/prefer-regexp-exec": 0 diff --git a/src.mainproc/ipc/app.ts b/src.mainproc/ipc/app.ts index 17521e2..493e3c7 100644 --- a/src.mainproc/ipc/app.ts +++ b/src.mainproc/ipc/app.ts @@ -10,7 +10,8 @@ import util from 'util'; import { HtmlRenderer } from 'red-agate/modules/red-agate/renderer'; import requireDynamic from 'red-agate-util/modules/runtime/require-dynamic'; import { render, - getAppEnv } from 'menneu/modules'; + getAppEnv, + CliConfig } from 'menneu/modules'; import { ipcMain, dialog, BrowserWindow, @@ -89,7 +90,7 @@ HtmlRenderer.rendererPackageName = 'puppeteer-core'; function ipc(eventName: string, fn: (arg: any, sender: WebContents) => any) { // eslint-disable-next-line @typescript-eslint/no-misused-promises - ipcMain.on(eventName, async (event: any, arg: any) => { + ipcMain.on(eventName, async (event, arg: any) => { try { let ret = fn(arg, event.sender); if (ret instanceof Promise) { @@ -108,7 +109,7 @@ function ipc(eventName: string, fn: (arg: any, sender: WebContents) => any) { function ipcSync(eventName: string, fn: (arg: any, sender: WebContents) => any) { - ipcMain.on(eventName, (event: any, arg: any) => { + ipcMain.on(eventName, (event, arg: any) => { try { event.returnValue = fn(arg, event.sender); } catch (e) { @@ -127,7 +128,7 @@ function getDesktopPath() { } -ipcMain.on('app:editor:toggleFullScreen', (event: any, arg: any) => { +ipcMain.on('app:editor:toggleFullScreen', (event, arg: any) => { try { if (arg.force || app.isPackaged) { const win = BrowserWindow.fromWebContents(event.sender); @@ -144,7 +145,7 @@ ipcMain.on('app:editor:toggleFullScreen', (event: any, arg: any) => { }); -ipcMain.on('app:editor:notifyEditorDirty', (event: any, arg: any) => { +ipcMain.on('app:editor:notifyEditorDirty', (event, arg: any) => { try { const win = BrowserWindow.fromWebContents(event.sender); (win as any).editorIsDirty = arg.dirty; @@ -286,7 +287,7 @@ async function nativeFileSaveDialog(sender: BrowserWindow | null, title: string, ipc('app:editor:renderByMenneu', arg => renderByMenneu(arg.source, arg.data, arg.options, arg.srcPath, ...arg.exportPath)); async function renderByMenneu( - source: string, data: Record | string, options: any, srcPath: string, ...exportPath: string[]) { + source: string, data: Record | string, options: CliConfig, srcPath: string, ...exportPath: string[]) { if (srcPath === null || srcPath === void 0) { srcPath = path.join(getDesktopPath(), 'H8f5iZPgOwtZoIN4'); @@ -294,7 +295,7 @@ async function renderByMenneu( const srcDir = path.dirname(srcPath); const srcBaseName = path.basename(srcPath).slice(0, -(path.extname(srcPath).length)); - let cf = null; + let cf: CliConfig | null = null; if (! cf) { const fileName = path.join(srcDir, srcBaseName + '.config.json'); if (fs.existsSync(fileName)) { @@ -305,9 +306,12 @@ async function renderByMenneu( if (! cf) { const fileName = path.join(srcDir, srcBaseName + '.config.js'); if (fs.existsSync(fileName)) { - cf = requireDynamic(fileName); - if (typeof cf === 'function') { - cf = cf(getAppEnv()); + const mod = requireDynamic(fileName); + if (typeof mod === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + cf = mod(getAppEnv()); + } else { + cf = mod; } } } @@ -321,31 +325,37 @@ async function renderByMenneu( if (! cf) { const fileName = path.join(srcDir, 'menneu.config.js'); if (fs.existsSync(fileName)) { - cf = requireDynamic(fileName); - if (typeof cf === 'function') { - cf = cf(getAppEnv()); + const mod = requireDynamic(fileName); + if (typeof mod === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + cf = mod(getAppEnv()); + } else { + cf = mod; } } } - cf = Object.assign({}, cf || {}); + cf = Object.assign({}, cf || {}) as any; let d = data; if (! d) { const fileName = path.join(srcDir, srcBaseName + '.data.lisp'); if (fs.existsSync(fileName)) { d = await readFileAsync(fileName, { encoding: 'utf8' }); - cf.dataFormat = 'lisp'; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + cf!.dataFormat = 'lisp'; } } if (! d) { const fileName = path.join(srcDir, srcBaseName + '.data.json'); if (fs.existsSync(fileName)) { d = await readFileAsync(fileName, { encoding: 'utf8' }); - cf.dataFormat = 'json'; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + cf!.dataFormat = 'json'; } } - cf.tempDir = srcDir; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + cf!.tempDir = srcDir; let buf = null; try { // TODO: This has concurrency issue. diff --git a/src.mainproc/windows/MainWindow.ts b/src.mainproc/windows/MainWindow.ts index b7c0b01..491cfc8 100644 --- a/src.mainproc/windows/MainWindow.ts +++ b/src.mainproc/windows/MainWindow.ts @@ -43,7 +43,7 @@ export function createMainWindow(): electron.BrowserWindow { // CSP is not work while the location scheme is 'file'. // And when if navigated to http/https, CSP is to be enabled. - mainWindow.webContents.session.webRequest.onHeadersReceived((details: any, callback: any) => { + mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => { if (details.url.match(/^https?:\/\//)) { const headers = {...details.responseHeaders}; for (const key of Object.keys(headers)) { @@ -121,7 +121,7 @@ export function createMainWindow(): electron.BrowserWindow { mainWindow = null; }); - mainWindow.webContents.on('new-window', (event: any, url: string) => { + mainWindow.webContents.on('new-window', (event, url) => { event.preventDefault(); if (url.match(/^https?:\/\//)) { // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -129,7 +129,7 @@ export function createMainWindow(): electron.BrowserWindow { } }); - mainWindow.webContents.on('will-navigate', (event: any, url: string) => { + mainWindow.webContents.on('will-navigate', (event, url) => { // NOTE: Protect from `target="_top"` navigation links in the iframe. event.preventDefault(); }); From 14fb4f1b17a049b2013c034b53c3ae0277aed67a Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 20:17:52 +0900 Subject: [PATCH 09/11] Fix lint errors --- src.mainproc/.eslintrc | 2 +- src.mainproc/ipc/app.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src.mainproc/.eslintrc b/src.mainproc/.eslintrc index 0d979c8..2b2e993 100644 --- a/src.mainproc/.eslintrc +++ b/src.mainproc/.eslintrc @@ -10,7 +10,7 @@ "@typescript-eslint/no-unsafe-assignment": 0, "@typescript-eslint/no-unsafe-member-access": 0, "@typescript-eslint/no-unsafe-call": 1, - "@typescript-eslint/no-unsafe-return": 0, + "@typescript-eslint/no-unsafe-return": 1, "@typescript-eslint/restrict-template-expressions": 0, "@typescript-eslint/prefer-regexp-exec": 0 } diff --git a/src.mainproc/ipc/app.ts b/src.mainproc/ipc/app.ts index 493e3c7..61aa145 100644 --- a/src.mainproc/ipc/app.ts +++ b/src.mainproc/ipc/app.ts @@ -422,7 +422,14 @@ async function saveFile(text: string, ...filePath: string[]) { } -async function listDirectoryImpl(dir: string): Promise { +type FileInfo = { + name: string; + path?: string; + isDirectory: boolean; +}; + + +async function listDirectoryImpl(dir: string): Promise<{directory: string, files: FileInfo[]}> { if (typeof dir !== 'string') { throw new Error('directory name is not specified'); } @@ -436,7 +443,7 @@ async function listDirectoryImpl(dir: string): Promise { } if (stat.isDirectory()) { const files = await readdirAsync(dir); - const fileInfos = []; + const fileInfos: FileInfo[] = []; for (const f of files) { let isDir = false; let succeeded = false; From e36c5f3eb945ccee35d05ca9f1f914979d28a4ac Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 20:51:12 +0900 Subject: [PATCH 10/11] [FIX] Fix file filter --- src.renderer/assets/script/libs/backends/browser.js | 1 + src.renderer/assets/script/libs/filefilters.js | 13 ++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index d569d19..4c70f23 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -71,6 +71,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI const welcomeFile = 'assets/data/welcome.md'; + // TODO: Clear it if new file is dropped to the fileDropDialog let nativeSaveFileHandle = null; if (window.showOpenFilePicker) { diff --git a/src.renderer/assets/script/libs/filefilters.js b/src.renderer/assets/script/libs/filefilters.js index 8b9e539..08140b5 100644 --- a/src.renderer/assets/script/libs/filefilters.js +++ b/src.renderer/assets/script/libs/filefilters.js @@ -9,12 +9,12 @@ export const openFilter = [{ text: 'Markdown (*.md, *.markdown)', exts: ['.md', '.markdown'], mime: 'text/markdown', -},{ +}, { value: 'html', text: 'HTML (*.html, *.htm)', exts: ['.html', '.htm'], mime: 'text/html', -},{ +}, { value: '*', text: 'All Files (*.*)', exts: [], @@ -27,12 +27,7 @@ export const saveAsFilter = [{ text: 'Markdown (*.md, *.markdown)', exts: ['.md', '.markdown'], mime: 'text/markdown', -},{ - value: 'html', - text: 'HTML (*.html, *.htm)', - exts: ['.html', '.htm'], - mime: 'text/html', -},{ +}, { value: '*', text: 'All Files (*.*)', exts: [], @@ -52,7 +47,7 @@ export const exportFilter = [].concat( text: 'HTML (*.html, *.htm)', exts: ['.html', '.htm'], mime: 'text/html', - },{ + }, { value: '*', text: 'All Files (*.*)', exts: [], From 45864d17e9979fc70143eba74fdbbf57248c6c73 Mon Sep 17 00:00:00 2001 From: shellyln Date: Wed, 16 Dec 2020 22:52:50 +0900 Subject: [PATCH 11/11] [FIX] Clear opened file handler on file is dropped --- .../assets/script/components/filedropdialog.js | 4 ++++ src.renderer/assets/script/libs/backends/browser.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src.renderer/assets/script/components/filedropdialog.js b/src.renderer/assets/script/components/filedropdialog.js index e5e23f7..2eb5a74 100644 --- a/src.renderer/assets/script/components/filedropdialog.js +++ b/src.renderer/assets/script/components/filedropdialog.js @@ -59,12 +59,16 @@ export default class FileDropDialog extends React.Component { ev.preventDefault(); } + /** + * @param {DragEvent} ev + */ async handleOnDrop(ev) { try { ev.preventDefault(); const files = []; for (let i = 0; i < ev.dataTransfer.files.length; i++) { files.push(carlo.fileInfo(ev.dataTransfer.files[i])); + break; // Only use first item } const paths = await Promise.all(files); const texts = await Promise.all( diff --git a/src.renderer/assets/script/libs/backends/browser.js b/src.renderer/assets/script/libs/backends/browser.js index 4c70f23..c263a59 100644 --- a/src.renderer/assets/script/libs/backends/browser.js +++ b/src.renderer/assets/script/libs/backends/browser.js @@ -71,7 +71,7 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI const welcomeFile = 'assets/data/welcome.md'; - // TODO: Clear it if new file is dropped to the fileDropDialog + /** @type {FileSystemFileHandle | null} */ let nativeSaveFileHandle = null; if (window.showOpenFilePicker) { @@ -392,11 +392,20 @@ if (!window._MDNE_BACKEND_TYPE || window._MDNE_BACKEND_TYPE === 'BROWSER_EMULATI }; carlo_ = { + /** @type {() => Promise} */ loadParams: (async () => { return [backend_]; }), + /** + * @type {(file: File) => Promise<{path: string, fileBodyText: string}>} + * File is dropped. + * Get the file info and content. + */ fileInfo: (async (file) => { const promise = new Promise((resolve, reject) => { + // Reset the opened file's handler + nativeSaveFileHandle = null; + const reader = new FileReader(); // eslint-disable-next-line no-unused-vars reader.onload = ev => {