一些检查失败了
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
124 行
4.2 KiB
TypeScript
124 行
4.2 KiB
TypeScript
import { ListRendererDependency } from '@joplin/lib/services/plugins/api/noteListType';
|
|
import { FolderEntity, NoteEntity, TagEntity } from '@joplin/lib/services/database/types';
|
|
import { Size } from '@joplin/utils/types';
|
|
import Note from '@joplin/lib/models/Note';
|
|
import Setting from '@joplin/lib/models/Setting';
|
|
import { _ } from '@joplin/lib/locale';
|
|
|
|
interface CheckboxStats {
|
|
total: number;
|
|
checked: number;
|
|
percent: number;
|
|
isComplete: boolean;
|
|
}
|
|
|
|
const countCheckboxes = (body: string): CheckboxStats | null => {
|
|
if (!body) return null;
|
|
|
|
// Match unchecked: - [ ] and checked: - [x] or - [X]
|
|
const uncheckedMatches = body.match(/(^|\n)[ \t>]*- \[ \]/g);
|
|
const checkedMatches = body.match(/(^|\n)[ \t>]*- \[[xX]\]/g);
|
|
|
|
const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
|
|
const checked = checkedMatches ? checkedMatches.length : 0;
|
|
const total = unchecked + checked;
|
|
|
|
if (total === 0) return null;
|
|
|
|
return {
|
|
total,
|
|
checked,
|
|
percent: Math.round((checked / total) * 100),
|
|
isComplete: checked === total,
|
|
};
|
|
};
|
|
|
|
const prepareViewProps = async (
|
|
dependencies: ListRendererDependency[],
|
|
note: NoteEntity,
|
|
itemSize: Size,
|
|
selected: boolean,
|
|
noteTitleHtml: string,
|
|
noteIsWatched: boolean,
|
|
noteTags: TagEntity[],
|
|
folder: FolderEntity | null,
|
|
itemIndex: number,
|
|
noteIsPublished = false,
|
|
) => {
|
|
const output: {
|
|
note?: Record<string, unknown> & { folder?: Record<string, unknown> };
|
|
item?: { size?: Record<string, unknown>; selected?: boolean; index?: number };
|
|
} = {};
|
|
|
|
for (const dep of dependencies) {
|
|
if (dep.startsWith('note.')) {
|
|
const splitted = dep.split('.');
|
|
if (splitted.length <= 1) throw new Error(`Invalid dependency name: ${dep}`);
|
|
const propName = splitted.pop();
|
|
|
|
if (!output.note) output.note = {};
|
|
if (dep === 'note.titleHtml') { // For backward compatibility
|
|
output.note.titleHtml = noteTitleHtml;
|
|
} else if (dep === 'note.isWatched') {
|
|
output.note[propName] = noteIsWatched;
|
|
} else if (dep === 'note.is_published') {
|
|
output.note[propName] = noteIsPublished;
|
|
} else if (dep === 'note.is_pinned') {
|
|
output.note[propName] = (note as unknown as { is_pinned?: number }).is_pinned || 0;
|
|
} else if (dep === 'note.tags') {
|
|
output.note[propName] = noteTags;
|
|
} else if (dep === 'note.folder.title') {
|
|
if (!output.note.folder) output.note.folder = {};
|
|
output.note.folder[propName] = folder.title;
|
|
} else if (dep === 'note.todoStatusText') {
|
|
let taskStatus = '';
|
|
if (note.is_todo) {
|
|
taskStatus = note.todo_completed ? _('Complete to-do') : _('Incomplete to-do');
|
|
}
|
|
output.note[propName] = taskStatus;
|
|
} else if (dep === 'note.checkboxes') {
|
|
// Only load the note body and compute checkbox stats if the setting is enabled
|
|
if (Setting.value('notes.showCheckboxCompletionChart')) {
|
|
if (!('body' in note)) note = await Note.load(note.id);
|
|
output.note[propName] = countCheckboxes(note.body);
|
|
} else {
|
|
output.note[propName] = null;
|
|
}
|
|
} else {
|
|
// The notes in the state only contain the properties defined in
|
|
// Note.previewFields(). It means that if a view request a
|
|
// property not present there, we need to load the full note.
|
|
// One such missing property is the note body, which we don't
|
|
// load by default.
|
|
if (!(propName in note)) note = await Note.load(note.id);
|
|
if (!(propName in note)) throw new Error(`Invalid dependency name: ${dep}`);
|
|
output.note[propName] = (note as unknown as Record<string, unknown>)[propName];
|
|
}
|
|
}
|
|
|
|
if (dep.startsWith('item.size.')) {
|
|
const splitted = dep.split('.');
|
|
if (splitted.length !== 3) throw new Error(`Invalid dependency name: ${dep}`);
|
|
const propName = splitted.pop();
|
|
if (!output.item) output.item = {};
|
|
if (!output.item.size) output.item.size = {};
|
|
if (!(propName in itemSize)) throw new Error(`Invalid dependency name: ${dep}`);
|
|
output.item.size[propName] = (itemSize as unknown as Record<string, unknown>)[propName];
|
|
}
|
|
|
|
if (dep === 'item.selected') {
|
|
if (!output.item) output.item = {};
|
|
output.item.selected = selected;
|
|
}
|
|
|
|
if (dep === 'item.index') {
|
|
if (!output.item) output.item = {};
|
|
output.item.index = itemIndex;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
};
|
|
|
|
export default prepareViewProps;
|