feat(layout): edited notes underneath title details

This commit is contained in:
Elian Doran 2025-12-14 19:36:53 +02:00
parent 8e6ea87754
commit 9d7e2855d3
No known key found for this signature in database
7 changed files with 89 additions and 23 deletions

View File

@ -1758,7 +1758,8 @@
"note_type_switcher_label": "Switch from {{type}} to:", "note_type_switcher_label": "Switch from {{type}} to:",
"note_type_switcher_others": "More note types", "note_type_switcher_others": "More note types",
"note_type_switcher_templates": "Templates", "note_type_switcher_templates": "Templates",
"note_type_switcher_collection": "Collections" "note_type_switcher_collection": "Collections",
"edited_notes": "Edited notes"
}, },
"search_result": { "search_result": {
"no_notes_found": "No notes have been found for given search parameters.", "no_notes_found": "No notes have been found for given search parameters.",

View File

@ -89,3 +89,21 @@ body.prefers-centered-content .inline-title {
flex-shrink: 0; flex-shrink: 0;
} }
} }
.edited-notes {
padding-top: 1em;
display: flex;
flex-wrap: wrap;
gap: 0.3em;
.badge {
margin: 0;
a.tn-link {
color: inherit;
text-transform: none;
text-decoration: none;
display: inline-block;
}
}
}

View File

@ -16,10 +16,12 @@ import server from "../../services/server";
import { formatDateTime } from "../../utils/formatters"; import { formatDateTime } from "../../utils/formatters";
import NoteIcon from "../note_icon"; import NoteIcon from "../note_icon";
import NoteTitleWidget from "../note_title"; import NoteTitleWidget from "../note_title";
import { Badge, BadgeWithDropdown } from "../react/Badge"; import SimpleBadge, { Badge, BadgeWithDropdown } from "../react/Badge";
import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteBlob, useNoteContext, useNoteProperty, useStaticTooltip, useTriliumEvent } from "../react/hooks"; import { useNoteBlob, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip, useTriliumEvent } from "../react/hooks";
import NoteLink from "../react/NoteLink";
import { joinElements } from "../react/react_utils"; import { joinElements } from "../react/react_utils";
import { useEditedNotes } from "../ribbon/EditedNotesTab";
import { useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useNoteMetadata } from "../ribbon/NoteInfoTab";
import { onWheelHorizontalScroll } from "../widget_utils"; import { onWheelHorizontalScroll } from "../widget_utils";
@ -72,6 +74,7 @@ export default function InlineTitle() {
</div> </div>
<NoteTitleDetails /> <NoteTitleDetails />
<EditedNotes />
<NoteTypeSwitcher /> <NoteTypeSwitcher />
</div> </div>
); );
@ -301,3 +304,38 @@ function useBuiltinTemplates() {
return templates; return templates;
} }
//#endregion //#endregion
//#region Edited Notes
function EditedNotes() {
const { note } = useNoteContext();
const [ dateNote ] = useNoteLabel(note, "dateNote");
return (note && dateNote &&
<div className="edited-notes">
<EditedNotesContent note={note} />
</div>
);
}
function EditedNotesContent({ note }: { note: FNote }) {
const editedNotes = useEditedNotes(note);
return (
<>
<strong>{t("note_title.edited_notes")}</strong><br />
{editedNotes?.map(editedNote => (
<SimpleBadge
key={editedNote.noteId}
title={(
<NoteLink
notePath={editedNote.noteId}
showNoteIcon
/>
)}
/>
))}
</>
);
}
//#endregion

View File

@ -10,7 +10,7 @@ import Icon from "./Icon";
interface SimpleBadgeProps { interface SimpleBadgeProps {
className?: string; className?: string;
title: string; title: ComponentChildren;
} }
interface BadgeProps { interface BadgeProps {

View File

@ -1,24 +1,16 @@
import { useEffect, useState } from "preact/hooks";
import { TabContext } from "./ribbon-interface";
import { EditedNotesResponse } from "@triliumnext/commons"; import { EditedNotesResponse } from "@triliumnext/commons";
import server from "../../services/server"; import { useEffect, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import FNote from "../../entities/fnote";
import froca from "../../services/froca"; import froca from "../../services/froca";
import { t } from "../../services/i18n";
import server from "../../services/server";
import NoteLink from "../react/NoteLink"; import NoteLink from "../react/NoteLink";
import { joinElements } from "../react/react_utils"; import { joinElements } from "../react/react_utils";
import { TabContext } from "./ribbon-interface";
export default function EditedNotesTab({ note }: TabContext) { export default function EditedNotesTab({ note }: TabContext) {
const [ editedNotes, setEditedNotes ] = useState<EditedNotesResponse>(); const editedNotes = useEditedNotes(note);
useEffect(() => {
if (!note) return;
server.get<EditedNotesResponse>(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => {
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
const noteIds = editedNotes.flatMap((n) => n.noteId);
await froca.getNotes(noteIds, true); // preload all at once
setEditedNotes(editedNotes);
});
}, [ note?.noteId ]);
return ( return (
<div className="edited-notes-widget" style={{ <div className="edited-notes-widget" style={{
@ -31,7 +23,7 @@ export default function EditedNotesTab({ note }: TabContext) {
<div className="edited-notes-list use-tn-links"> <div className="edited-notes-list use-tn-links">
{joinElements(editedNotes.map(editedNote => { {joinElements(editedNotes.map(editedNote => {
return ( return (
<span className="edited-note-line"> <span key={editedNote.noteId} className="edited-note-line">
{editedNote.isDeleted ? ( {editedNote.isDeleted ? (
<i>{`${editedNote.title} ${t("edited_notes.deleted")}`}</i> <i>{`${editedNote.title} ${t("edited_notes.deleted")}`}</i>
) : ( ) : (
@ -40,12 +32,28 @@ export default function EditedNotesTab({ note }: TabContext) {
</> </>
)} )}
</span> </span>
) );
}), " ")} }), " ")}
</div> </div>
) : ( ) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div> <div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)} )}
</div> </div>
) );
}
export function useEditedNotes(note: FNote | null | undefined) {
const [ editedNotes, setEditedNotes ] = useState<EditedNotesResponse>();
useEffect(() => {
if (!note) return;
server.get<EditedNotesResponse>(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => {
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
const noteIds = editedNotes.flatMap((n) => n.noteId);
await froca.getNotes(noteIds, true); // preload all at once
setEditedNotes(editedNotes);
});
}, [ note ]);
return editedNotes;
} }

View File

@ -53,7 +53,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
title: t("edited_notes.title"), title: t("edited_notes.title"),
icon: "bx bx-calendar-edit", icon: "bx bx-calendar-edit",
content: EditedNotesTab, content: EditedNotesTab,
show: ({ note }) => note?.hasOwnedLabel("dateNote"), show: ({ note }) => !isNewLayout && note?.hasOwnedLabel("dateNote"),
activate: () => options.is("editedNotesOpenInRibbon") activate: () => options.is("editedNotesOpenInRibbon")
}, },
{ {

View File

@ -18,6 +18,7 @@ type Labels = {
language: string; language: string;
originalFileName: string; originalFileName: string;
pageUrl: string; pageUrl: string;
dateNote: string;
// Search // Search
searchString: string; searchString: string;