Skip to content

Commit ac8b053

Browse files
authored
Introduce the table view (#6097)
2 parents 300e5a5 + 6ce25a8 commit ac8b053

File tree

43 files changed

+1185
-77
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1185
-77
lines changed

apps/client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"preact": "10.26.9",
5555
"split.js": "1.6.5",
5656
"svg-pan-zoom": "3.6.2",
57+
"tabulator-tables": "6.3.1",
5758
"vanilla-js-wheel-zoom": "9.0.4"
5859
},
5960
"devDependencies": {
@@ -63,6 +64,7 @@
6364
"@types/leaflet": "1.9.19",
6465
"@types/leaflet-gpx": "1.3.7",
6566
"@types/mark.js": "8.11.12",
67+
"@types/tabulator-tables": "6.2.6",
6668
"copy-webpack-plugin": "13.0.0",
6769
"happy-dom": "18.0.1",
6870
"script-loader": "0.7.2",

apps/client/src/components/component.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
9393

9494
if (fun) {
9595
return this.callMethod(fun, data);
96-
} else {
97-
if (!this.parent) {
98-
throw new Error(`Component "${this.componentId}" does not have a parent attached to propagate a command.`);
99-
}
100-
96+
} else if (this.parent) {
10197
return this.parent.triggerCommand(name, data);
10298
}
10399
}

apps/client/src/components/note_context.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -315,14 +315,38 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
315315
}
316316

