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,127 @@
|
||||
// React Native WebView cannot load external JS files, however it can load
|
||||
// arbitrary JS via the injectedJavaScript property. So we use this to load external
|
||||
// files: First here we convert the JS file to a plain string, and that string
|
||||
// is then loaded by eg. the Mermaid plugin, and finally injected in the WebView.
|
||||
|
||||
import { dirname, extname, basename, resolve } from 'path';
|
||||
|
||||
import * as esbuild from 'esbuild';
|
||||
import copyAssets from './copyAssets';
|
||||
import { writeFile, readFile } from 'fs-extra';
|
||||
|
||||
export default class BundledFile {
|
||||
private readonly bundleOutputPathBase_: string;
|
||||
private readonly bundleBaseName_: string;
|
||||
private readonly rootFileDirectory_: string;
|
||||
|
||||
public constructor(
|
||||
public readonly bundleName: string,
|
||||
private readonly sourceFilePath_: string,
|
||||
) {
|
||||
this.rootFileDirectory_ = dirname(sourceFilePath_);
|
||||
this.bundleBaseName_ = basename(sourceFilePath_, extname(sourceFilePath_));
|
||||
this.bundleOutputPathBase_ = `${this.rootFileDirectory_}/${this.bundleBaseName_}.bundle`;
|
||||
}
|
||||
|
||||
private makeBuildContext(mode: 'production' | 'development') {
|
||||
return esbuild.context({
|
||||
entryPoints: [this.sourceFilePath_],
|
||||
outfile: `${this.bundleOutputPathBase_}.js`,
|
||||
minify: mode === 'production',
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
globalName: this.bundleName,
|
||||
metafile: false,
|
||||
|
||||
// es5: Have Webpack's generated code target ES5. This doesn't apply to code not
|
||||
// generated by Webpack.
|
||||
target: ['chrome58', 'safari14'],
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: 'joplin--node-polyfills',
|
||||
setup: build => {
|
||||
build.onResolve({ filter: /^(path|events)$/ }, args => {
|
||||
let path = args.path;
|
||||
if (args.path === 'path') {
|
||||
path = require.resolve('path-browserify');
|
||||
} else if (args.path === 'events') {
|
||||
path = require.resolve('events/');
|
||||
}
|
||||
|
||||
return { path };
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
// Supports require(...)ing SVG images
|
||||
name: 'joplin--require-svg',
|
||||
setup: build => {
|
||||
// A relative path to an SVG:
|
||||
build.onResolve({ filter: /^\.{1,2}\/.*\.svg$/ }, args => ({
|
||||
path: resolve(args.resolveDir, args.path),
|
||||
namespace: 'joplin-require-svg',
|
||||
}));
|
||||
|
||||
build.onLoad({ filter: /^.*$/, namespace: 'joplin-require-svg' }, async args => {
|
||||
const fileContent = await readFile(args.path, 'utf-8');
|
||||
return { contents: `
|
||||
let svg = null;
|
||||
export default () => {
|
||||
svg ??= (() => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(${JSON.stringify(fileContent)}, 'image/svg+xml');
|
||||
return doc.querySelector('svg');
|
||||
})();
|
||||
return svg.cloneNode(true);
|
||||
};
|
||||
` };
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'joplin--copy-final',
|
||||
setup: build => {
|
||||
build.onEnd(async (result) => {
|
||||
if (result.errors.length === 0) {
|
||||
console.log('copy output');
|
||||
await this.copyToImportableFile_();
|
||||
} else {
|
||||
console.warn('Copying skipped. Build produced errors');
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Creates a file that can be imported by React native. This file contains the
|
||||
// bundled JS as a string.
|
||||
private async copyToImportableFile_() {
|
||||
await copyAssets(`${this.bundleName}.bundle`, {
|
||||
js: `${this.bundleOutputPathBase_}.js`,
|
||||
css: `${this.bundleOutputPathBase_}.css`,
|
||||
});
|
||||
}
|
||||
|
||||
// Create a minified JS file in the same directory as `this.sourceFilePath` with
|
||||
// the same name.
|
||||
public async build() {
|
||||
console.info(`Building bundle: ${this.bundleName}...`);
|
||||
const compiler = await this.makeBuildContext('production');
|
||||
const result = await compiler.rebuild();
|
||||
await compiler.dispose();
|
||||
|
||||
if (result?.metafile) {
|
||||
await writeFile(`${this.bundleOutputPathBase_}.meta.json`, JSON.stringify(result.metafile, undefined, '\t'));
|
||||
}
|
||||
}
|
||||
|
||||
public async startWatching() {
|
||||
console.info(`Watching bundle: ${this.bundleName}...`);
|
||||
const compiler = await this.makeBuildContext('development');
|
||||
await compiler.watch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
import { dirname } from 'path';
|
||||
|
||||
export const mobileDir = dirname(dirname(__dirname));
|
||||
export const outputDir = `${mobileDir}/lib/rnInjectedJs`;
|
||||
export const rootDir = dirname(dirname(mobileDir));
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
import { exists, readFile, writeFile } from 'fs-extra';
|
||||
import { outputDir } from './constants';
|
||||
|
||||
type InputFilePaths = {
|
||||
js: string;
|
||||
css?: string;
|
||||
};
|
||||
|
||||
// Stores the contents of the file at [filePath] as an importable string.
|
||||
// [name] should be the name (excluding the .js extension) of the output file that will contain
|
||||
// the JSON-ified file content.
|
||||
const copyAssets = async (name: string, input: InputFilePaths) => {
|
||||
const outputPath = `${outputDir}/${name}.js`;
|
||||
console.info(`Creating: ${outputPath}`);
|
||||
|
||||
const hasJs = await exists(input.js);
|
||||
const js = hasJs ? await readFile(input.js, 'utf-8') : null;
|
||||
const hasCss = !!input.css && await exists(input.css);
|
||||
const css = hasCss ? await readFile(input.css, 'utf-8') : null;
|
||||
|
||||
const json = `module.exports = {
|
||||
js: ${JSON.stringify(js)},
|
||||
css: ${JSON.stringify(css)}
|
||||
};`;
|
||||
await writeFile(outputPath, json);
|
||||
};
|
||||
export default copyAssets;
|
||||
@@ -0,0 +1,59 @@
|
||||
import BundledFile from './BundledFile';
|
||||
import { mkdirp } from 'fs-extra';
|
||||
import { mobileDir, outputDir } from './constants';
|
||||
import copyAssets from './copyAssets';
|
||||
import { readdir, existsSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
|
||||
const getBundles = async () => {
|
||||
// All folders in the contentScripts/ directories are bundles
|
||||
const contentScriptDir = `${mobileDir}/contentScripts/`;
|
||||
const bundles = [];
|
||||
|
||||
for (const folderOrFileName of await readdir(contentScriptDir)) {
|
||||
// Only check subfolders
|
||||
if (folderOrFileName.includes('.')) continue;
|
||||
// Skip utilities shared between bundles
|
||||
if (folderOrFileName === 'utils') continue;
|
||||
|
||||
const bundlePath = join(contentScriptDir, folderOrFileName);
|
||||
const indexPath = join(bundlePath, 'contentScript', 'index.ts');
|
||||
const contentScriptPath = existsSync(indexPath) ? indexPath : join(bundlePath, 'contentScript.ts');
|
||||
bundles.push(new BundledFile(folderOrFileName, contentScriptPath));
|
||||
}
|
||||
|
||||
// Bundled JS may also exist within the components/ directory:
|
||||
bundles.push(new BundledFile(
|
||||
'pluginBackgroundPage',
|
||||
`${mobileDir}/components/plugins/backgroundPage/pluginRunnerBackgroundPage.ts`,
|
||||
));
|
||||
|
||||
return bundles;
|
||||
};
|
||||
|
||||
const gulpTasks = {
|
||||
beforeBundle: {
|
||||
fn: () => mkdirp(outputDir),
|
||||
},
|
||||
buildBundledJs: {
|
||||
fn: async () => {
|
||||
for (const bundle of await getBundles()) {
|
||||
await bundle.build();
|
||||
}
|
||||
},
|
||||
},
|
||||
watchBundledJs: {
|
||||
fn: async () => {
|
||||
const watchPromises = [];
|
||||
for (const bundle of await getBundles()) {
|
||||
watchPromises.push(bundle.startWatching());
|
||||
}
|
||||
await Promise.all(watchPromises);
|
||||
},
|
||||
},
|
||||
copyWebviewLib: {
|
||||
fn: () => copyAssets('webviewLib', { js: `${mobileDir}/../lib/renderers/webviewLib.js` }),
|
||||
},
|
||||
};
|
||||
|
||||
export default gulpTasks;
|
||||
在新工单中引用
屏蔽一个用户