mirror of
https://github.com/zadam/trilium.git
synced 2025-11-10 08:29:00 +01:00
Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements
This commit is contained in:
commit
40b08e1828
@ -2433,3 +2433,7 @@ iframe.print-iframe {
|
|||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.excalidraw.theme--dark canvas {
|
||||||
|
--theme-filter: invert(100%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
@ -47,8 +47,9 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
|
this.$widget.attr("title", this.title);
|
||||||
this.tooltip = new Tooltip(this.$tooltip[0], {
|
this.tooltip = Tooltip.getOrCreateInstance(this.$widget[0], {
|
||||||
|
trigger: "hover",
|
||||||
placement: handleRightToLeftPlacement(this.settings.titlePlacement),
|
placement: handleRightToLeftPlacement(this.settings.titlePlacement),
|
||||||
fallbackPlacements: [ handleRightToLeftPlacement(this.settings.titlePlacement) ]
|
fallbackPlacements: [ handleRightToLeftPlacement(this.settings.titlePlacement) ]
|
||||||
});
|
});
|
||||||
@ -56,9 +57,7 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
|||||||
this.$widget
|
this.$widget
|
||||||
.find(".right-dropdown-button")
|
.find(".right-dropdown-button")
|
||||||
.addClass(this.iconClass)
|
.addClass(this.iconClass)
|
||||||
.on("click", () => this.tooltip.hide())
|
.on("click", () => this.tooltip.hide());
|
||||||
.on("mouseenter", () => this.tooltip.show())
|
|
||||||
.on("mouseleave", () => this.tooltip.hide());
|
|
||||||
|
|
||||||
this.$widget.on("show.bs.dropdown", async () => {
|
this.$widget.on("show.bs.dropdown", async () => {
|
||||||
await this.dropdownShown();
|
await this.dropdownShown();
|
||||||
|
|||||||
@ -141,7 +141,11 @@ function NoteContent({ note, trim, noChildrenList, highlightedTokens }: { note:
|
|||||||
})
|
})
|
||||||
.then(({ $renderedContent, type }) => {
|
.then(({ $renderedContent, type }) => {
|
||||||
if (!contentRef.current) return;
|
if (!contentRef.current) return;
|
||||||
contentRef.current.replaceChildren(...$renderedContent);
|
if ($renderedContent[0].innerHTML) {
|
||||||
|
contentRef.current.replaceChildren(...$renderedContent);
|
||||||
|
} else {
|
||||||
|
contentRef.current.replaceChildren();
|
||||||
|
}
|
||||||
contentRef.current.classList.add(`type-${type}`);
|
contentRef.current.classList.add(`type-${type}`);
|
||||||
highlightSearch(contentRef.current);
|
highlightSearch(contentRef.current);
|
||||||
})
|
})
|
||||||
|
|||||||
@ -46,7 +46,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
||||||
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
||||||
const isInOptions = note.noteId.startsWith("_options");
|
const isInOptionsOrHelp = note?.noteId.startsWith("_options") || note?.noteId.startsWith("_help");
|
||||||
const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && note.getLabelValue("viewType") === "presentation");
|
const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && note.getLabelValue("viewType") === "presentation");
|
||||||
const isElectron = getIsElectron();
|
const isElectron = getIsElectron();
|
||||||
const isMac = getIsMac();
|
const isMac = getIsMac();
|
||||||
@ -69,10 +69,10 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
||||||
disabled={isInOptions || note.type === "search"}
|
disabled={isInOptionsOrHelp || note.type === "search"}
|
||||||
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
||||||
<CommandItem icon="bx bx-export" text={t("note_actions.export_note")}
|
<CommandItem icon="bx bx-export" text={t("note_actions.export_note")}
|
||||||
disabled={isInOptions || note.noteId === "_backendLog"}
|
disabled={isInOptionsOrHelp || note.noteId === "_backendLog"}
|
||||||
command={() => noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", {
|
command={() => noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", {
|
||||||
notePath: noteContext.notePath,
|
notePath: noteContext.notePath,
|
||||||
defaultType: "single"
|
defaultType: "single"
|
||||||
@ -84,14 +84,14 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
<CommandItem command="showNoteSource" icon="bx bx-code" disabled={!hasSource} text={t("note_actions.note_source")} />
|
<CommandItem command="showNoteSource" icon="bx bx-code" disabled={!hasSource} text={t("note_actions.note_source")} />
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem command="forceSaveRevision" icon="bx bx-save" disabled={isInOptions} text={t("note_actions.save_revision")} />
|
<CommandItem command="forceSaveRevision" icon="bx bx-save" disabled={isInOptionsOrHelp} text={t("note_actions.save_revision")} />
|
||||||
<CommandItem icon="bx bx-trash destructive-action-icon" text={t("note_actions.delete_note")} destructive
|
<CommandItem icon="bx bx-trash destructive-action-icon" text={t("note_actions.delete_note")} destructive
|
||||||
disabled={isInOptions}
|
disabled={isInOptionsOrHelp}
|
||||||
command={() => branches.deleteNotes([note.getParentBranches()[0].branchId])}
|
command={() => branches.deleteNotes([note.getParentBranches()[0].branchId])}
|
||||||
/>
|
/>
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem command="showAttachments" icon="bx bx-paperclip" disabled={isInOptions} text={t("note_actions.note_attachments")} />
|
<CommandItem command="showAttachments" icon="bx bx-paperclip" disabled={isInOptionsOrHelp} text={t("note_actions.note_attachments")} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -152,14 +152,14 @@ function restoreRevision(req: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEditedNotesOnDate(req: Request) {
|
function getEditedNotesOnDate(req: Request) {
|
||||||
const noteIds = sql.getColumn<string>(
|
const noteIds = sql.getColumn<string>(/*sql*/`\
|
||||||
`
|
|
||||||
SELECT notes.*
|
SELECT notes.*
|
||||||
FROM notes
|
FROM notes
|
||||||
WHERE noteId IN (
|
WHERE noteId IN (
|
||||||
SELECT noteId FROM notes
|
SELECT noteId FROM notes
|
||||||
WHERE notes.dateCreated LIKE :date
|
WHERE
|
||||||
OR notes.dateModified LIKE :date
|
(notes.dateCreated LIKE :date OR notes.dateModified LIKE :date)
|
||||||
|
AND (noteId NOT LIKE '_%')
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT noteId FROM revisions
|
SELECT noteId FROM revisions
|
||||||
WHERE revisions.dateLastEdited LIKE :date
|
WHERE revisions.dateLastEdited LIKE :date
|
||||||
|
|||||||
@ -681,3 +681,34 @@ describe("#normalizeCustomHandlerPattern", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#slugify", () => {
|
||||||
|
it("should return a slugified string", () => {
|
||||||
|
const testString = "This is a Test String! With unicode & Special #Chars.";
|
||||||
|
const expectedSlug = "this-is-a-test-string-with-unicode-special-chars";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports CJK characters without alteration", () => {
|
||||||
|
const testString = "测试中文字符";
|
||||||
|
const expectedSlug = "测试中文字符";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports Cyrillic characters without alteration", () => {
|
||||||
|
const testString = "Тестирование кириллических символов";
|
||||||
|
const expectedSlug = "тестирование-кириллических-символов";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
// preserves diacritic marks
|
||||||
|
it("preserves diacritic marks", () => {
|
||||||
|
const testString = "Café naïve façade jalapeño";
|
||||||
|
const expectedSlug = "café-naïve-façade-jalapeño";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -497,6 +497,14 @@ export function formatSize(size: number | null | undefined) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function slugify(text: string) {
|
||||||
|
return text
|
||||||
|
.normalize("NFC") // keep composed form, preserves accents
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\p{Letter}\p{Number}]+/gu, "-") // replace non-letter/number with "-"
|
||||||
|
.replace(/(^-|-$)+/g, ""); // trim dashes
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
compareVersions,
|
compareVersions,
|
||||||
crash,
|
crash,
|
||||||
@ -532,6 +540,7 @@ export default {
|
|||||||
safeExtractMessageAndStackFromError,
|
safeExtractMessageAndStackFromError,
|
||||||
sanitizeSqlIdentifier,
|
sanitizeSqlIdentifier,
|
||||||
stripTags,
|
stripTags,
|
||||||
|
slugify,
|
||||||
timeLimit,
|
timeLimit,
|
||||||
toBase64,
|
toBase64,
|
||||||
toMap,
|
toMap,
|
||||||
|
|||||||
@ -175,7 +175,8 @@ function register(router: Router) {
|
|||||||
appPath: isDev ? appPath : `../${appPath}`,
|
appPath: isDev ? appPath : `../${appPath}`,
|
||||||
showLoginInShareTheme,
|
showLoginInShareTheme,
|
||||||
t,
|
t,
|
||||||
isDev
|
isDev,
|
||||||
|
utils
|
||||||
};
|
};
|
||||||
let useDefaultView = true;
|
let useDefaultView = true;
|
||||||
|
|
||||||
|
|||||||
@ -90,9 +90,9 @@ const currentTheme = note.getLabel("shareTheme") === "light" ? "light" : "dark";
|
|||||||
const themeClass = currentTheme === "light" ? " theme-light" : " theme-dark";
|
const themeClass = currentTheme === "light" ? " theme-light" : " theme-dark";
|
||||||
const headingRe = /(<h[1-6]>)(.+?)(<\/h[1-6]>)/g;
|
const headingRe = /(<h[1-6]>)(.+?)(<\/h[1-6]>)/g;
|
||||||
const headingMatches = [...content.matchAll(headingRe)];
|
const headingMatches = [...content.matchAll(headingRe)];
|
||||||
const slugify = (text) => text.toLowerCase().replace(/[^\w]/g, "-");
|
|
||||||
content = content.replaceAll(headingRe, (...match) => {
|
content = content.replaceAll(headingRe, (...match) => {
|
||||||
match[0] = match[0].replace(match[3], `<a id="${slugify(match[2])}" class="toc-anchor" name="${slugify(match[2])}" href="#${slugify(match[2])}">#</a>${match[3]}`);
|
const slug = utils.slugify(utils.stripTags(match[2]));
|
||||||
|
match[0] = match[0].replace(match[3], `<a id="${slug}" class="toc-anchor" name="${slug}" href="#${slug}">#</a>${match[3]}`);
|
||||||
return match[0];
|
return match[0];
|
||||||
});
|
});
|
||||||
%>
|
%>
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
<%
|
<%
|
||||||
const slugify = (text) => text.toLowerCase().replace(/[^\w]/g, "-");
|
const strippedName = utils.stripTags(entry.name);
|
||||||
const slug = slugify(entry.name);
|
const slug = utils.slugify(strippedName);
|
||||||
%>
|
%>
|
||||||
|
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="#<%= slug %>">
|
<a href="#<%= slug %>">
|
||||||
<span><%= entry.name %></span>
|
<span><%= strippedName %></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<% if (entry.children.length) { %>
|
<% if (entry.children.length) { %>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user