317317
hasNoteList() {
318-
return (
319-
this.note &&
320-
["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "") &&
321-
(this.note.hasChildren() || this.note.getLabelValue("viewType") === "calendar") &&
322-
["book", "text", "code"].includes(this.note.type) &&
323-
this.note.mime !== "text/x-sqlite;schema=trilium" &&
324-
!this.note.isLabelTruthy("hideChildrenOverview")
325-
);
318+
const note = this.note;
319+
320+
if (!note) {
321+
return false;
322+
}
323+
324+
if (!["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "")) {
325+
return false;
326+
}
327+
328+
// Some book types must always display a note list, even if no children.
329+
if (["calendar", "table"].includes(note.getLabelValue("viewType") ?? "")) {
330+
return true;
331+
}
332+
333+
if (!note.hasChildren()) {
334+
return false;
335+
}
336+
337+
if (!["book", "text", "code"].includes(note.type)) {
338+
return false;
339+
}
340+
341+
if (note.mime === "text/x-sqlite;schema=trilium") {
342+
return false;
343+
}
344+
345+
if (note.isLabelTruthy("hideChildrenOverview")) {
346+
return false;
347+
}
348+
349+
return true;
326350
}
327351

328352
async getTextEditor(callback?: GetTextEditorCallback) {

apps/client/src/menus/context_menu.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import keyboardActionService from "../services/keyboard_actions.js";
22
import note_tooltip from "../services/note_tooltip.js";
33
import utils from "../services/utils.js";
44

5-
interface ContextMenuOptions<T> {
5+
export interface ContextMenuOptions<T> {
66
x: number;
77
y: number;
88
orientation?: "left";
@@ -28,6 +28,7 @@ export interface MenuCommandItem<T> {
2828
items?: MenuItem<T>[] | null;
2929
shortcut?: string;
3030
spellingSuggestion?: string;
31+
checked?: boolean;
3132
}
3233

3334
export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem;
@@ -146,10 +147,13 @@ class ContextMenu {
146147
} else {
147148
const $icon = $("<span>");
148149

149-
if ("uiIcon" in item && item.uiIcon) {
150-
$icon.addClass(item.uiIcon);
151-
} else {
152-
$icon.append("&nbsp;");
150+
if ("uiIcon" in item || "checked" in item) {
151+
const icon = (item.checked ? "bx bx-check" : item.uiIcon);
152+
if (icon) {
153+
$icon.addClass(icon);
154+
} else {
155+
$icon.append("&nbsp;");
156+
}
153157
}
154158

155159
const $link = $("<span>")

apps/client/src/services/attributes.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ import froca from "./froca.js";
33
import type FNote from "../entities/fnote.js";
44
import type { AttributeRow } from "./load_results.js";
55

6-
async function addLabel(noteId: string, name: string, value: string = "") {
6+
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
77
await server.put(`notes/${noteId}/attribute`, {
88
type: "label",
99
name: name,
10-
value: value
10+
value: value,
11+
isInheritable
1112
});
1213
}
1314

14-
async function setLabel(noteId: string, name: string, value: string = "") {
15+
export async function setLabel(noteId: string, name: string, value: string = "") {
1516
await server.put(`notes/${noteId}/set-attribute`, {
1617
type: "label",
1718
name: name,
@@ -49,7 +50,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
4950
* @param name the name of the attribute to set.
5051
* @param value the value of the attribute to set.
5152
*/
52-
async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
53+
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
5354
if (value) {
5455
// Create or update the attribute.
5556
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });

apps/client/src/services/content_renderer.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,17 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HT
118118
async function renderCode(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
119119
const blob = await note.getBlob();
120120

121+
let content = blob?.content || "";
122+
if (note.mime === "application/json") {
123+
try {
124+
content = JSON.stringify(JSON.parse(content), null, 4);
125+
} catch (e) {
126+
// Ignore JSON parsing errors.
127+
}
128+
}
129+
121130
const $codeBlock = $("<code>");
122-
$codeBlock.text(blob?.content || "");
131+
$codeBlock.text(content);
123132
$renderedContent.append($("<pre>").append($codeBlock));
124133
await applySingleBlockSyntaxHighlight($codeBlock, normalizeMimeTypeForCKEditor(note.mime));
125134
}
@@ -301,7 +310,7 @@ function getRenderingType(entity: FNote | FAttachment) {
301310

302311
if (type === "file" && mime === "application/pdf") {
303312
type = "pdf";
304-
} else if (type === "file" && mime && CODE_MIME_TYPES.has(mime)) {
313+
} else if ((type === "file" || type === "viewConfig") && mime && CODE_MIME_TYPES.has(mime)) {
305314
type = "code";
306315
} else if (type === "file" && mime && mime.startsWith("audio/")) {
307316
type = "audio";

apps/client/src/services/link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ function linkContextMenu(e: PointerEvent) {
384384
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
385385
}
386386

387-
async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
387+
export async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
388388
const $link = $el[0].tagName === "A" ? $el : $el.find("a");
389389

390390
href = href || $link.attr("href");

apps/client/src/services/note_list_renderer.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
import type FNote from "../entities/fnote.js";
22
import CalendarView from "../widgets/view_widgets/calendar_view.js";
33
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
4+
import TableView from "../widgets/view_widgets/table_view/index.js";
45
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
56
import type ViewMode from "../widgets/view_widgets/view_mode.js";
67

7-
export type ViewTypeOptions = "list" | "grid" | "calendar";
8+
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table";
89

910
export default class NoteListRenderer {
1011

1112
private viewType: ViewTypeOptions;
12-
public viewMode: ViewMode | null;
13-
14-
constructor($parent: JQuery<HTMLElement>, parentNote: FNote, noteIds: string[], showNotePath: boolean = false) {
15-
this.viewType = this.#getViewType(parentNote);
16-
const args: ViewModeArgs = {
17-
$parent,
18-
parentNote,
19-
noteIds,
20-
showNotePath
21-
};
22-
23-
if (this.viewType === "list" || this.viewType === "grid") {
24-
this.viewMode = new ListOrGridView(this.viewType, args);
25-
} else if (this.viewType === "calendar") {
26-
this.viewMode = new CalendarView(args);
27-
} else {
28-
this.viewMode = null;
13+
public viewMode: ViewMode<any> | null;
14+
15+
constructor(args: ViewModeArgs) {
16+
this.viewType = this.#getViewType(args.parentNote);
17+
18+
switch (this.viewType) {
19+
case "list":
20+
case "grid":
21+
this.viewMode = new ListOrGridView(this.viewType, args);
22+
break;
23+
case "calendar":
24+
this.viewMode = new CalendarView(args);
25+
break;
26+
case "table":
27+
this.viewMode = new TableView(args);
28+
break;
29+
default:
30+
this.viewMode = null;
2931
}
3032
}
3133

3234
#getViewType(parentNote: FNote): ViewTypeOptions {
3335
const viewType = parentNote.getLabelValue("viewType");
3436

35-
if (!["list", "grid", "calendar"].includes(viewType || "")) {
37+
if (!["list", "grid", "calendar", "table"].includes(viewType || "")) {
3638
// when not explicitly set, decide based on the note type
3739
return parentNote.type === "search" ? "list" : "grid";
3840
} else {

apps/client/src/services/note_tooltip.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ let dismissTimer: ReturnType<typeof setTimeout>;
1414

1515
function setupGlobalTooltip() {
1616
$(document).on("mouseenter", "a", mouseEnterHandler);
17+
$(document).on("mouseenter", "[data-href]", mouseEnterHandler);
1718

1819
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
1920
$(document).on("click", (e) => {

apps/client/src/services/promoted_attribute_definition_parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
1+
export type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
22
type Multiplicity = "single" | "multi";
33

44
export interface DefinitionObject {

0 commit comments

Comments
 (0)