mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 07:46:30 +01:00
Merge branch 'main' into feat/llm-tool-improvement
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -120,7 +120,7 @@ jobs:
|
||||
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md
|
||||
fail_on_unmatched_files: true
|
||||
files: upload/*.*
|
||||
discussion_category_name: Announcements
|
||||
discussion_category_name: Releases
|
||||
make_latest: ${{ !contains(github.ref, 'rc') }}
|
||||
prerelease: ${{ contains(github.ref, 'rc') }}
|
||||
token: ${{ secrets.RELEASE_PAT }}
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.53.1",
|
||||
"@stylistic/eslint-plugin": "5.0.0",
|
||||
"@playwright/test": "1.53.2",
|
||||
"@stylistic/eslint-plugin": "5.1.0",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.15.33",
|
||||
"@types/node": "22.16.0",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.29.0",
|
||||
"eslint": "9.30.1",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
"jsdoc": "4.0.4",
|
||||
@@ -49,7 +49,7 @@
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"tslib": "2.8.1",
|
||||
"typedoc": "0.28.5",
|
||||
"typedoc": "0.28.7",
|
||||
"typedoc-plugin-missing-exports": "4.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
@@ -10,14 +10,14 @@
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "9.29.0",
|
||||
"@eslint/js": "9.30.1",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.17",
|
||||
"@fullcalendar/daygrid": "6.1.17",
|
||||
"@fullcalendar/interaction": "6.1.17",
|
||||
"@fullcalendar/list": "6.1.17",
|
||||
"@fullcalendar/multimonth": "6.1.17",
|
||||
"@fullcalendar/timegrid": "6.1.17",
|
||||
"@fullcalendar/core": "6.1.18",
|
||||
"@fullcalendar/daygrid": "6.1.18",
|
||||
"@fullcalendar/interaction": "6.1.18",
|
||||
"@fullcalendar/list": "6.1.18",
|
||||
"@fullcalendar/multimonth": "6.1.18",
|
||||
"@fullcalendar/timegrid": "6.1.18",
|
||||
"@mermaid-js/layout-elk": "0.1.8",
|
||||
"@mind-elixir/node-menu": "1.0.5",
|
||||
"@popperjs/core": "2.11.8",
|
||||
@@ -33,9 +33,9 @@
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "2.2.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.49.6",
|
||||
"globals": "16.2.0",
|
||||
"i18next": "25.2.1",
|
||||
"force-graph": "1.50.1",
|
||||
"globals": "16.3.0",
|
||||
"i18next": "25.3.0",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
@@ -46,14 +46,15 @@
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "15.0.12",
|
||||
"mermaid": "11.7.0",
|
||||
"mind-elixir": "4.6.1",
|
||||
"marked": "16.0.0",
|
||||
"mermaid": "11.8.0",
|
||||
"mind-elixir": "4.6.2",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.26.9",
|
||||
"split.js": "1.6.5",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
"vanilla-js-wheel-zoom": "9.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -63,6 +64,7 @@
|
||||
"@types/leaflet": "1.9.19",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/mark.js": "8.11.12",
|
||||
"@types/tabulator-tables": "6.2.6",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"happy-dom": "18.0.1",
|
||||
"script-loader": "0.7.2",
|
||||
|
||||
@@ -93,11 +93,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
|
||||
if (fun) {
|
||||
return this.callMethod(fun, data);
|
||||
} else {
|
||||
if (!this.parent) {
|
||||
throw new Error(`Component "${this.componentId}" does not have a parent attached to propagate a command.`);
|
||||
}
|
||||
|
||||
} else if (this.parent) {
|
||||
return this.parent.triggerCommand(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,14 +315,38 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
}
|
||||
|
||||
hasNoteList() {
|
||||
return (
|
||||
this.note &&
|
||||
["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "") &&
|
||||
(this.note.hasChildren() || this.note.getLabelValue("viewType") === "calendar") &&
|
||||
["book", "text", "code"].includes(this.note.type) &&
|
||||
this.note.mime !== "text/x-sqlite;schema=trilium" &&
|
||||
!this.note.isLabelTruthy("hideChildrenOverview")
|
||||
);
|
||||
const note = this.note;
|
||||
|
||||
if (!note) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some book types must always display a note list, even if no children.
|
||||
if (["calendar", "table"].includes(note.getLabelValue("viewType") ?? "")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!note.hasChildren()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!["book", "text", "code"].includes(note.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (note.mime === "text/x-sqlite;schema=trilium") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (note.isLabelTruthy("hideChildrenOverview")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getTextEditor(callback?: GetTextEditorCallback) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import keyboardActionService from "../services/keyboard_actions.js";
|
||||
import note_tooltip from "../services/note_tooltip.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
interface ContextMenuOptions<T> {
|
||||
export interface ContextMenuOptions<T> {
|
||||
x: number;
|
||||
y: number;
|
||||
orientation?: "left";
|
||||
@@ -28,6 +28,7 @@ export interface MenuCommandItem<T> {
|
||||
items?: MenuItem<T>[] | null;
|
||||
shortcut?: string;
|
||||
spellingSuggestion?: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem;
|
||||
@@ -146,10 +147,13 @@ class ContextMenu {
|
||||
} else {
|
||||
const $icon = $("<span>");
|
||||
|
||||
if ("uiIcon" in item && item.uiIcon) {
|
||||
$icon.addClass(item.uiIcon);
|
||||
} else {
|
||||
$icon.append(" ");
|
||||
if ("uiIcon" in item || "checked" in item) {
|
||||
const icon = (item.checked ? "bx bx-check" : item.uiIcon);
|
||||
if (icon) {
|
||||
$icon.addClass(icon);
|
||||
} else {
|
||||
$icon.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
const $link = $("<span>")
|
||||
|
||||
@@ -3,15 +3,16 @@ import froca from "./froca.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { AttributeRow } from "./load_results.js";
|
||||
|
||||
async function addLabel(noteId: string, name: string, value: string = "") {
|
||||
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
|
||||
await server.put(`notes/${noteId}/attribute`, {
|
||||
type: "label",
|
||||
name: name,
|
||||
value: value
|
||||
value: value,
|
||||
isInheritable
|
||||
});
|
||||
}
|
||||
|
||||
async function setLabel(noteId: string, name: string, value: string = "") {
|
||||
export async function setLabel(noteId: string, name: string, value: string = "") {
|
||||
await server.put(`notes/${noteId}/set-attribute`, {
|
||||
type: "label",
|
||||
name: name,
|
||||
@@ -49,7 +50,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
|
||||
* @param name the name of the attribute to set.
|
||||
* @param value the value of the attribute to set.
|
||||
*/
|
||||
async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
if (value) {
|
||||
// Create or update the attribute.
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
|
||||
|
||||
@@ -118,8 +118,17 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HT
|
||||
async function renderCode(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
|
||||
const blob = await note.getBlob();
|
||||
|
||||
let content = blob?.content || "";
|
||||
if (note.mime === "application/json") {
|
||||
try {
|
||||
content = JSON.stringify(JSON.parse(content), null, 4);
|
||||
} catch (e) {
|
||||
// Ignore JSON parsing errors.
|
||||
}
|
||||
}
|
||||
|
||||
const $codeBlock = $("<code>");
|
||||
$codeBlock.text(blob?.content || "");
|
||||
$codeBlock.text(content);
|
||||
$renderedContent.append($("<pre>").append($codeBlock));
|
||||
await applySingleBlockSyntaxHighlight($codeBlock, normalizeMimeTypeForCKEditor(note.mime));
|
||||
}
|
||||
@@ -301,7 +310,7 @@ function getRenderingType(entity: FNote | FAttachment) {
|
||||
|
||||
if (type === "file" && mime === "application/pdf") {
|
||||
type = "pdf";
|
||||
} else if (type === "file" && mime && CODE_MIME_TYPES.has(mime)) {
|
||||
} else if ((type === "file" || type === "viewConfig") && mime && CODE_MIME_TYPES.has(mime)) {
|
||||
type = "code";
|
||||
} else if (type === "file" && mime && mime.startsWith("audio/")) {
|
||||
type = "audio";
|
||||
|
||||
@@ -384,7 +384,7 @@ function linkContextMenu(e: PointerEvent) {
|
||||
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
|
||||
}
|
||||
|
||||
async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
|
||||
export async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
|
||||
const $link = $el[0].tagName === "A" ? $el : $el.find("a");
|
||||
|
||||
href = href || $link.attr("href");
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import CalendarView from "../widgets/view_widgets/calendar_view.js";
|
||||
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
|
||||
import TableView from "../widgets/view_widgets/table_view/index.js";
|
||||
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
|
||||
import type ViewMode from "../widgets/view_widgets/view_mode.js";
|
||||
|
||||
export type ViewTypeOptions = "list" | "grid" | "calendar";
|
||||
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table";
|
||||
|
||||
export default class NoteListRenderer {
|
||||
|
||||
private viewType: ViewTypeOptions;
|
||||
public viewMode: ViewMode | null;
|
||||
public viewMode: ViewMode<any> | null;
|
||||
|
||||
constructor($parent: JQuery<HTMLElement>, parentNote: FNote, noteIds: string[], showNotePath: boolean = false) {
|
||||
this.viewType = this.#getViewType(parentNote);
|
||||
const args: ViewModeArgs = {
|
||||
$parent,
|
||||
parentNote,
|
||||
noteIds,
|
||||
showNotePath
|
||||
};
|
||||
constructor(args: ViewModeArgs) {
|
||||
this.viewType = this.#getViewType(args.parentNote);
|
||||
|
||||
if (this.viewType === "list" || this.viewType === "grid") {
|
||||
this.viewMode = new ListOrGridView(this.viewType, args);
|
||||
} else if (this.viewType === "calendar") {
|
||||
this.viewMode = new CalendarView(args);
|
||||
} else {
|
||||
this.viewMode = null;
|
||||
switch (this.viewType) {
|
||||
case "list":
|
||||
case "grid":
|
||||
this.viewMode = new ListOrGridView(this.viewType, args);
|
||||
break;
|
||||
case "calendar":
|
||||
this.viewMode = new CalendarView(args);
|
||||
break;
|
||||
case "table":
|
||||
this.viewMode = new TableView(args);
|
||||
break;
|
||||
default:
|
||||
this.viewMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
#getViewType(parentNote: FNote): ViewTypeOptions {
|
||||
const viewType = parentNote.getLabelValue("viewType");
|
||||
|
||||
if (!["list", "grid", "calendar"].includes(viewType || "")) {
|
||||
if (!["list", "grid", "calendar", "table"].includes(viewType || "")) {
|
||||
// when not explicitly set, decide based on the note type
|
||||
return parentNote.type === "search" ? "list" : "grid";
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ let dismissTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
function setupGlobalTooltip() {
|
||||
$(document).on("mouseenter", "a", mouseEnterHandler);
|
||||
$(document).on("mouseenter", "[data-href]", mouseEnterHandler);
|
||||
|
||||
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
|
||||
$(document).on("click", (e) => {
|
||||
|
||||
@@ -8,18 +8,35 @@ const SEPARATOR = { title: "----" };
|
||||
|
||||
async function getNoteTypeItems(command?: TreeCommandNames) {
|
||||
const items: MenuItem<TreeCommandNames>[] = [
|
||||
// The suggested note type ordering method: insert the item into the corresponding group,
|
||||
// then ensure the items within the group are ordered alphabetically.
|
||||
// Please keep the order synced with the listing found also in aps/client/src/widgets/note_types.ts.
|
||||
|
||||
// The default note type (always the first item)
|
||||
{ title: t("note_types.text"), command, type: "text", uiIcon: "bx bx-note" },
|
||||
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
|
||||
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: t("note_types.relation-map"), command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.note-map"), command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.render-note"), command, type: "render", uiIcon: "bx bx-extension" },
|
||||
|
||||
// Text notes group
|
||||
{ title: t("note_types.book"), command, type: "book", uiIcon: "bx bx-book" },
|
||||
{ title: t("note_types.mermaid-diagram"), command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
|
||||
// Graphic notes
|
||||
{ title: t("note_types.canvas"), command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: t("note_types.web-view"), command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" },
|
||||
{ title: t("note_types.mermaid-diagram"), command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
|
||||
// Map notes
|
||||
{ title: t("note_types.geo-map"), command, type: "geoMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" },
|
||||
{ title: t("note_types.note-map"), command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.relation-map"), command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
|
||||
|
||||
// Misc note types
|
||||
{ title: t("note_types.render-note"), command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: t("note_types.web-view"), command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
|
||||
// Code notes
|
||||
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
|
||||
|
||||
// Templates
|
||||
...await getBuiltInTemplates(command),
|
||||
...await getUserTemplates(command)
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
|
||||
export type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
|
||||
type Multiplicity = "single" | "multi";
|
||||
|
||||
export interface DefinitionObject {
|
||||
|
||||
@@ -382,6 +382,10 @@ div.tn-tool-dialog {
|
||||
|
||||
/* DELETE NOTE PREVIEW DIALOG */
|
||||
|
||||
.delete-notes-dialog .modal-dialog {
|
||||
--bs-modal-width: fit-content;
|
||||
}
|
||||
|
||||
.delete-notes-list .note-path {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,12 @@ div.promoted-attributes-container {
|
||||
.image-properties > div:first-child > span > strong {
|
||||
opacity: 0.65;
|
||||
font-weight: 500;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.note-info-widget-table td,
|
||||
.file-properties-widget .file-table td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.file-properties-widget {
|
||||
|
||||
@@ -71,7 +71,7 @@ body.background-effects.platform-win32.layout-vertical #vertical-main-container
|
||||
/* #endregion */
|
||||
|
||||
/* Matches when the left pane is collapsed */
|
||||
:has(.layout-vertical #left-pane.hidden-int) {
|
||||
#horizontal-main-container:has(#left-pane.hidden-int) {
|
||||
--center-pane-border-radius: 0;
|
||||
--tab-first-item-horiz-offset: 5px;
|
||||
}
|
||||
|
||||
@@ -760,7 +760,8 @@
|
||||
"expand": "Expand",
|
||||
"book_properties": "Book Properties",
|
||||
"invalid_view_type": "Invalid view type '{{type}}'",
|
||||
"calendar": "Calendar"
|
||||
"calendar": "Calendar",
|
||||
"table": "Table"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "No edited notes on this day yet...",
|
||||
@@ -1933,5 +1934,9 @@
|
||||
"title": "Features",
|
||||
"emoji_completion_enabled": "Enable Emoji auto-completion",
|
||||
"note_completion_enabled": "Enable note auto-completion"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "New row",
|
||||
"new-column": "New column"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
list: null,
|
||||
grid: null,
|
||||
calendar: "xWbu3jpNWapp"
|
||||
calendar: "xWbu3jpNWapp",
|
||||
table: "2FvYrpmOXm29"
|
||||
};
|
||||
|
||||
export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import NoteListRenderer from "../services/note_list_renderer.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { CommandListener, CommandListenerData, EventData } from "../components/app_context.js";
|
||||
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData } from "../components/app_context.js";
|
||||
import type ViewMode from "./view_widgets/view_mode.js";
|
||||
import AttributeDetailWidget from "./attribute_widgets/attribute_detail.js";
|
||||
import { Attribute } from "../services/attribute_parser.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-list-widget">
|
||||
@@ -36,7 +38,15 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
private isIntersecting?: boolean;
|
||||
private noteIdRefreshed?: string;
|
||||
private shownNoteId?: string | null;
|
||||
private viewMode?: ViewMode | null;
|
||||
private viewMode?: ViewMode<any> | null;
|
||||
private attributeDetailWidget: AttributeDetailWidget;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attributeDetailWidget = new AttributeDetailWidget()
|
||||
.contentSized()
|
||||
.setParent(this);
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.noteContext?.hasNoteList();
|
||||
@@ -46,6 +56,7 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
this.$widget = $(TPL);
|
||||
this.contentSized();
|
||||
this.$content = this.$widget.find(".note-list-widget-content");
|
||||
this.$widget.append(this.attributeDetailWidget.render());
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
@@ -64,6 +75,23 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
setTimeout(() => observer.observe(this.$widget[0]), 10);
|
||||
}
|
||||
|
||||
addNoteListItemEvent() {
|
||||
const attr: Attribute = {
|
||||
type: "label",
|
||||
name: "label:myLabel",
|
||||
value: "promoted,single,text"
|
||||
};
|
||||
|
||||
this.attributeDetailWidget!.showAttributeDetail({
|
||||
attribute: attr,
|
||||
allAttributes: [ attr ],
|
||||
isOwned: true,
|
||||
x: 100,
|
||||
y: 200,
|
||||
focus: "name"
|
||||
});
|
||||
}
|
||||
|
||||
checkRenderStatus() {
|
||||
// console.log("this.isIntersecting", this.isIntersecting);
|
||||
// console.log(`${this.noteIdRefreshed} === ${this.noteId}`, this.noteIdRefreshed === this.noteId);
|
||||
@@ -76,7 +104,12 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async renderNoteList(note: FNote) {
|
||||
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds());
|
||||
const noteListRenderer = new NoteListRenderer({
|
||||
$parent: this.$content,
|
||||
parentNote: note,
|
||||
parentNotePath: this.notePath,
|
||||
noteIds: note.getChildNoteIds()
|
||||
});
|
||||
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
|
||||
await noteListRenderer.renderList();
|
||||
this.viewMode = noteListRenderer.viewMode;
|
||||
@@ -134,4 +167,13 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
triggerCommand<K extends CommandNames>(name: K, data?: CommandMappings[K]): Promise<unknown> | undefined | null {
|
||||
// Pass the commands to the view mode, which is not actually attached to the hierarchy.
|
||||
if (this.viewMode?.triggerCommand(name, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return super.triggerCommand(name, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -324,7 +324,13 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
const mapRootNoteId = this.getMapRootNoteId();
|
||||
const data = await this.loadNotesAndRelations(mapRootNoteId);
|
||||
|
||||
const labelValues = (name: string) => this.note?.getLabels(name).map(l => l.value) ?? [];
|
||||
|
||||
const excludeRelations = labelValues("mapExcludeRelation");
|
||||
const includeRelations = labelValues("mapIncludeRelation");
|
||||
|
||||
const data = await this.loadNotesAndRelations(mapRootNoteId, excludeRelations, includeRelations);
|
||||
|
||||
const nodeLinkRatio = data.nodes.length / data.links.length;
|
||||
const magnifiedRatio = Math.pow(nodeLinkRatio, 1.5);
|
||||
@@ -473,8 +479,10 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
async loadNotesAndRelations(mapRootNoteId: string): Promise<NotesAndRelationsData> {
|
||||
const resp = await server.post<PostNotesMapResponse>(`note-map/${mapRootNoteId}/${this.mapType}`);
|
||||
async loadNotesAndRelations(mapRootNoteId: string, excludeRelations: string[], includeRelations: string[]): Promise<NotesAndRelationsData> {
|
||||
const resp = await server.post<PostNotesMapResponse>(`note-map/${mapRootNoteId}/${this.mapType}`, {
|
||||
excludeRelations, includeRelations
|
||||
});
|
||||
|
||||
this.calculateNodeSizes(resp);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ interface NoteTypeMapping {
|
||||
const NOTE_TYPES: NoteTypeMapping[] = [
|
||||
// The suggested note type ordering method: insert the item into the corresponding group,
|
||||
// then ensure the items within the group are ordered alphabetically.
|
||||
// Please keep the order synced with the listing found also in apps/client/src/services/note_types.ts.
|
||||
|
||||
// The default note type (always the first item)
|
||||
{ type: "text", mime: "text/html", title: t("note_types.text"), selectable: true },
|
||||
|
||||
@@ -24,6 +24,7 @@ const TPL = /*html*/`
|
||||
<option value="grid">${t("book_properties.grid")}</option>
|
||||
<option value="list">${t("book_properties.list")}</option>
|
||||
<option value="calendar">${t("book_properties.calendar")}</option>
|
||||
<option value="table">${t("book_properties.table")}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +68,6 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
||||
getTitle() {
|
||||
return {
|
||||
show: this.isEnabled(),
|
||||
activate: true,
|
||||
title: t("book_properties.book_properties"),
|
||||
icon: "bx bx-book"
|
||||
};
|
||||
@@ -126,7 +126,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!["list", "grid", "calendar"].includes(type)) {
|
||||
if (!["list", "grid", "calendar", "table"].includes(type)) {
|
||||
throw new Error(t("book_properties.invalid_view_type", { type }));
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
// the order of attributes is important as well
|
||||
ownedAttributes.sort((a, b) => a.position - b.position);
|
||||
|
||||
if (promotedDefAttrs.length === 0) {
|
||||
if (promotedDefAttrs.length === 0 || note.getLabelValue("viewType") === "table") {
|
||||
this.toggleInt(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,13 @@ export default class SearchResultWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds(), true);
|
||||
// this.$content, note, note.getChildNoteIds(), true
|
||||
const noteListRenderer = new NoteListRenderer({
|
||||
$parent: this.$content,
|
||||
parentNote: note,
|
||||
noteIds: note.getChildNoteIds(),
|
||||
showNotePath: true
|
||||
});
|
||||
await noteListRenderer.renderList();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,21 @@ export default class BookTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
this.$helpNoChildren.toggle(!this.note?.hasChildren() && this.note?.getAttributeValue("label", "viewType") !== "calendar");
|
||||
this.$helpNoChildren.toggle(this.shouldDisplayNoChildrenWarning());
|
||||
}
|
||||
|
||||
shouldDisplayNoChildrenWarning() {
|
||||
if (this.note?.hasChildren()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this.note?.getAttributeValue("label", "viewType")) {
|
||||
case "calendar":
|
||||
case "table":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
|
||||
@@ -109,24 +109,22 @@ const CALENDAR_VIEWS = [
|
||||
"listMonth"
|
||||
]
|
||||
|
||||
export default class CalendarView extends ViewMode {
|
||||
export default class CalendarView extends ViewMode<{}> {
|
||||
|
||||
private $root: JQuery<HTMLElement>;
|
||||
private $calendarContainer: JQuery<HTMLElement>;
|
||||
private noteIds: string[];
|
||||
private parentNote: FNote;
|
||||
private calendar?: Calendar;
|
||||
private isCalendarRoot: boolean;
|
||||
private lastView?: string;
|
||||
private debouncedSaveView?: DebouncedFunction<() => void>;
|
||||
|
||||
constructor(args: ViewModeArgs) {
|
||||
super(args);
|
||||
super(args, "calendar");
|
||||
|
||||
this.$root = $(TPL);
|
||||
this.$calendarContainer = this.$root.find(".calendar-container");
|
||||
this.noteIds = args.noteIds;
|
||||
this.parentNote = args.parentNote;
|
||||
this.isCalendarRoot = false;
|
||||
args.$parent.append(this.$root);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import treeService from "../../services/tree.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import ViewMode, { type ViewModeArgs } from "./view_mode.js";
|
||||
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-list">
|
||||
@@ -157,26 +158,22 @@ const TPL = /*html*/`
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
class ListOrGridView extends ViewMode {
|
||||
class ListOrGridView extends ViewMode<{}> {
|
||||
private $noteList: JQuery<HTMLElement>;
|
||||
|
||||
private parentNote: FNote;
|
||||
private noteIds: string[];
|
||||
private page?: number;
|
||||
private pageSize?: number;
|
||||
private viewType?: string | null;
|
||||
private showNotePath?: boolean;
|
||||
private highlightRegex?: RegExp | null;
|
||||
|
||||
/*
|
||||
* We're using noteIds so that it's not necessary to load all notes at once when paging
|
||||
*/
|
||||
constructor(viewType: string, args: ViewModeArgs) {
|
||||
super(args);
|
||||
constructor(viewType: ViewTypeOptions, args: ViewModeArgs) {
|
||||
super(args, viewType);
|
||||
this.$noteList = $(TPL);
|
||||
this.viewType = viewType;
|
||||
|
||||
this.parentNote = args.parentNote;
|
||||
const includedNoteIds = this.getIncludedNoteIds();
|
||||
|
||||
this.noteIds = args.noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden");
|
||||
|
||||
110
apps/client/src/widgets/view_widgets/table_view/columns.ts
Normal file
110
apps/client/src/widgets/view_widgets/table_view/columns.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { RelationEditor } from "./relation_editor.js";
|
||||
import { NoteFormatter, NoteTitleFormatter } from "./formatters.js";
|
||||
import { applyHeaderMenu } from "./header-menu.js";
|
||||
import type { ColumnDefinition } from "tabulator-tables";
|
||||
import { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
||||
|
||||
type ColumnType = LabelType | "relation";
|
||||
|
||||
export interface PromotedAttributeInformation {
|
||||
name: string;
|
||||
title?: string;
|
||||
type?: ColumnType;
|
||||
}
|
||||
|
||||
const labelTypeMappings: Record<ColumnType, Partial<ColumnDefinition>> = {
|
||||
text: {
|
||||
editor: "input"
|
||||
},
|
||||
boolean: {
|
||||
formatter: "tickCross",
|
||||
editor: "tickCross"
|
||||
},
|
||||
date: {
|
||||
editor: "date",
|
||||
},
|
||||
datetime: {
|
||||
editor: "datetime"
|
||||
},
|
||||
number: {
|
||||
editor: "number"
|
||||
},
|
||||
time: {
|
||||
editor: "input"
|
||||
},
|
||||
url: {
|
||||
formatter: "link",
|
||||
editor: "input"
|
||||
},
|
||||
relation: {
|
||||
editor: RelationEditor,
|
||||
formatter: NoteFormatter
|
||||
}
|
||||
};
|
||||
|
||||
export function buildColumnDefinitions(info: PromotedAttributeInformation[], existingColumnData?: ColumnDefinition[]) {
|
||||
const columnDefs: ColumnDefinition[] = [
|
||||
{
|
||||
title: "#",
|
||||
formatter: "rownum",
|
||||
headerSort: false,
|
||||
hozAlign: "center",
|
||||
resizable: false,
|
||||
frozen: true
|
||||
},
|
||||
{
|
||||
field: "noteId",
|
||||
title: "Note ID",
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
field: "title",
|
||||
title: "Title",
|
||||
editor: "input",
|
||||
formatter: NoteTitleFormatter,
|
||||
width: 400
|
||||
}
|
||||
];
|
||||
|
||||
const seenFields = new Set<string>();
|
||||
for (const { name, title, type } of info) {
|
||||
const prefix = (type === "relation" ? "relations" : "labels");
|
||||
const field = `${prefix}.${name}`;
|
||||
|
||||
if (seenFields.has(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
columnDefs.push({
|
||||
field,
|
||||
title: title ?? name,
|
||||
editor: "input",
|
||||
...labelTypeMappings[type ?? "text"],
|
||||
});
|
||||
seenFields.add(field);
|
||||
}
|
||||
|
||||
applyHeaderMenu(columnDefs);
|
||||
if (existingColumnData) {
|
||||
restoreExistingData(columnDefs, existingColumnData);
|
||||
}
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[]) {
|
||||
const byField = new Map<string, ColumnDefinition>;
|
||||
for (const def of oldDefs) {
|
||||
byField.set(def.field ?? "", def);
|
||||
}
|
||||
|
||||
for (const newDef of newDefs) {
|
||||
const oldDef = byField.get(newDef.field ?? "");
|
||||
if (!oldDef) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newDef.width = oldDef.width;
|
||||
newDef.visible = oldDef.visible;
|
||||
}
|
||||
}
|
||||
25
apps/client/src/widgets/view_widgets/table_view/dragging.ts
Normal file
25
apps/client/src/widgets/view_widgets/table_view/dragging.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Tabulator } from "tabulator-tables";
|
||||
import type FNote from "../../../entities/fnote.js";
|
||||
import branches from "../../../services/branches.js";
|
||||
|
||||
export function canReorderRows(parentNote: FNote) {
|
||||
return !parentNote.hasLabel("sorted")
|
||||
&& parentNote.type !== "search";
|
||||
}
|
||||
|
||||
export function configureReorderingRows(tabulator: Tabulator) {
|
||||
tabulator.on("rowMoved", (row) => {
|
||||
const branchIdsToMove = [ row.getData().branchId ];
|
||||
|
||||
const prevRow = row.getPrevRow();
|
||||
if (prevRow) {
|
||||
branches.moveAfterBranch(branchIdsToMove, prevRow.getData().branchId);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextRow = row.getNextRow();
|
||||
if (nextRow) {
|
||||
branches.moveBeforeBranch(branchIdsToMove, nextRow.getData().branchId);
|
||||
}
|
||||
});
|
||||
}
|
||||
22
apps/client/src/widgets/view_widgets/table_view/footer.ts
Normal file
22
apps/client/src/widgets/view_widgets/table_view/footer.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import FNote from "../../../entities/fnote.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
|
||||
function shouldDisplayFooter(parentNote: FNote) {
|
||||
return (parentNote.type !== "search");
|
||||
}
|
||||
|
||||
export default function buildFooter(parentNote: FNote) {
|
||||
if (!shouldDisplayFooter(parentNote)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return /*html*/`\
|
||||
<button class="btn btn-sm" style="padding: 0px 10px 0px 10px;" data-trigger-command="addNewRow">
|
||||
<span class="bx bx-plus"></span> ${t("table_view.new-row")}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm" style="padding: 0px 10px 0px 10px;" data-trigger-command="addNoteListItem">
|
||||
<span class="bx bx-columns"></span> ${t("table_view.new-column")}
|
||||
</button>
|
||||
`.trimStart();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { CellComponent } from "tabulator-tables";
|
||||
import { loadReferenceLinkTitle } from "../../../services/link.js";
|
||||
|
||||
/**
|
||||
* Custom formatter to represent a note, with the icon and note title being rendered.
|
||||
*
|
||||
* The value of the cell must be the note ID.
|
||||
*/
|
||||
export function NoteFormatter(cell: CellComponent, _formatterParams, onRendered) {
|
||||
let noteId = cell.getValue();
|
||||
if (!noteId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
onRendered(async () => {
|
||||
const { $noteRef, href } = buildNoteLink(noteId);
|
||||
await loadReferenceLinkTitle($noteRef, href);
|
||||
cell.getElement().appendChild($noteRef[0]);
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom formatter for the note title that is quite similar to {@link NoteFormatter}, but where the title and icons are read from separate fields.
|
||||
*/
|
||||
export function NoteTitleFormatter(cell: CellComponent) {
|
||||
const { noteId, iconClass } = cell.getRow().getData();
|
||||
if (!noteId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { $noteRef } = buildNoteLink(noteId);
|
||||
$noteRef.text(cell.getValue());
|
||||
$noteRef.prepend($("<span>").addClass(iconClass));
|
||||
|
||||
return $noteRef[0].outerHTML;
|
||||
}
|
||||
|
||||
function buildNoteLink(noteId: string) {
|
||||
const $noteRef = $("<span>");
|
||||
const href = `#root/${noteId}`;
|
||||
$noteRef.addClass("reference-link");
|
||||
$noteRef.attr("data-href", href);
|
||||
return { $noteRef, href };
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { ColumnComponent, ColumnDefinition, MenuObject, Tabulator } from "tabulator-tables";
|
||||
|
||||
export function applyHeaderMenu(columns: ColumnDefinition[]) {
|
||||
for (let column of columns) {
|
||||
if (column.headerSort !== false) {
|
||||
column.headerMenu = headerMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function headerMenu(this: Tabulator) {
|
||||
const menu: MenuObject<ColumnComponent>[] = [];
|
||||
const columns = this.getColumns();
|
||||
|
||||
for (let column of columns) {
|
||||
//create checkbox element using font awesome icons
|
||||
let icon = document.createElement("i");
|
||||
icon.classList.add("bx");
|
||||
icon.classList.add(column.isVisible() ? "bx-check" : "bx-empty");
|
||||
|
||||
//build label
|
||||
let label = document.createElement("span");
|
||||
let title = document.createElement("span");
|
||||
|
||||
title.textContent = " " + column.getDefinition().title;
|
||||
|
||||
label.appendChild(icon);
|
||||
label.appendChild(title);
|
||||
|
||||
//create menu item
|
||||
menu.push({
|
||||
label: label,
|
||||
action: function (e) {
|
||||
//prevent menu closing
|
||||
e.stopPropagation();
|
||||
|
||||
//toggle current column visibility
|
||||
column.toggle();
|
||||
|
||||
//change menu item icon
|
||||
if (column.isVisible()) {
|
||||
icon.classList.remove("bx-empty");
|
||||
icon.classList.add("bx-check");
|
||||
} else {
|
||||
icon.classList.remove("bx-check");
|
||||
icon.classList.add("bx-empty");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
};
|
||||
265
apps/client/src/widgets/view_widgets/table_view/index.ts
Normal file
265
apps/client/src/widgets/view_widgets/table_view/index.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import froca from "../../../services/froca.js";
|
||||
import ViewMode, { type ViewModeArgs } from "../view_mode.js";
|
||||
import attributes, { setAttribute, setLabel } from "../../../services/attributes.js";
|
||||
import server from "../../../services/server.js";
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import type { CommandListenerData, EventData } from "../../../components/app_context.js";
|
||||
import type { Attribute } from "../../../services/attribute_parser.js";
|
||||
import note_create from "../../../services/note_create.js";
|
||||
import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MenuModule, MoveRowsModule, ColumnDefinition} from 'tabulator-tables';
|
||||
import "tabulator-tables/dist/css/tabulator_bootstrap5.min.css";
|
||||
import { canReorderRows, configureReorderingRows } from "./dragging.js";
|
||||
import buildFooter from "./footer.js";
|
||||
import getPromotedAttributeInformation, { buildRowDefinitions } from "./rows.js";
|
||||
import { buildColumnDefinitions } from "./columns.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="table-view">
|
||||
<style>
|
||||
.table-view {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
padding: 0 5px 0 10px;
|
||||
}
|
||||
|
||||
.table-view-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.search-result-widget-content .table-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.tabulator-cell .autocomplete {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header {
|
||||
border-top: unset;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left,
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-footer {
|
||||
background-color: unset;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-footer .tabulator-footer-contents {
|
||||
justify-content: left;
|
||||
gap: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="table-view-container"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export interface StateInfo {
|
||||
tableData?: {
|
||||
columns?: ColumnDefinition[];
|
||||
};
|
||||
}
|
||||
|
||||
export default class TableView extends ViewMode<StateInfo> {
|
||||
|
||||
private $root: JQuery<HTMLElement>;
|
||||
private $container: JQuery<HTMLElement>;
|
||||
private args: ViewModeArgs;
|
||||
private spacedUpdate: SpacedUpdate;
|
||||
private api?: Tabulator;
|
||||
private newAttribute?: Attribute;
|
||||
private persistentData: StateInfo["tableData"];
|
||||
/** If set to a note ID, whenever the rows will be updated, the title of the note will be automatically focused for editing. */
|
||||
private noteIdToEdit?: string;
|
||||
|
||||
constructor(args: ViewModeArgs) {
|
||||
super(args, "table");
|
||||
|
||||
this.$root = $(TPL);
|
||||
this.$container = this.$root.find(".table-view-container");
|
||||
this.args = args;
|
||||
this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000);
|
||||
this.persistentData = {};
|
||||
args.$parent.append(this.$root);
|
||||
}
|
||||
|
||||
get isFullHeight(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async renderList() {
|
||||
this.$container.empty();
|
||||
this.renderTable(this.$container[0]);
|
||||
return this.$root;
|
||||
}
|
||||
|
||||
private async renderTable(el: HTMLElement) {
|
||||
const modules = [SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, MenuModule];
|
||||
for (const module of modules) {
|
||||
Tabulator.registerModule(module);
|
||||
}
|
||||
|
||||
this.initialize(el);
|
||||
}
|
||||
|
||||
private async initialize(el: HTMLElement) {
|
||||
const notes = await froca.getNotes(this.args.noteIds);
|
||||
const info = getPromotedAttributeInformation(this.parentNote);
|
||||
|
||||
const viewStorage = await this.viewStorage.restore();
|
||||
this.persistentData = viewStorage?.tableData || {};
|
||||
|
||||
const columnDefs = buildColumnDefinitions(info);
|
||||
const movableRows = canReorderRows(this.parentNote);
|
||||
|
||||
this.api = new Tabulator(el, {
|
||||
layout: "fitDataFill",
|
||||
index: "noteId",
|
||||
columns: columnDefs,
|
||||
data: await buildRowDefinitions(this.parentNote, notes, info),
|
||||
persistence: true,
|
||||
movableColumns: true,
|
||||
movableRows,
|
||||
footerElement: buildFooter(this.parentNote),
|
||||
persistenceWriterFunc: (_id, type: string, data: object) => {
|
||||
(this.persistentData as Record<string, {}>)[type] = data;
|
||||
this.spacedUpdate.scheduleUpdate();
|
||||
},
|
||||
persistenceReaderFunc: (_id, type: string) => this.persistentData?.[type],
|
||||
});
|
||||
configureReorderingRows(this.api);
|
||||
this.setupEditing();
|
||||
}
|
||||
|
||||
private onSave() {
|
||||
this.viewStorage.store({
|
||||
tableData: this.persistentData,
|
||||
});
|
||||
}
|
||||
|
||||
private setupEditing() {
|
||||
this.api!.on("cellEdited", async (cell) => {
|
||||
const noteId = cell.getRow().getData().noteId;
|
||||
const field = cell.getField();
|
||||
const newValue = cell.getValue();
|
||||
|
||||
if (field === "title") {
|
||||
server.put(`notes/${noteId}/title`, { title: newValue });
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.includes(".")) {
|
||||
const [ type, name ] = field.split(".", 2);
|
||||
if (type === "labels") {
|
||||
setLabel(noteId, name, newValue);
|
||||
} else if (type === "relations") {
|
||||
const note = await froca.getNote(noteId);
|
||||
if (note) {
|
||||
setAttribute(note, "relation", name, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async reloadAttributesCommand() {
|
||||
console.log("Reload attributes");
|
||||
}
|
||||
|
||||
async updateAttributeListCommand({ attributes }: CommandListenerData<"updateAttributeList">) {
|
||||
this.newAttribute = attributes[0];
|
||||
}
|
||||
|
||||
async saveAttributesCommand() {
|
||||
if (!this.newAttribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, value } = this.newAttribute;
|
||||
attributes.addLabel(this.parentNote.noteId, name, value, true);
|
||||
console.log("Save attributes", this.newAttribute);
|
||||
}
|
||||
|
||||
addNewRowCommand() {
|
||||
const parentNotePath = this.args.parentNotePath;
|
||||
if (parentNotePath) {
|
||||
note_create.createNote(parentNotePath, {
|
||||
activate: false
|
||||
}).then(({ note }) => {
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
this.noteIdToEdit = note.noteId;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">): boolean | void {
|
||||
if (!this.api) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh if promoted attributes get changed.
|
||||
if (loadResults.getAttributeRows().find(attr =>
|
||||
attr.type === "label" &&
|
||||
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
||||
attributes.isAffecting(attr, this.parentNote))) {
|
||||
this.#manageColumnUpdate();
|
||||
}
|
||||
|
||||
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId)) {
|
||||
this.#manageRowsUpdate();
|
||||
}
|
||||
|
||||
if (loadResults.getAttributeRows().some(attr => this.args.noteIds.includes(attr.noteId!))) {
|
||||
this.#manageRowsUpdate();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#manageColumnUpdate() {
|
||||
if (!this.api) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = getPromotedAttributeInformation(this.parentNote);
|
||||
const columnDefs = buildColumnDefinitions(info, this.persistentData?.columns);
|
||||
this.api.setColumns(columnDefs);
|
||||
}
|
||||
|
||||
async #manageRowsUpdate() {
|
||||
if (!this.api) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notes = await froca.getNotes(this.args.noteIds);
|
||||
const info = getPromotedAttributeInformation(this.parentNote);
|
||||
this.api.replaceData(await buildRowDefinitions(this.parentNote, notes, info));
|
||||
|
||||
if (this.noteIdToEdit) {
|
||||
const row = this.api?.getRows().find(r => r.getData().noteId === this.noteIdToEdit);
|
||||
if (row) {
|
||||
row.getCell("title").edit();
|
||||
}
|
||||
this.noteIdToEdit = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { CellComponent } from "tabulator-tables";
|
||||
import note_autocomplete from "../../../services/note_autocomplete";
|
||||
import froca from "../../../services/froca";
|
||||
|
||||
export function RelationEditor(cell: CellComponent, onRendered, success, cancel, editorParams){
|
||||
//cell - the cell component for the editable cell
|
||||
//onRendered - function to call when the editor has been rendered
|
||||
//success - function to call to pass thesuccessfully updated value to Tabulator
|
||||
//cancel - function to call to abort the edit and return to a normal cell
|
||||
//editorParams - params object passed into the editorParams column definition property
|
||||
|
||||
//create and style editor
|
||||
const editor = document.createElement("input");
|
||||
|
||||
const $editor = $(editor);
|
||||
editor.classList.add("form-control");
|
||||
|
||||
//create and style input
|
||||
editor.style.padding = "3px";
|
||||
editor.style.width = "100%";
|
||||
editor.style.boxSizing = "border-box";
|
||||
|
||||
//Set value of editor to the current value of the cell
|
||||
const noteId = cell.getValue();
|
||||
if (noteId) {
|
||||
const note = froca.getNoteFromCache(noteId);
|
||||
editor.value = note.title;
|
||||
}
|
||||
|
||||
//set focus on the select box when the editor is selected
|
||||
onRendered(function(){
|
||||
note_autocomplete.initNoteAutocomplete($editor, {
|
||||
allowCreatingNotes: true
|
||||
}).on("autocomplete:noteselected", (event, suggestion, dataset) => {
|
||||
const notePath = suggestion.notePath;
|
||||
if (!notePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noteId = notePath.split("/").at(-1);
|
||||
success(noteId);
|
||||
});
|
||||
editor.focus();
|
||||
});
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("input-group");
|
||||
container.classList.add("autocomplete");
|
||||
container.appendChild(editor);
|
||||
return container;
|
||||
};
|
||||
74
apps/client/src/widgets/view_widgets/table_view/rows.ts
Normal file
74
apps/client/src/widgets/view_widgets/table_view/rows.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import FNote from "../../../entities/fnote.js";
|
||||
import type { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
||||
import type { PromotedAttributeInformation } from "./columns.js";
|
||||
|
||||
export type TableData = {
|
||||
iconClass: string;
|
||||
noteId: string;
|
||||
title: string;
|
||||
labels: Record<string, boolean | string | null>;
|
||||
relations: Record<string, boolean | string | null>;
|
||||
branchId: string;
|
||||
};
|
||||
|
||||
export async function buildRowDefinitions(parentNote: FNote, notes: FNote[], infos: PromotedAttributeInformation[]) {
|
||||
const definitions: TableData[] = [];
|
||||
for (const branch of parentNote.getChildBranches()) {
|
||||
const note = await branch.getNote();
|
||||
if (!note) {
|
||||
continue; // Skip if the note is not found
|
||||
}
|
||||
|
||||
const labels: typeof definitions[0]["labels"] = {};
|
||||
const relations: typeof definitions[0]["relations"] = {};
|
||||
for (const { name, type } of infos) {
|
||||
if (type === "relation") {
|
||||
relations[name] = note.getRelationValue(name);
|
||||
} else if (type === "boolean") {
|
||||
labels[name] = note.hasLabel(name);
|
||||
} else {
|
||||
labels[name] = note.getLabelValue(name);
|
||||
}
|
||||
}
|
||||
definitions.push({
|
||||
iconClass: note.getIcon(),
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
labels,
|
||||
relations,
|
||||
branchId: branch.branchId
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
export default function getPromotedAttributeInformation(parentNote: FNote) {
|
||||
const info: PromotedAttributeInformation[] = [];
|
||||
for (const promotedAttribute of parentNote.getPromotedDefinitionAttributes()) {
|
||||
const def = promotedAttribute.getDefinition();
|
||||
if (def.multiplicity !== "single") {
|
||||
console.warn("Multiple values are not supported for now");
|
||||
continue;
|
||||
}
|
||||
|
||||
const [ labelType, name ] = promotedAttribute.name.split(":", 2);
|
||||
if (promotedAttribute.type !== "label") {
|
||||
console.warn("Relations are not supported for now");
|
||||
continue;
|
||||
}
|
||||
|
||||
let type: LabelType | "relation" = def.labelType || "text";
|
||||
if (labelType === "relation") {
|
||||
type = "relation";
|
||||
}
|
||||
|
||||
info.push({
|
||||
name,
|
||||
title: def.promotedAlias,
|
||||
type
|
||||
});
|
||||
}
|
||||
console.log("Promoted attribute information", info);
|
||||
return info;
|
||||
}
|
||||
@@ -1,18 +1,30 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import Component from "../../components/component.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
|
||||
import ViewModeStorage from "./view_mode_storage.js";
|
||||
|
||||
export interface ViewModeArgs {
|
||||
$parent: JQuery<HTMLElement>;
|
||||
parentNote: FNote;
|
||||
parentNotePath?: string | null;
|
||||
noteIds: string[];
|
||||
showNotePath?: boolean;
|
||||
}
|
||||
|
||||
export default abstract class ViewMode {
|
||||
export default abstract class ViewMode<T extends object> extends Component {
|
||||
|
||||
constructor(args: ViewModeArgs) {
|
||||
private _viewStorage: ViewModeStorage<T> | null;
|
||||
protected parentNote: FNote;
|
||||
protected viewType: ViewTypeOptions;
|
||||
|
||||
constructor(args: ViewModeArgs, viewType: ViewTypeOptions) {
|
||||
super();
|
||||
this.parentNote = args.parentNote;
|
||||
this._viewStorage = null;
|
||||
// note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work
|
||||
args.$parent.empty();
|
||||
this.viewType = viewType;
|
||||
}
|
||||
|
||||
abstract renderList(): Promise<JQuery<HTMLElement> | undefined>;
|
||||
@@ -32,4 +44,13 @@ export default abstract class ViewMode {
|
||||
return false;
|
||||
}
|
||||
|
||||
get viewStorage() {
|
||||
if (this._viewStorage) {
|
||||
return this._viewStorage;
|
||||
}
|
||||
|
||||
this._viewStorage = new ViewModeStorage(this.parentNote, this.viewType);
|
||||
return this._viewStorage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
43
apps/client/src/widgets/view_widgets/view_mode_storage.ts
Normal file
43
apps/client/src/widgets/view_widgets/view_mode_storage.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type FNote from "../../entities/fnote";
|
||||
import type { ViewTypeOptions } from "../../services/note_list_renderer";
|
||||
import server from "../../services/server";
|
||||
|
||||
const ATTACHMENT_ROLE = "viewConfig";
|
||||
|
||||
export default class ViewModeStorage<T extends object> {
|
||||
|
||||
private note: FNote;
|
||||
private attachmentName: string;
|
||||
|
||||
constructor(note: FNote, viewType: ViewTypeOptions) {
|
||||
this.note = note;
|
||||
this.attachmentName = viewType + ".json";
|
||||
}
|
||||
|
||||
async store(data: T) {
|
||||
const payload = {
|
||||
role: ATTACHMENT_ROLE,
|
||||
title: this.attachmentName,
|
||||
mime: "application/json",
|
||||
content: JSON.stringify(data),
|
||||
position: 0
|
||||
};
|
||||
await server.post(`notes/${this.note.noteId}/attachments?matchBy=title`, payload);
|
||||
}
|
||||
|
||||
async restore() {
|
||||
const existingAttachments = await this.note.getAttachmentsByRole(ATTACHMENT_ROLE);
|
||||
if (existingAttachments.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attachment = existingAttachments
|
||||
.find(a => a.title === this.attachmentName);
|
||||
if (!attachment) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attachmentData = await server.get<{ content: string } | null>(`attachments/${attachment.attachmentId}/blob`);
|
||||
return JSON.parse(attachmentData?.content ?? "{}");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": [ "ESNext" ],
|
||||
"outDir": "dist",
|
||||
"types": [
|
||||
"node"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "16.6.0",
|
||||
"electron": "36.6.0"
|
||||
"dotenv": "17.0.1",
|
||||
"electron": "37.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "main.cjs",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "36.6.0",
|
||||
"electron": "37.2.0",
|
||||
"@electron-forge/cli": "7.8.1",
|
||||
"@electron-forge/maker-deb": "7.8.1",
|
||||
"@electron-forge/maker-dmg": "7.8.1",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@triliumnext/desktop": "workspace:*",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "36.6.0",
|
||||
"electron": "37.2.0",
|
||||
"fs-extra": "11.3.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "16.6.0"
|
||||
"dotenv": "17.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.1.1"
|
||||
"better-sqlite3": "12.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@triliumnext/server",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.1.1"
|
||||
"better-sqlite3": "12.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/remote": "2.1.2",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/multer": "1.4.13",
|
||||
"@types/multer": "2.0.0",
|
||||
"@types/safe-compare": "1.1.2",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sax": "1.2.7",
|
||||
@@ -39,7 +39,7 @@
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/xml2js": "0.4.14",
|
||||
"express-http-proxy": "2.1.1",
|
||||
"@anthropic-ai/sdk": "0.55.0",
|
||||
"@anthropic-ai/sdk": "0.55.1",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/express-partial-content": "workspace:*",
|
||||
@@ -59,7 +59,7 @@
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.1",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "36.6.0",
|
||||
"electron": "37.2.0",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
@@ -74,7 +74,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.2.1",
|
||||
"i18next": "25.3.0",
|
||||
"i18next-fs-backend": "2.6.0",
|
||||
"image-type": "6.0.0",
|
||||
"ini": "5.0.0",
|
||||
@@ -83,12 +83,12 @@
|
||||
"jimp": "1.6.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "26.1.0",
|
||||
"marked": "15.0.12",
|
||||
"marked": "16.0.0",
|
||||
"mime-types": "3.0.1",
|
||||
"multer": "2.0.1",
|
||||
"normalize-strings": "1.1.1",
|
||||
"ollama": "0.5.16",
|
||||
"openai": "5.8.1",
|
||||
"openai": "5.8.2",
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
@@ -105,7 +105,7 @@
|
||||
"tmp": "0.2.3",
|
||||
"turndown": "7.2.0",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "8.18.2",
|
||||
"ws": "8.18.3",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "3.2.0"
|
||||
},
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
File diff suppressed because one or more lines are too long
@@ -42,5 +42,5 @@
|
||||
This will export the notes in an unencrypted form, so if you reimport into
|
||||
Trilium, make sure to re-protect these notes.</p>
|
||||
<h2>Supported syntax</h2>
|
||||
<p>See the dedicated page: <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/mHbBMPDPkVV5/Oau6X9rCuegd/_help_rJ9grSgoExl9">Supported syntax</a>
|
||||
<p>See the dedicated page: <a class="reference-link" href="#root/_help_rJ9grSgoExl9">Supported syntax</a>
|
||||
</p>
|
||||
@@ -41,7 +41,7 @@
|
||||
Trilium-compatible syntax, but it will not export Trilium Notes into Markdown
|
||||
files with this syntax.</p>
|
||||
<aside class="admonition important">
|
||||
<p>The path to pages in wikilinks is resolved relatively to the <em>import root </em>and
|
||||
<p>The path to pages in wikilinks is resolved relatively to the <em>import root</em> and
|
||||
not the current directory of the note. This is to be inline with other
|
||||
platforms that use wikilinks such as SilverBullet.</p>
|
||||
<p>The root path of the import is determined as follows:</p>
|
||||
|
||||
98
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table.html
generated
vendored
Normal file
98
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table.html
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1050/259;" src="Table_image.png" width="1050"
|
||||
height="259">
|
||||
</figure>
|
||||
<p>The table view displays information in a grid, where the rows are individual
|
||||
notes and the columns are <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
In addition, values are editable.</p>
|
||||
<h2>Interaction</h2>
|
||||
<h3>Creating a new table</h3>
|
||||
<p>Right click the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||
select <em>Insert child note</em> and look for the <em>Table item</em>.</p>
|
||||
<h3>Adding columns</h3>
|
||||
<p>Each column is a <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">promoted attribute</a> that
|
||||
is defined on the Book note. Ideally, the promoted attributes need to be
|
||||
inheritable in order to show up in the child notes.</p>
|
||||
<p>To create a new column, simply press <em>Add new column </em>at the bottom
|
||||
of the table.</p>
|
||||
<p>There are also a few predefined columns:</p>
|
||||
<ul>
|
||||
<li>The current item number, identified by the <code>#</code> symbol. This simply
|
||||
counts the note and is affected by sorting.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_m1lbrzyKDaRB">Note ID</a>,
|
||||
representing the unique ID used internally by Trilium</li>
|
||||
<li>The title of the note.</li>
|
||||
</ul>
|
||||
<h3>Adding new rows</h3>
|
||||
<p>Each row is actually a note that is a child of the book note.</p>
|
||||
<p>To create a new note, press <em>Add new row</em> at the bottom of the table.
|
||||
By default it will try to edit the title of the newly created note.</p>
|
||||
<p>Alternatively, the note can be created from the<a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> or
|
||||
<a
|
||||
href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">scripting</a>.</p>
|
||||
<h3>Editing data</h3>
|
||||
<p>Simply click on a cell within a row to change its value. The change will
|
||||
not only reflect in the table, but also as an attribute of the corresponding
|
||||
note.</p>
|
||||
<ul>
|
||||
<li>The editing will respect the type of the promoted attribute, by presenting
|
||||
a normal text box, a number selector or a date selector for example.</li>
|
||||
<li>It also possible to change the title of a note.</li>
|
||||
<li>Editing relations is also possible, by using the note autocomplete.</li>
|
||||
</ul>
|
||||
<h2>Working with the data</h2>
|
||||
<h3>Sorting</h3>
|
||||
<p>It is possible to sort the data by the values of a column:</p>
|
||||
<ul>
|
||||
<li>To do so, simply click on a column.</li>
|
||||
<li>To switch between ascending or descending sort, simply click again on
|
||||
the same column. The arrow next to the column will indicate the direction
|
||||
of the sort.</li>
|
||||
</ul>
|
||||
<h3>Reordering and hiding columns</h3>
|
||||
<ul>
|
||||
<li>Columns can be reordered by dragging the header of the columns.</li>
|
||||
<li>Columns can be hidden or shown by right clicking on a column and clicking
|
||||
the item corresponding to the column.</li>
|
||||
</ul>
|
||||
<h3>Reordering rows</h3>
|
||||
<p>Notes can be dragged around to change their order. This will also change
|
||||
the order of the note in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
||||
<p>Currently, it's possible to reorder notes even if sorting is used, but
|
||||
the result might be inconsistent.</p>
|
||||
<h2>Limitations</h2>
|
||||
<p>The table functionality is still in its early stages, as such it faces
|
||||
quite a few important limitations:</p>
|
||||
<ol>
|
||||
<li>As mentioned previously, the columns of the table are defined as
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
<ol>
|
||||
<li>But only the promoted attributes that are defined at the level of the
|
||||
Book note are actually taken into consideration.</li>
|
||||
<li>There are plans to recursively look for columns across the sub-hierarchy.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>Hierarchy is not yet supported, so the table will only show the items
|
||||
that are direct children of the <em>Book</em> note.</li>
|
||||
<li>Multiple labels and relations are not supported. If a <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> is
|
||||
defined with a <em>Multi value</em> specificity, they will be ignored.</li>
|
||||
</ol>
|
||||
<h2>Use in search</h2>
|
||||
<p>The table view can be used in a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_m523cpzocqaD">Saved Search</a> by
|
||||
adding the <code>#viewType=table</code> attribute.</p>
|
||||
<p>Unlike when used in a book, saved searches are not limited to the sub-hierarchy
|
||||
of a note and allows for advanced queries thanks to the power of the
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>.</p>
|
||||
<p>However, there are also some limitations:</p>
|
||||
<ul>
|
||||
<li>It's not possible to reorder notes.</li>
|
||||
<li>It's not possible to add a new row.</li>
|
||||
</ul>
|
||||
<p>Columns are supported, by being defined as <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> to
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_m523cpzocqaD">Saved Search</a> note.</p>
|
||||
<p>Editing is also supported.</p>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table_image.png
generated
vendored
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table_image.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
5
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Note Map.html
generated
vendored
5
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Note Map.html
generated
vendored
@@ -6,4 +6,7 @@
|
||||
of the same name: <a href="#root/_help_BCkXAVs63Ttv">Note Map (Link map, Tree map)</a>.</p>
|
||||
<p>Once created, the note map will display the relations between notes. Only
|
||||
the notes that are part of the parent of the note map will be displayed
|
||||
(including their children).</p>
|
||||
(including their children).</p>
|
||||
<p>The labels <code>mapIncludeRelation</code> and <code>mapExcludeRelation</code>,
|
||||
if set, filter the note map to include only the specified relations or
|
||||
to exclude the specified relations, respectively.</p>
|
||||
@@ -62,4 +62,4 @@ class="image image-style-align-center">
|
||||
are currently no plans for adjusting it or allowing the user to customize
|
||||
them.</p>
|
||||
<h3>Markdown support</h3>
|
||||
<p>See <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/mHbBMPDPkVV5/Oau6X9rCuegd/_help_rJ9grSgoExl9">Supported syntax</a>.</p>
|
||||
<p>See <a class="reference-link" href="#root/_help_rJ9grSgoExl9">Supported syntax</a>.</p>
|
||||
@@ -48,7 +48,8 @@ function updateNoteAttribute(req: Request) {
|
||||
attribute = new BAttribute({
|
||||
noteId: noteId,
|
||||
name: body.name,
|
||||
type: body.type
|
||||
type: body.type,
|
||||
isInheritable: body.isInheritable
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,11 @@ function getLinkMap(req: Request) {
|
||||
const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy("excludeFromNoteMap");
|
||||
let unfilteredNotes;
|
||||
|
||||
const toSet = (data: unknown) => new Set<string>(data instanceof Array ? data : []);
|
||||
|
||||
const excludeRelations = toSet(req.body.excludeRelations);
|
||||
const includeRelations = toSet(req.body.includeRelations);
|
||||
|
||||
if (mapRootNote.type === "search") {
|
||||
// for search notes, we want to consider the direct search results only without the descendants
|
||||
unfilteredNotes = mapRootNote.getSearchResultNotes();
|
||||
@@ -152,6 +157,10 @@ function getLinkMap(req: Request) {
|
||||
}
|
||||
|
||||
return !parentNote.getChildNotes().find((childNote) => childNote.noteId === rel.value);
|
||||
} else if (includeRelations.size != 0 && !includeRelations.has(rel.name)) {
|
||||
return false;
|
||||
} else if (excludeRelations.has(rel.name)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,8 +255,12 @@ export interface Api {
|
||||
/**
|
||||
* Returns week note for given date. If such a note doesn't exist, it is created.
|
||||
*
|
||||
* <p>
|
||||
* If the calendar does not support week notes, this method will return `null`.
|
||||
*
|
||||
* @param date in YYYY-MM-DD format
|
||||
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||
* @return an existing or newly created week note, or `null` if the calendar does not support week notes.
|
||||
*/
|
||||
getWeekNote(date: string, rootNote: BNote): BNote | null;
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ export default [
|
||||
{ type: "label", name: "pageSize" },
|
||||
{ type: "label", name: "viewType" },
|
||||
{ type: "label", name: "mapRootNoteId" },
|
||||
{ type: "label", name: "mapExcludeRelation" },
|
||||
{ type: "label", name: "mapIncludeRelation" },
|
||||
{ type: "label", name: "bookmarkFolder" },
|
||||
{ type: "label", name: "sorted" },
|
||||
{ type: "label", name: "sortDirection" },
|
||||
|
||||
@@ -75,6 +75,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
|
||||
|
||||
function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
|
||||
let fileName = baseFileName.trim();
|
||||
if (!fileName) {
|
||||
fileName = "note";
|
||||
}
|
||||
|
||||
// Crop fileName to avoid its length exceeding 30 and prevent cutting into the extension.
|
||||
if (fileName.length > 30) {
|
||||
@@ -366,7 +369,7 @@ ${markdownContent}`;
|
||||
function saveNote(noteMeta: NoteMeta, filePathPrefix: string) {
|
||||
log.info(`Exporting note '${noteMeta.noteId}'`);
|
||||
|
||||
if (!noteMeta.noteId || !noteMeta.title) {
|
||||
if (!noteMeta.noteId || noteMeta.title === undefined) {
|
||||
throw new Error("Missing note meta.");
|
||||
}
|
||||
|
||||
@@ -515,97 +518,108 @@ ${markdownContent}`;
|
||||
archive.append(cssContent, { name: cssMeta.dataFileName });
|
||||
}
|
||||
|
||||
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
|
||||
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
||||
if (!rootMeta) {
|
||||
throw new Error("Unable to create root meta.");
|
||||
}
|
||||
try {
|
||||
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
|
||||
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
||||
if (!rootMeta) {
|
||||
throw new Error("Unable to create root meta.");
|
||||
}
|
||||
|
||||
const metaFile: NoteMetaFile = {
|
||||
formatVersion: 2,
|
||||
appVersion: packageInfo.version,
|
||||
files: [rootMeta]
|
||||
};
|
||||
|
||||
let navigationMeta: NoteMeta | null = null;
|
||||
let indexMeta: NoteMeta | null = null;
|
||||
let cssMeta: NoteMeta | null = null;
|
||||
|
||||
if (format === "html") {
|
||||
navigationMeta = {
|
||||
noImport: true,
|
||||
dataFileName: "navigation.html"
|
||||
const metaFile: NoteMetaFile = {
|
||||
formatVersion: 2,
|
||||
appVersion: packageInfo.version,
|
||||
files: [rootMeta]
|
||||
};
|
||||
|
||||
metaFile.files.push(navigationMeta);
|
||||
let navigationMeta: NoteMeta | null = null;
|
||||
let indexMeta: NoteMeta | null = null;
|
||||
let cssMeta: NoteMeta | null = null;
|
||||
|
||||
indexMeta = {
|
||||
noImport: true,
|
||||
dataFileName: "index.html"
|
||||
};
|
||||
if (format === "html") {
|
||||
navigationMeta = {
|
||||
noImport: true,
|
||||
dataFileName: "navigation.html"
|
||||
};
|
||||
|
||||
metaFile.files.push(indexMeta);
|
||||
metaFile.files.push(navigationMeta);
|
||||
|
||||
cssMeta = {
|
||||
noImport: true,
|
||||
dataFileName: "style.css"
|
||||
};
|
||||
indexMeta = {
|
||||
noImport: true,
|
||||
dataFileName: "index.html"
|
||||
};
|
||||
|
||||
metaFile.files.push(cssMeta);
|
||||
}
|
||||
metaFile.files.push(indexMeta);
|
||||
|
||||
for (const noteMeta of Object.values(noteIdToMeta)) {
|
||||
// filter out relations which are not inside this export
|
||||
noteMeta.attributes = (noteMeta.attributes || []).filter((attr) => {
|
||||
if (attr.type !== "relation") {
|
||||
return true;
|
||||
} else if (attr.value in noteIdToMeta) {
|
||||
return true;
|
||||
} else if (attr.value === "root" || attr.value?.startsWith("_")) {
|
||||
// relations to "named" noteIds can be preserved
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
cssMeta = {
|
||||
noImport: true,
|
||||
dataFileName: "style.css"
|
||||
};
|
||||
|
||||
metaFile.files.push(cssMeta);
|
||||
}
|
||||
|
||||
for (const noteMeta of Object.values(noteIdToMeta)) {
|
||||
// filter out relations which are not inside this export
|
||||
noteMeta.attributes = (noteMeta.attributes || []).filter((attr) => {
|
||||
if (attr.type !== "relation") {
|
||||
return true;
|
||||
} else if (attr.value in noteIdToMeta) {
|
||||
return true;
|
||||
} else if (attr.value === "root" || attr.value?.startsWith("_")) {
|
||||
// relations to "named" noteIds can be preserved
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!rootMeta) {
|
||||
// corner case of disabled export for exported note
|
||||
if ("sendStatus" in res) {
|
||||
res.sendStatus(400);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const metaFileJson = JSON.stringify(metaFile, null, "\t");
|
||||
|
||||
archive.append(metaFileJson, { name: "!!!meta.json" });
|
||||
|
||||
saveNote(rootMeta, "");
|
||||
|
||||
if (format === "html") {
|
||||
if (!navigationMeta || !indexMeta || !cssMeta) {
|
||||
throw new Error("Missing meta.");
|
||||
}
|
||||
|
||||
saveNavigation(rootMeta, navigationMeta);
|
||||
saveIndex(rootMeta, indexMeta);
|
||||
saveCss(rootMeta, cssMeta);
|
||||
}
|
||||
|
||||
const note = branch.getNote();
|
||||
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected() || "note"}.zip`;
|
||||
|
||||
if (setHeaders && "setHeader" in res) {
|
||||
res.setHeader("Content-Disposition", getContentDisposition(zipFileName));
|
||||
res.setHeader("Content-Type", "application/zip");
|
||||
}
|
||||
|
||||
archive.pipe(res);
|
||||
await archive.finalize();
|
||||
taskContext.taskSucceeded();
|
||||
} catch (e: unknown) {
|
||||
const message = `Export failed with error: ${e instanceof Error ? e.message : String(e)}`;
|
||||
log.error(message);
|
||||
taskContext.reportError(message);
|
||||
|
||||
if (!rootMeta) {
|
||||
// corner case of disabled export for exported note
|
||||
if ("sendStatus" in res) {
|
||||
res.sendStatus(400);
|
||||
res.removeHeader("Content-Disposition");
|
||||
res.removeHeader("Content-Type");
|
||||
res.status(500).send(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const metaFileJson = JSON.stringify(metaFile, null, "\t");
|
||||
|
||||
archive.append(metaFileJson, { name: "!!!meta.json" });
|
||||
|
||||
saveNote(rootMeta, "");
|
||||
|
||||
if (format === "html") {
|
||||
if (!navigationMeta || !indexMeta || !cssMeta) {
|
||||
throw new Error("Missing meta.");
|
||||
}
|
||||
|
||||
saveNavigation(rootMeta, navigationMeta);
|
||||
saveIndex(rootMeta, indexMeta);
|
||||
saveCss(rootMeta, cssMeta);
|
||||
}
|
||||
|
||||
const note = branch.getNote();
|
||||
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
|
||||
|
||||
if (setHeaders && "setHeader" in res) {
|
||||
res.setHeader("Content-Disposition", getContentDisposition(zipFileName));
|
||||
res.setHeader("Content-Type", "application/zip");
|
||||
}
|
||||
|
||||
archive.pipe(res);
|
||||
await archive.finalize();
|
||||
|
||||
taskContext.taskSucceeded();
|
||||
}
|
||||
|
||||
async function exportToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string, zipExportOptions?: AdvancedExportOptions) {
|
||||
|
||||
@@ -26,6 +26,23 @@ export default function buildHiddenSubtreeTemplates() {
|
||||
value: "promoted,alias=Description,single,text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "_template_table",
|
||||
type: "book",
|
||||
title: "Table",
|
||||
icon: "bx bx-table",
|
||||
attributes: [
|
||||
{
|
||||
name: "template",
|
||||
type: "label"
|
||||
},
|
||||
{
|
||||
name: "viewType",
|
||||
type: "label",
|
||||
value: "table"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -29,16 +29,18 @@
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^6.2.6"
|
||||
"vite": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inlang/paraglide-js": "^2.0.0"
|
||||
},
|
||||
"nx": {
|
||||
"typecheck": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"targets": {
|
||||
"typecheck": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
docs/Developer Guide/!!!meta.json
vendored
2
docs/Developer Guide/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.95.0",
|
||||
"appVersion": "0.96.0",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
|
||||
92
docs/Release Notes/!!!meta.json
vendored
92
docs/Release Notes/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.95.0",
|
||||
"appVersion": "0.96.0",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
@@ -61,6 +61,32 @@
|
||||
"attachments": [],
|
||||
"dirFileName": "Release Notes",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "mYXFde3LuNR7",
|
||||
"notePath": [
|
||||
"hD3V4hiu2VW4",
|
||||
"mYXFde3LuNR7"
|
||||
],
|
||||
"title": "v0.96.0",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "template",
|
||||
"value": "wyurrlcDl416",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "v0.96.0.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "jthwbL0FdaeU",
|
||||
@@ -69,7 +95,7 @@
|
||||
"jthwbL0FdaeU"
|
||||
],
|
||||
"title": "v0.95.0",
|
||||
"notePosition": 10,
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -95,7 +121,7 @@
|
||||
"7HGYsJbLuhnv"
|
||||
],
|
||||
"title": "v0.94.1",
|
||||
"notePosition": 20,
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -121,7 +147,7 @@
|
||||
"Neq53ujRGBqv"
|
||||
],
|
||||
"title": "v0.94.0",
|
||||
"notePosition": 30,
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -147,7 +173,7 @@
|
||||
"VN3xnce1vLkX"
|
||||
],
|
||||
"title": "v0.93.0",
|
||||
"notePosition": 40,
|
||||
"notePosition": 50,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -165,7 +191,7 @@
|
||||
"WRaBfQqPr6qo"
|
||||
],
|
||||
"title": "v0.92.7",
|
||||
"notePosition": 50,
|
||||
"notePosition": 60,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -191,7 +217,7 @@
|
||||
"a2rwfKNmUFU1"
|
||||
],
|
||||
"title": "v0.92.6",
|
||||
"notePosition": 60,
|
||||
"notePosition": 70,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -209,7 +235,7 @@
|
||||
"fEJ8qErr0BKL"
|
||||
],
|
||||
"title": "v0.92.5-beta",
|
||||
"notePosition": 70,
|
||||
"notePosition": 80,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -227,7 +253,7 @@
|
||||
"kkkZQQGSXjwy"
|
||||
],
|
||||
"title": "v0.92.4",
|
||||
"notePosition": 80,
|
||||
"notePosition": 90,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -245,7 +271,7 @@
|
||||
"vAroNixiezaH"
|
||||
],
|
||||
"title": "v0.92.3-beta",
|
||||
"notePosition": 90,
|
||||
"notePosition": 100,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -263,7 +289,7 @@
|
||||
"mHEq1wxAKNZd"
|
||||
],
|
||||
"title": "v0.92.2-beta",
|
||||
"notePosition": 100,
|
||||
"notePosition": 110,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -281,7 +307,7 @@
|
||||
"IykjoAmBpc61"
|
||||
],
|
||||
"title": "v0.92.1-beta",
|
||||
"notePosition": 110,
|
||||
"notePosition": 120,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -299,7 +325,7 @@
|
||||
"dq2AJ9vSBX4Y"
|
||||
],
|
||||
"title": "v0.92.0-beta",
|
||||
"notePosition": 120,
|
||||
"notePosition": 130,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -317,7 +343,7 @@
|
||||
"3a8aMe4jz4yM"
|
||||
],
|
||||
"title": "v0.91.6",
|
||||
"notePosition": 130,
|
||||
"notePosition": 140,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -335,7 +361,7 @@
|
||||
"8djQjkiDGESe"
|
||||
],
|
||||
"title": "v0.91.5",
|
||||
"notePosition": 140,
|
||||
"notePosition": 150,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -353,7 +379,7 @@
|
||||
"OylxVoVJqNmr"
|
||||
],
|
||||
"title": "v0.91.4-beta",
|
||||
"notePosition": 150,
|
||||
"notePosition": 160,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -371,7 +397,7 @@
|
||||
"tANGQDvnyhrj"
|
||||
],
|
||||
"title": "v0.91.3-beta",
|
||||
"notePosition": 160,
|
||||
"notePosition": 170,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -389,7 +415,7 @@
|
||||
"hMoBfwSoj1SC"
|
||||
],
|
||||
"title": "v0.91.2-beta",
|
||||
"notePosition": 170,
|
||||
"notePosition": 180,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -407,7 +433,7 @@
|
||||
"a2XMSKROCl9z"
|
||||
],
|
||||
"title": "v0.91.1-beta",
|
||||
"notePosition": 180,
|
||||
"notePosition": 190,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -425,7 +451,7 @@
|
||||
"yqXFvWbLkuMD"
|
||||
],
|
||||
"title": "v0.90.12",
|
||||
"notePosition": 190,
|
||||
"notePosition": 200,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -443,7 +469,7 @@
|
||||
"veS7pg311yJP"
|
||||
],
|
||||
"title": "v0.90.11-beta",
|
||||
"notePosition": 200,
|
||||
"notePosition": 210,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -461,7 +487,7 @@
|
||||
"sq5W9TQxRqMq"
|
||||
],
|
||||
"title": "v0.90.10-beta",
|
||||
"notePosition": 210,
|
||||
"notePosition": 220,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -479,7 +505,7 @@
|
||||
"yFEGVCUM9tPx"
|
||||
],
|
||||
"title": "v0.90.9-beta",
|
||||
"notePosition": 220,
|
||||
"notePosition": 230,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -497,7 +523,7 @@
|
||||
"o4wAGqOQuJtV"
|
||||
],
|
||||
"title": "v0.90.8",
|
||||
"notePosition": 230,
|
||||
"notePosition": 240,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -530,7 +556,7 @@
|
||||
"i4A5g9iOg9I0"
|
||||
],
|
||||
"title": "v0.90.7-beta",
|
||||
"notePosition": 240,
|
||||
"notePosition": 250,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -548,7 +574,7 @@
|
||||
"ThNf2GaKgXUs"
|
||||
],
|
||||
"title": "v0.90.6-beta",
|
||||
"notePosition": 250,
|
||||
"notePosition": 260,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -566,7 +592,7 @@
|
||||
"G4PAi554kQUr"
|
||||
],
|
||||
"title": "v0.90.5-beta",
|
||||
"notePosition": 260,
|
||||
"notePosition": 270,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -593,7 +619,7 @@
|
||||
"zATRobGRCmBn"
|
||||
],
|
||||
"title": "v0.90.4",
|
||||
"notePosition": 270,
|
||||
"notePosition": 280,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -611,7 +637,7 @@
|
||||
"sCDLf8IKn3Iz"
|
||||
],
|
||||
"title": "v0.90.3",
|
||||
"notePosition": 280,
|
||||
"notePosition": 290,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -629,7 +655,7 @@
|
||||
"VqqyBu4AuTjC"
|
||||
],
|
||||
"title": "v0.90.2-beta",
|
||||
"notePosition": 290,
|
||||
"notePosition": 300,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -647,7 +673,7 @@
|
||||
"RX3Nl7wInLsA"
|
||||
],
|
||||
"title": "v0.90.1-beta",
|
||||
"notePosition": 300,
|
||||
"notePosition": 310,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -665,7 +691,7 @@
|
||||
"GyueACukPWjk"
|
||||
],
|
||||
"title": "v0.90.0-beta",
|
||||
"notePosition": 310,
|
||||
"notePosition": 320,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -683,7 +709,7 @@
|
||||
"wyurrlcDl416"
|
||||
],
|
||||
"title": "Release Template",
|
||||
"notePosition": 320,
|
||||
"notePosition": 330,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
|
||||
2
docs/Release Notes/Release Notes/v0.94.1.md
vendored
2
docs/Release Notes/Release Notes/v0.94.1.md
vendored
@@ -1,6 +1,6 @@
|
||||
# v0.94.1
|
||||
> [!NOTE]
|
||||
> _Trilium Notes_ will rebrand itself back to Trilium Notes since @zadam was kind enough to give us the original name. See [#2190](https://github.com/orgs/TriliumNext/discussions/2190) for more info. This will probably be the "last" version branded as Trilium Notes_.
|
||||
> _Trilium Notes_ will rebrand itself back to Trilium Notes since @zadam was kind enough to give us the original name. See [#2190](https://github.com/orgs/TriliumNext/discussions/2190) for more info. This will probably be the "last" version branded as Trilium Notes\_.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
|
||||
50
docs/Release Notes/Release Notes/v0.96.0.md
vendored
Normal file
50
docs/Release Notes/Release Notes/v0.96.0.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# v0.96.0
|
||||
> [!NOTE]
|
||||
> The Docker image has been relocated to `triliumnext/trilium`. Please update your configuration accordingly.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
>
|
||||
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Trilium) (top-right).
|
||||
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
|
||||
|
||||
## 💡 Key highlights
|
||||
|
||||
* Thanks to a partnership with CKEditor, we now have a set of features that would otherwise be available on a commercial license only.
|
||||
* Slash commands for easy commands via the keyboard.
|
||||
* Text snippets to insert reusable pieces of text (similar to templates, but for blocks of text content).
|
||||
* For more information, see the user guide → Note Types → Text → Premium features.
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
* [“Insert note after” long-press dialog doesn’t create Note](https://github.com/TriliumNext/Notes/issues/2246)
|
||||
* Code notes: user's font selection not respected.
|
||||
* [Windows V0.95.0 Client Failed to sync with server (use of double-slashes)](https://github.com/TriliumNext/Notes/issues/2339) by @perfectra1n
|
||||
* Desktop client not working on older Linux distros
|
||||
* [NOT NULL constraint failed: revisions.title when saving an empty note](https://github.com/TriliumNext/Trilium/issues/6103)
|
||||
|
||||
## ✨ Improvements
|
||||
|
||||
* [Elixir language syntax highlighting for text notes](https://github.com/TriliumNext/Notes/pull/2327) (by @jshprentz) and code notes.
|
||||
* [Autocomplete: support specifying path when creating a new note](https://github.com/TriliumNext/Notes/pull/2342) by @SiriusXT
|
||||
* Markdown import: basic support for importing wikilinks
|
||||
* Text notes:
|
||||
* Allow disabling emoji auto-completion from settings.
|
||||
* Allow disabling note auto-completion from settings.
|
||||
* [Backend scripts: re-enable dayjs plugins by default](https://github.com/TriliumNext/Trilium/issues/6080)
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
* [regex search / Nix flake / restore dev docs](https://github.com/TriliumNext/Notes/pull/2341) by @FliegendeWurst
|
||||
* New premium features in text note type.
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
* Spanish improvements by @hasecilu
|
||||
|
||||
## 🛠️ Technical updates
|
||||
|
||||
* flake: fix Electron version, fix Wayland support, fix source filter by @FliegendeWurst
|
||||
* Improvements to the landing page (under development) by @FliegendeWurst
|
||||
* Updated Node.js to v22.17.0
|
||||
* Updated CKEditor to v45.2.1
|
||||
112
docs/User Guide/!!!meta.json
vendored
112
docs/User Guide/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.95.0",
|
||||
"appVersion": "0.96.0",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
@@ -3420,6 +3420,86 @@
|
||||
"dataFileName": "11_Calendar View_image.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "2FvYrpmOXm29",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"gh7bpGYxajRS",
|
||||
"BFs8mudNFgCS",
|
||||
"0ESUbbAxVnoK",
|
||||
"2FvYrpmOXm29"
|
||||
],
|
||||
"title": "Table",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-table",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "OFXdgB2nNk1F",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "m1lbrzyKDaRB",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "CdNpE2pqjmI6",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "m523cpzocqaD",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "eIg8jdvaoNNd",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Table.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "vJYUG9fLQ2Pd",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Table_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4350,6 +4430,13 @@
|
||||
"type": "text",
|
||||
"mime": "text/markdown",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "rJ9grSgoExl9",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
@@ -4363,13 +4450,6 @@
|
||||
"value": "bx bxl-markdown",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "rJ9grSgoExl9",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
@@ -5113,23 +5193,23 @@
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "nRhnJkTT8cPs",
|
||||
"value": "rJ9grSgoExl9",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "nRhnJkTT8cPs",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-info-circle",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "rJ9grSgoExl9",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
|
||||
83
docs/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table.md
vendored
Normal file
83
docs/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table.md
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
# Table
|
||||
<figure class="image"><img style="aspect-ratio:1050/259;" src="Table_image.png" width="1050" height="259"></figure>
|
||||
|
||||
The table view displays information in a grid, where the rows are individual notes and the columns are <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a>. In addition, values are editable.
|
||||
|
||||
## Interaction
|
||||
|
||||
### Creating a new table
|
||||
|
||||
Right click the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a> and select _Insert child note_ and look for the _Table item_.
|
||||
|
||||
### Adding columns
|
||||
|
||||
Each column is a [promoted attribute](../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md) that is defined on the Book note. Ideally, the promoted attributes need to be inheritable in order to show up in the child notes.
|
||||
|
||||
To create a new column, simply press _Add new column_ at the bottom of the table.
|
||||
|
||||
There are also a few predefined columns:
|
||||
|
||||
* The current item number, identified by the `#` symbol. This simply counts the note and is affected by sorting.
|
||||
* <a class="reference-link" href="../../../Advanced%20Usage/Note%20ID.md">Note ID</a>, representing the unique ID used internally by Trilium
|
||||
* The title of the note.
|
||||
|
||||
### Adding new rows
|
||||
|
||||
Each row is actually a note that is a child of the book note.
|
||||
|
||||
To create a new note, press _Add new row_ at the bottom of the table. By default it will try to edit the title of the newly created note.
|
||||
|
||||
Alternatively, the note can be created from the<a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a> or [scripting](../../../Scripting.md).
|
||||
|
||||
### Editing data
|
||||
|
||||
Simply click on a cell within a row to change its value. The change will not only reflect in the table, but also as an attribute of the corresponding note.
|
||||
|
||||
* The editing will respect the type of the promoted attribute, by presenting a normal text box, a number selector or a date selector for example.
|
||||
* It also possible to change the title of a note.
|
||||
* Editing relations is also possible, by using the note autocomplete.
|
||||
|
||||
## Working with the data
|
||||
|
||||
### Sorting
|
||||
|
||||
It is possible to sort the data by the values of a column:
|
||||
|
||||
* To do so, simply click on a column.
|
||||
* To switch between ascending or descending sort, simply click again on the same column. The arrow next to the column will indicate the direction of the sort.
|
||||
|
||||
### Reordering and hiding columns
|
||||
|
||||
* Columns can be reordered by dragging the header of the columns.
|
||||
* Columns can be hidden or shown by right clicking on a column and clicking the item corresponding to the column.
|
||||
|
||||
### Reordering rows
|
||||
|
||||
Notes can be dragged around to change their order. This will also change the order of the note in the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a>.
|
||||
|
||||
Currently, it's possible to reorder notes even if sorting is used, but the result might be inconsistent.
|
||||
|
||||
## Limitations
|
||||
|
||||
The table functionality is still in its early stages, as such it faces quite a few important limitations:
|
||||
|
||||
1. As mentioned previously, the columns of the table are defined as <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a>.
|
||||
1. But only the promoted attributes that are defined at the level of the Book note are actually taken into consideration.
|
||||
2. There are plans to recursively look for columns across the sub-hierarchy.
|
||||
2. Hierarchy is not yet supported, so the table will only show the items that are direct children of the _Book_ note.
|
||||
3. Multiple labels and relations are not supported. If a <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a> is defined with a _Multi value_ specificity, they will be ignored.
|
||||
|
||||
## Use in search
|
||||
|
||||
The table view can be used in a <a class="reference-link" href="../../../Note%20Types/Saved%20Search.md">Saved Search</a> by adding the `#viewType=table` attribute.
|
||||
|
||||
Unlike when used in a book, saved searches are not limited to the sub-hierarchy of a note and allows for advanced queries thanks to the power of the <a class="reference-link" href="../../Navigation/Search.md">Search</a>.
|
||||
|
||||
However, there are also some limitations:
|
||||
|
||||
* It's not possible to reorder notes.
|
||||
* It's not possible to add a new row.
|
||||
|
||||
Columns are supported, by being defined as <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a> to the <a class="reference-link" href="../../../Note%20Types/Saved%20Search.md">Saved Search</a> note.
|
||||
|
||||
Editing is also supported.
|
||||
BIN
docs/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table_image.png
vendored
Normal file
BIN
docs/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Table_image.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -3,4 +3,6 @@
|
||||
|
||||
A Note map is a note type which displays a standalone version of the feature of the same name: [Note Map (Link map, Tree map)](../Advanced%20Usage/Note%20Map%20\(Link%20map%2C%20Tree%20map\).md).
|
||||
|
||||
Once created, the note map will display the relations between notes. Only the notes that are part of the parent of the note map will be displayed (including their children).
|
||||
Once created, the note map will display the relations between notes. Only the notes that are part of the parent of the note map will be displayed (including their children).
|
||||
|
||||
The labels `mapIncludeRelation` and `mapExcludeRelation`, if set, filter the note map to include only the specified relations or to exclude the specified relations, respectively.
|
||||
32
package.json
32
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/source",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
@@ -27,20 +27,20 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@nx/devkit": "21.2.1",
|
||||
"@nx/esbuild": "21.2.1",
|
||||
"@nx/eslint": "21.2.1",
|
||||
"@nx/eslint-plugin": "21.2.1",
|
||||
"@nx/express": "21.2.1",
|
||||
"@nx/js": "21.2.1",
|
||||
"@nx/node": "21.2.1",
|
||||
"@nx/playwright": "21.2.1",
|
||||
"@nx/vite": "21.2.1",
|
||||
"@nx/web": "21.2.1",
|
||||
"@nx/devkit": "21.2.2",
|
||||
"@nx/esbuild": "21.2.2",
|
||||
"@nx/eslint": "21.2.2",
|
||||
"@nx/eslint-plugin": "21.2.2",
|
||||
"@nx/express": "21.2.2",
|
||||
"@nx/js": "21.2.2",
|
||||
"@nx/node": "21.2.2",
|
||||
"@nx/playwright": "21.2.2",
|
||||
"@nx/vite": "21.2.2",
|
||||
"@nx/web": "21.2.2",
|
||||
"@playwright/test": "^1.36.0",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "22.15.33",
|
||||
"@types/node": "22.16.0",
|
||||
"@vitest/coverage-v8": "^3.0.5",
|
||||
"@vitest/ui": "^3.0.0",
|
||||
"chalk": "5.4.1",
|
||||
@@ -54,15 +54,15 @@
|
||||
"jiti": "2.4.2",
|
||||
"jsdom": "~26.1.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"nx": "21.2.1",
|
||||
"nx": "21.2.2",
|
||||
"react-refresh": "^0.17.0",
|
||||
"rollup-plugin-webpack-stats": "2.0.7",
|
||||
"rollup-plugin-webpack-stats": "2.1.0",
|
||||
"tslib": "^2.3.0",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "~5.8.0",
|
||||
"typescript-eslint": "^8.19.0",
|
||||
"upath": "2.0.1",
|
||||
"vite": "^6.0.0",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-dts": "~4.5.0",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
@@ -89,7 +89,7 @@
|
||||
"@nx/js": "patches/@nx__js.patch"
|
||||
},
|
||||
"overrides": {
|
||||
"mermaid": "11.7.0",
|
||||
"mermaid": "11.8.0",
|
||||
"preact": "10.26.9",
|
||||
"roughjs": "4.6.6",
|
||||
"@types/express-serve-static-core": "5.0.6",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@codemirror/lang-xml": "6.1.0",
|
||||
"@codemirror/legacy-modes": "6.5.1",
|
||||
"@codemirror/search": "6.5.11",
|
||||
"@codemirror/view": "6.37.2",
|
||||
"@codemirror/view": "6.38.0",
|
||||
"@fsegurai/codemirror-theme-abcdef": "6.2.0",
|
||||
"@fsegurai/codemirror-theme-abyss": "6.2.0",
|
||||
"@fsegurai/codemirror-theme-android-studio": "6.2.0",
|
||||
@@ -62,6 +62,6 @@
|
||||
"codemirror-lang-elixir": "4.0.0",
|
||||
"codemirror-lang-hcl": "0.1.0",
|
||||
"codemirror-lang-mermaid": "0.5.0",
|
||||
"eslint-linter-browserify": "9.29.0"
|
||||
"eslint-linter-browserify": "9.30.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/commons",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "Shared library between the clients (e.g. browser, Electron) and the server, mostly for type definitions and utility methods.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@types/swagger-ui": "^5.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^17.0.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.0.0",
|
||||
"highlight.js": "^11.8.0",
|
||||
|
||||
3776
pnpm-lock.yaml
generated
3776
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user