feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
这个提交包含在:
@@ -0,0 +1,86 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
import { splitCommandString } from '@joplin/utils';
|
||||
import { spawn, SpawnOptions } from 'child_process';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import Setting from '../../models/Setting';
|
||||
import { fileExtension } from '../../path-utils';
|
||||
import shim from '../../shim';
|
||||
|
||||
const logger = Logger.create('ExternalEditWatcher/utils');
|
||||
|
||||
const spawnCommand = async (path: string, args: string[], options: SpawnOptions) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// App bundles need to be opened using the `open` command.
|
||||
// Additional args can be specified after --args, and the
|
||||
// -n flag is needed to ensure that the app is always launched
|
||||
// with the arguments. Without it, if the app is already opened,
|
||||
// it will just bring it to the foreground without opening the file.
|
||||
// So the full command is:
|
||||
//
|
||||
// open -n /path/to/editor.app --args -app-flag -bla /path/to/file.md
|
||||
//
|
||||
if (shim.isMac() && fileExtension(path) === 'app') {
|
||||
args = args.slice();
|
||||
args.splice(0, 0, '--args');
|
||||
args.splice(0, 0, path);
|
||||
args.splice(0, 0, '-n');
|
||||
path = 'open';
|
||||
}
|
||||
|
||||
const wrapError = (error: Error | null) => {
|
||||
if (!error) return error;
|
||||
const msg = error.message ? [error.message] : [];
|
||||
msg.push(`Command was: "${path}" ${args.join(' ')}`);
|
||||
error.message = msg.join('\n\n');
|
||||
return error;
|
||||
};
|
||||
|
||||
try {
|
||||
const subProcess = spawn(path, args, options);
|
||||
|
||||
const iid = shim.setInterval(() => {
|
||||
if (subProcess && subProcess.pid) {
|
||||
logger.debug(`Started editor with PID ${subProcess.pid}`);
|
||||
shim.clearInterval(iid);
|
||||
resolve(null);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
subProcess.on('error', (error: Error) => {
|
||||
shim.clearInterval(iid);
|
||||
reject(wrapError(error));
|
||||
});
|
||||
} catch (error) {
|
||||
throw wrapError(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const textEditorCommand = () => {
|
||||
const editorCommand = Setting.value('editor');
|
||||
if (!editorCommand) return null;
|
||||
|
||||
const s = splitCommandString(editorCommand, { handleEscape: false });
|
||||
const path = s.splice(0, 1);
|
||||
if (!path.length) throw new Error(`Invalid editor command: ${editorCommand}`);
|
||||
|
||||
return {
|
||||
path: path[0],
|
||||
args: s,
|
||||
};
|
||||
};
|
||||
|
||||
interface ExternalBridge {
|
||||
openItem(filePath: string): void;
|
||||
}
|
||||
|
||||
export const openFileWithExternalEditor = async (filePath: string, bridge: ExternalBridge) => {
|
||||
const cmd = textEditorCommand();
|
||||
if (!cmd) {
|
||||
bridge.openItem(filePath);
|
||||
} else {
|
||||
cmd.args.push(filePath);
|
||||
await spawnCommand(cmd.path, cmd.args, { detached: true });
|
||||
}
|
||||
};
|
||||
在新工单中引用
屏蔽一个用户