一些检查失败了
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
94 行
3.9 KiB
TypeScript
94 行
3.9 KiB
TypeScript
import { _, _n } from '../locale';
|
|
import Note from '../models/Note';
|
|
import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService';
|
|
import { MarkupLanguage } from '@joplin/renderer';
|
|
import { runtime as convertHtmlToMarkdown } from './convertHtmlToMarkdown';
|
|
import shim, { ToastType } from '../shim';
|
|
import { NoteEntity } from '../services/database/types';
|
|
import { itemIsReadOnly } from '../models/utils/readOnly';
|
|
import { ModelType } from '../BaseModel';
|
|
import ItemChange from '../models/ItemChange';
|
|
import Setting from '../models/Setting';
|
|
import isNoteLockEnabled from '../services/noteLock/isNoteLockEnabled';
|
|
import NoteLockNote, { NoteLockNoteEntity } from '../services/noteLock/NoteLockNote';
|
|
import NoteLockSession from '../services/noteLock/NoteLockSession';
|
|
import Logger from '@joplin/utils/Logger';
|
|
|
|
const logger = Logger.create('convertNoteToMarkdown');
|
|
|
|
export const declaration: CommandDeclaration = {
|
|
name: 'convertNoteToMarkdown',
|
|
label: () => _('Convert to Markdown'),
|
|
};
|
|
|
|
export const runtime = (): CommandRuntime => {
|
|
return {
|
|
execute: async (context: CommandContext, noteIds: string|string[] = []) => {
|
|
if (typeof noteIds === 'string') {
|
|
noteIds = [noteIds];
|
|
}
|
|
if (noteIds.length === 0) {
|
|
noteIds = context.state.selectedNoteIds;
|
|
}
|
|
|
|
const notes: NoteEntity[] = await Note.loadItemsByIdsOrFail(noteIds);
|
|
|
|
try {
|
|
const firstLockedNote = isNoteLockEnabled() ? notes.find(note => note.markup_language !== MarkupLanguage.Markdown && NoteLockNote.isLocked(note)) : null;
|
|
if (firstLockedNote && !NoteLockSession.instance().isUnlocked()) {
|
|
throw new Error(_('Cannot convert locked note: "%s"', firstLockedNote.title));
|
|
}
|
|
// Captured once so a session lock mid-run cannot fail the remaining conversions.
|
|
const noteLockKey = firstLockedNote ? NoteLockSession.instance().decryptedKey() : null;
|
|
let isFirst = true;
|
|
let processedCount = 0;
|
|
for (const note of notes) {
|
|
if (note.markup_language === MarkupLanguage.Markdown) {
|
|
logger.warn('Skipping item: Already Markdown.');
|
|
continue;
|
|
}
|
|
if (await itemIsReadOnly(Note, ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note.id, Setting.value('sync.userId'), context.state.shareService)) {
|
|
throw new Error(_('Cannot convert read-only item: "%s"', note.title));
|
|
}
|
|
// Every note converts through a full gated load and save, so a locked body is
|
|
// decrypted for the conversion and the converted note is encrypted again.
|
|
const sourceNote: NoteLockNoteEntity = await Note.load(note.id, { useNoteLock: true, noteLockKey });
|
|
const markdownBody = await convertHtmlToMarkdown().execute(context, sourceNote.body);
|
|
const backupNote = await Note.duplicate(note.id, {
|
|
changes: {
|
|
user_created_time: note.user_created_time,
|
|
user_updated_time: note.user_updated_time,
|
|
},
|
|
});
|
|
|
|
note.body = markdownBody;
|
|
note.markup_language = MarkupLanguage.Markdown;
|
|
note.updated_time = Date.now();
|
|
|
|
const toSave = { ...note, isDecrypted: sourceNote.isDecrypted };
|
|
await Note.save(toSave, { autoTimestamp: false, useNoteLock: true, noteLockKey });
|
|
await Note.delete(backupNote.id, { toTrash: true });
|
|
processedCount ++;
|
|
|
|
if (isFirst) {
|
|
context.dispatch({
|
|
type: 'NOTE_SELECT',
|
|
id: note.id,
|
|
});
|
|
isFirst = false;
|
|
}
|
|
}
|
|
|
|
void shim.showToast(_n(
|
|
'The note has been converted to Markdown and an HTML copy has been moved to the trash',
|
|
'The notes have been converted to Markdown and HTML copies have been moved to the trash',
|
|
processedCount,
|
|
), { type: ToastType.Success });
|
|
} catch (error) {
|
|
await shim.showErrorDialog(_('Could not convert notes to Markdown: %s', error.message));
|
|
}
|
|
},
|
|
enabledCondition: 'selectionIncludesHtmlNotes && (multipleNotesSelected || !noteIsReadOnly) && !noteLockContentUnavailable',
|
|
};
|
|
};
|