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,54 @@
|
||||
/** @jest-environment jsdom */
|
||||
import getFormData from './getFormData';
|
||||
|
||||
describe('getFormData', () => {
|
||||
afterEach(() => {
|
||||
// Remove all forms to prevent tests from conflicting -- getFormData
|
||||
// uses document.querySelectorAll to select all forms.
|
||||
document.body.replaceChildren();
|
||||
});
|
||||
|
||||
const addTextInputWithValue = (parent: HTMLElement, value: string, name: string) => {
|
||||
const input = document.createElement('input');
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
parent.appendChild(input);
|
||||
};
|
||||
|
||||
test('should return data for all forms', () => {
|
||||
const testForm1 = document.createElement('form');
|
||||
testForm1.setAttribute('name', 'test-form-1');
|
||||
addTextInputWithValue(testForm1, 'Test', 'input');
|
||||
addTextInputWithValue(testForm1, 'Test2', 'another-input');
|
||||
document.body.appendChild(testForm1);
|
||||
|
||||
const testForm2 = document.createElement('form');
|
||||
testForm2.setAttribute('name', 'another-test-form');
|
||||
addTextInputWithValue(testForm2, 'Test2', 'text-input');
|
||||
document.body.appendChild(testForm2);
|
||||
|
||||
expect(getFormData()).toMatchObject({
|
||||
'test-form-1': {
|
||||
'input': 'Test',
|
||||
'another-input': 'Test2',
|
||||
},
|
||||
'another-test-form': {
|
||||
'text-input': 'Test2',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should auto-number forms without a name', () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const testForm = document.createElement('form');
|
||||
addTextInputWithValue(testForm, `Form ${i}`, 'input');
|
||||
document.body.appendChild(testForm);
|
||||
}
|
||||
|
||||
const formData = getFormData();
|
||||
expect(Object.keys(formData)).toHaveLength(3);
|
||||
expect(formData['form-0']).toMatchObject({ 'input': 'Form 0' });
|
||||
expect(formData['form-1']).toMatchObject({ 'input': 'Form 1' });
|
||||
expect(formData['form-2']).toMatchObject({ 'input': 'Form 2' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
const getFormData = () => {
|
||||
const forms = document.querySelectorAll('form');
|
||||
if (forms.length === 0) return null;
|
||||
|
||||
const serializeForm = (form: HTMLFormElement) => {
|
||||
const formData = new FormData(form);
|
||||
const serializedData: Record<string, FormDataEntryValue | null> = {};
|
||||
for (const key of formData.keys()) {
|
||||
serializedData[key] = formData.get(key);
|
||||
}
|
||||
return serializedData;
|
||||
};
|
||||
|
||||
const result = Object.create(null);
|
||||
let untitledFormId = 0;
|
||||
for (const form of forms) {
|
||||
const formId = form.getAttribute('name') || `form-${untitledFormId++}`;
|
||||
result[formId] = serializeForm(form);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
export default getFormData;
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
type OnErrorCallback = (errorMessage: string)=> void;
|
||||
|
||||
const reportUnhandledErrors = (onError: OnErrorCallback) => {
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
onError(`Unhandled promise rejection: ${event.reason}. Promise: ${event.promise}.`);
|
||||
});
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
onError(`Error: ${event.message}. ${event.error?.stack ?? ''}`);
|
||||
});
|
||||
};
|
||||
|
||||
export default reportUnhandledErrors;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { LogLevel } from '../../types';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
type OnLogCallback = (level: LogLevel, message: string)=> void;
|
||||
|
||||
const wrapConsoleLog = (onLog: OnLogCallback) => {
|
||||
// inOnLogCallback keeps track of whether onLog is currently being called or not.
|
||||
// This helps prevent StackOverflows if onLog itself calls console.log/console.error.
|
||||
let inOnLogCallback = false;
|
||||
|
||||
const wrapLogFunction = (key: keyof typeof console, logLevel: LogLevel) => {
|
||||
const originalLog = (console[key] as ((...args: unknown[])=> void) | undefined) ?? (()=>{});
|
||||
|
||||
console[key] = function(...args: unknown[]) {
|
||||
originalLog.call(this, ...args);
|
||||
if (inOnLogCallback) return;
|
||||
|
||||
// Work around
|
||||
// Uncaught (in promise) TypeError: Cannot convert object to primitive value
|
||||
// when console.log is called with an object that
|
||||
// can't be converted to a string.
|
||||
let argsString;
|
||||
try {
|
||||
argsString = args.join(' ');
|
||||
} catch (error) {
|
||||
argsString = `Error converting console arguments to string: ${error}`;
|
||||
}
|
||||
|
||||
// Long messages can be slow to transfer over IPC and make other messages difficult
|
||||
// to find/read in React Native's output.
|
||||
const maxLength = 4096;
|
||||
if (argsString.length > maxLength) {
|
||||
argsString = `${argsString.substring(0, maxLength)}...`;
|
||||
}
|
||||
|
||||
try {
|
||||
inOnLogCallback = true;
|
||||
onLog(logLevel, argsString);
|
||||
} finally {
|
||||
inOnLogCallback = false;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Assigning a generic (...args: unknown[]) function to a specific console method (which is the union of all method types) requires a cast through `any`
|
||||
} as any;
|
||||
};
|
||||
|
||||
wrapLogFunction('timeLog', LogLevel.Debug);
|
||||
wrapLogFunction('log', LogLevel.Debug);
|
||||
wrapLogFunction('warn', LogLevel.Warn);
|
||||
wrapLogFunction('info', LogLevel.Info);
|
||||
wrapLogFunction('error', LogLevel.Error);
|
||||
};
|
||||
|
||||
export default wrapConsoleLog;
|
||||
在新工单中引用
屏蔽一个用户