Merge remote-tracking branch 'origin/develop' into feature/trilium_next_theme

This commit is contained in:
Elian Doran
2024-11-30 00:55:37 +02:00
48 changed files with 427 additions and 83 deletions

View File

@@ -210,9 +210,9 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
isExpanded: true,
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ],
children: [
{ id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square',
{ id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-chevron-left',
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
{ id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square',
{ id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-chevron-right',
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
{ id: '_lbBackendLog', title: 'Backend Log', type: 'launcher', targetNoteId: '_backendLog', icon: 'bx bx-terminal' },
]

View File

@@ -1,5 +1,21 @@
import sanitizeHtml from "sanitize-html";
import sanitizeUrl from "@braintree/sanitize-url";
import optionService from "./options.js";
// Default list of allowed HTML tags
export const DEFAULT_ALLOWED_TAGS = [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tfoot', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'figcaption', 'span', 'label', 'input', 'details', 'summary', 'address', 'aside', 'footer',
'header', 'hgroup', 'main', 'nav', 'dl', 'dt', 'menu', 'bdi', 'bdo', 'dfn', 'kbd', 'mark', 'q', 'time',
'var', 'wbr', 'area', 'map', 'track', 'video', 'audio', 'picture', 'del', 'ins',
'en-media', // for ENEX import
// Additional tags (https://github.com/TriliumNext/Notes/issues/567)
'acronym', 'article', 'big', 'button', 'cite', 'col', 'colgroup', 'data', 'dd',
'fieldset', 'form', 'legend', 'meter', 'noscript', 'option', 'progress', 'rp',
'samp', 'small', 'sub', 'sup', 'template', 'textarea', 'tt'
] as const;
// intended mainly as protection against XSS via import
// secondarily, it (partly) protects against "CSS takeover"
@@ -23,17 +39,18 @@ function sanitize(dirtyHtml: string) {
}
}
// Get allowed tags from options, with fallback to default list if option not yet set
let allowedTags;
try {
allowedTags = JSON.parse(optionService.getOption('allowedHtmlTags'));
} catch (e) {
// Fallback to default list if option doesn't exist or is invalid
allowedTags = DEFAULT_ALLOWED_TAGS;
}
// to minimize document changes, compress H
return sanitizeHtml(dirtyHtml, {
allowedTags: [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tfoot', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'figcaption', 'span', 'label', 'input', 'details', 'summary', 'address', 'aside', 'footer',
'header', 'hgroup', 'main', 'nav', 'dl', 'dt', 'menu', 'bdi', 'bdo', 'dfn', 'kbd', 'mark', 'q', 'time',
'var', 'wbr', 'area', 'map', 'track', 'video', 'audio', 'picture', 'del', 'ins',
'en-media' // for ENEX import
],
allowedTags,
allowedAttributes: {
'*': [ 'class', 'style', 'title', 'src', 'href', 'hash', 'disabled', 'align', 'alt', 'center', 'data-*' ]
},
@@ -43,7 +60,10 @@ function sanitize(dirtyHtml: string) {
'mumble', 'nfs', 'onenote', 'pop', 'rmi', 's3', 'sftp', 'skype', 'sms', 'spotify', 'steam', 'svn', 'udp',
'view-source', 'vnc', 'ws', 'wss', 'xmpp', 'jdbc', 'slack'
],
transformTags,
nonTextTags: [
'head'
],
transformTags
});
}

View File

@@ -149,15 +149,20 @@ function importMarkdown(taskContext: TaskContext, file: File, parentNote: BNote)
}
function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) {
const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
let content = file.buffer.toString("utf-8");
if (taskContext?.data?.safeImport) {
content = htmlSanitizer.sanitize(content);
}
// Try to get title from HTML first, fall back to filename
// We do this before sanitization since that turns all <h1>s into <h2>
const htmlTitle = importUtils.extractHtmlTitle(content);
const title = htmlTitle || utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
content = importUtils.handleH1(content, title);
if (taskContext?.data?.safeImport) {
content = htmlSanitizer.sanitize(content);
}
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,
@@ -166,9 +171,9 @@ function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) {
mime: 'text/html',
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
});
taskContext.increaseProgressCount();
return note;
}

View File

@@ -1,7 +1,7 @@
"use strict";
function handleH1(content: string, title: string) {
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
content = content.replace(/<h1[^>]*>([^<]*)<\/h1>/gi, (match, text) => {
if (title.trim() === text.trim()) {
return ""; // remove whole H1 tag
} else {
@@ -11,6 +11,12 @@ function handleH1(content: string, title: string) {
return content;
}
function extractHtmlTitle(content: string): string | null {
const titleMatch = content.match(/<title[^>]*>([^<]+)<\/title>/i);
return titleMatch ? titleMatch[1].trim() : null;
}
export default {
handleH1
handleH1,
extractHtmlTitle
};

View File

@@ -135,7 +135,20 @@ const defaultOptions: DefaultOption[] = [
// Text note configuration
{ name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true },
{ name: "layoutOrientation", value: "vertical", isSynced: false }
// HTML import configuration
{ name: "layoutOrientation", value: "vertical", isSynced: false },
{ name: "allowedHtmlTags", value: JSON.stringify([
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tfoot', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'figcaption', 'span', 'label', 'input', 'details', 'summary', 'address', 'aside', 'footer',
'header', 'hgroup', 'main', 'nav', 'dl', 'dt', 'menu', 'bdi', 'bdo', 'dfn', 'kbd', 'mark', 'q', 'time',
'var', 'wbr', 'area', 'map', 'track', 'video', 'audio', 'picture', 'del', 'ins',
'en-media',
'acronym', 'article', 'big', 'button', 'cite', 'col', 'colgroup', 'data', 'dd',
'fieldset', 'form', 'legend', 'meter', 'noscript', 'option', 'progress', 'rp',
'samp', 'small', 'sub', 'sup', 'template', 'textarea', 'tt'
]), isSynced: true },
];
/**

View File

@@ -340,9 +340,9 @@ function findFirstNoteWithQuery(query: string, searchContext: SearchContext): BN
return searchResults.length > 0 ? becca.notes[searchResults[0].noteId] : null;
}
function searchNotesForAutocomplete(query: string) {
function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
const searchContext = new SearchContext({
fastSearch: true,
fastSearch: fastSearch,
includeArchivedNotes: false,
includeHiddenNotes: true,
fuzzyAttributeSearch: true,