mirror of
https://github.com/zadam/trilium.git
synced 2025-11-17 10:40:41 +01:00
Merge remote-tracking branch 'origin/develop' into feature/trilium_next_theme
This commit is contained in:
@@ -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' },
|
||||
]
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user