Skip to content

Commit

Permalink
Merge pull request #12 from shellyln/feature-file-system-access-api
Browse files Browse the repository at this point in the history
File system access API feature
  • Loading branch information
shellyln committed Dec 16, 2020
2 parents 3ef61e0 + 45864d1 commit 8a4d393
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 95 deletions.
4 changes: 2 additions & 2 deletions src.mainproc/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"rules": {
"@typescript-eslint/no-unsafe-assignment": 0,
"@typescript-eslint/no-unsafe-member-access": 0,
"@typescript-eslint/no-unsafe-call": 0,
"@typescript-eslint/no-unsafe-return": 0,
"@typescript-eslint/no-unsafe-call": 1,
"@typescript-eslint/no-unsafe-return": 1,
"@typescript-eslint/restrict-template-expressions": 0,
"@typescript-eslint/prefer-regexp-exec": 0
}
Expand Down
55 changes: 36 additions & 19 deletions src.mainproc/ipc/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -286,15 +287,15 @@ 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, unknown> | string, options: any, srcPath: string, ...exportPath: string[]) {
source: string, data: Record<string, unknown> | string, options: CliConfig, srcPath: string, ...exportPath: string[]) {

if (srcPath === null || srcPath === void 0) {
srcPath = path.join(getDesktopPath(), 'H8f5iZPgOwtZoIN4');
}
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)) {
Expand All @@ -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;
}
}
}
Expand All @@ -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.
Expand Down Expand Up @@ -412,7 +422,14 @@ async function saveFile(text: string, ...filePath: string[]) {
}


async function listDirectoryImpl(dir: string): Promise<any> {
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');
}
Expand All @@ -426,7 +443,7 @@ async function listDirectoryImpl(dir: string): Promise<any> {
}
if (stat.isDirectory()) {
const files = await readdirAsync(dir);
const fileInfos = [];
const fileInfos: FileInfo[] = [];
for (const f of files) {
let isDir = false;
let succeeded = false;
Expand Down
6 changes: 3 additions & 3 deletions src.mainproc/windows/MainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -121,15 +121,15 @@ 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
shell.openExternal(url);
}
});

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();
});
Expand Down
1 change: 1 addition & 0 deletions src.preload/preload-isolated.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 3 additions & 6 deletions src.renderer/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
10 changes: 9 additions & 1 deletion src.renderer/assets/script/components/aceeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';



Expand All @@ -32,7 +34,13 @@ 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);
AppState.filePath = fileInfo.path;
AppState.inputFormat = getInputFormat(AppState.filePath);

editor.session.setMode(getAceEditorMode(AppState.inputFormat));
editor.session.getUndoManager().markClean();
notifyEditorDirty(false);
updateAppIndicatorBar();
Expand Down
36 changes: 5 additions & 31 deletions src.renderer/assets/script/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -120,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);
}
}
Expand Down Expand Up @@ -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();
Expand All @@ -387,19 +388,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'],
},{
value: 'html',
text: 'HTML (*.html, *.htm)',
exts: ['.html', '.htm'],
},{
value: '*',
text: 'All files (*.*)',
exts: [],
}],
fileTypes: saveAsFilter,
}, async (currentDir, fileName) => {
try {
await this.fileSaveAs(currentDir, fileName);
Expand Down Expand Up @@ -450,22 +439,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'],
}]),
[{
value: 'html',
text: 'HTML (*.html, *.htm)',
exts: ['.html', '.htm'],
},{
value: '*',
text: 'All files (*.*)',
exts: [],
}]
),
fileTypes: exportFilter,
}, async (currentDir, fileName) => {
try {
await this.fileExport(currentDir, fileName);
Expand Down
20 changes: 6 additions & 14 deletions src.renderer/assets/script/components/filedropdialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AppState,
{ updateAppIndicatorBar } from '../libs/appstate.js';
import { getInputFormat,
getAceEditorMode } from '../libs/modes.js';
import { openFilter } from '../libs/filefilters';



Expand Down Expand Up @@ -58,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(
Expand All @@ -88,19 +93,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'],
},{
value: 'html',
text: 'HTML (*.html, *.htm)',
exts: ['.html', '.htm'],
},{
value: '*',
text: 'All files (*.*)',
exts: [],
}],
fileTypes: openFilter,
}, async (currentDir, fileName) => {
const path = await pathJoin(currentDir, fileName);
const text = await loadFile(path);
Expand All @@ -109,7 +102,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();
Expand Down
1 change: 1 addition & 0 deletions src.renderer/assets/script/components/fileopendialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down
1 change: 1 addition & 0 deletions src.renderer/assets/script/components/filesavedialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down

0 comments on commit 8a4d393

Please sign in to comment.