mirror of
https://github.com/zadam/trilium.git
synced 2026-02-16 11:26:55 +01:00
Compare commits
38 Commits
copilot/sw
...
feat/ui/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea25477e3d | ||
|
|
82576c9703 | ||
|
|
0b8cba78d5 | ||
|
|
d8806eaa04 | ||
|
|
b8bc85856b | ||
|
|
7551d0e044 | ||
|
|
489a88b8da | ||
|
|
48013dc264 | ||
|
|
4ee9d45dfc | ||
|
|
e00150a876 | ||
|
|
71668f8f8d | ||
|
|
b71424d239 | ||
|
|
d1a3bceaa6 | ||
|
|
483c57029a | ||
|
|
7e6daf5b36 | ||
|
|
b658253687 | ||
|
|
80b488deec | ||
|
|
10cf1a371e | ||
|
|
f645d9d721 | ||
|
|
900bfdff9d | ||
|
|
108ca5afb5 | ||
|
|
3df03a551c | ||
|
|
6ffbe19667 | ||
|
|
fea8de89c6 | ||
|
|
4afbabb977 | ||
|
|
b2ebaf111f | ||
|
|
9c2b01e3c9 | ||
|
|
dca201ce42 | ||
|
|
3774ea3768 | ||
|
|
0a9c6a3119 | ||
|
|
a6cbde88bb | ||
|
|
9c13f36ca0 | ||
|
|
fe4a11c5ad | ||
|
|
218343ca14 | ||
|
|
61953fd713 | ||
|
|
62ddf3a11b | ||
|
|
be12658864 | ||
|
|
b618e5a00f |
@@ -291,6 +291,15 @@
|
||||
--ck-editor-toolbar-button-on-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
|
||||
--ck-editor-toolbar-dropdown-button-open-background: #ffffff14;
|
||||
|
||||
--note-list-view-icon-color: var(--left-pane-icon-color);
|
||||
--note-list-view-large-icon-background: var(--note-icon-background-color);
|
||||
--note-list-view-large-icon-color: var(--note-icon-color);
|
||||
--note-list-view-search-result-highlight-background: transparent;
|
||||
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
|
||||
--note-list-view-content-background: rgba(0, 0, 0, .2);
|
||||
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
|
||||
--note-list-view-content-search-result-highlight-color: black;
|
||||
|
||||
--calendar-coll-event-background-saturation: 25%;
|
||||
--calendar-coll-event-background-lightness: 20%;
|
||||
--calendar-coll-event-background-color: #3c3c3c;
|
||||
@@ -304,7 +313,8 @@
|
||||
* Dark color scheme tweaks
|
||||
*/
|
||||
|
||||
#left-pane .fancytree-node.tinted {
|
||||
#left-pane .fancytree-node.tinted,
|
||||
.nested-note-list-item.use-note-color {
|
||||
--custom-color: var(--dark-theme-custom-color);
|
||||
|
||||
/* The background color of the active item in the note tree.
|
||||
@@ -354,7 +364,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
|
||||
}
|
||||
|
||||
.note-split.with-hue,
|
||||
.quick-edit-dialog-wrapper.with-hue {
|
||||
.quick-edit-dialog-wrapper.with-hue,
|
||||
.nested-note-list-item.with-hue {
|
||||
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
|
||||
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
|
||||
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);
|
||||
|
||||
@@ -289,6 +289,15 @@
|
||||
--ck-editor-toolbar-button-on-shadow: none;
|
||||
--ck-editor-toolbar-dropdown-button-open-background: #0000000f;
|
||||
|
||||
--note-list-view-icon-color: var(--left-pane-icon-color);
|
||||
--note-list-view-large-icon-background: var(--note-icon-background-color);
|
||||
--note-list-view-large-icon-color: var(--note-icon-color);
|
||||
--note-list-view-search-result-highlight-background: transparent;
|
||||
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
|
||||
--note-list-view-content-background: #b1b1b133;
|
||||
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
|
||||
--note-list-view-content-search-result-highlight-color: white;
|
||||
|
||||
--calendar-coll-event-background-lightness: 95%;
|
||||
--calendar-coll-event-background-saturation: 80%;
|
||||
--calendar-coll-event-background-color: #eaeaea;
|
||||
@@ -298,7 +307,8 @@
|
||||
--calendar-coll-today-background-color: #00000006;
|
||||
}
|
||||
|
||||
#left-pane .fancytree-node.tinted {
|
||||
#left-pane .fancytree-node.tinted,
|
||||
.nested-note-list-item.use-note-color {
|
||||
--custom-color: var(--light-theme-custom-color);
|
||||
|
||||
/* The background color of the active item in the note tree.
|
||||
@@ -324,7 +334,8 @@
|
||||
}
|
||||
|
||||
.note-split.with-hue,
|
||||
.quick-edit-dialog-wrapper.with-hue {
|
||||
.quick-edit-dialog-wrapper.with-hue,
|
||||
.nested-note-list-item.with-hue {
|
||||
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%);
|
||||
--note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%);
|
||||
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%);
|
||||
|
||||
@@ -751,12 +751,14 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
|
||||
}
|
||||
}
|
||||
|
||||
#left-pane .fancytree-expander {
|
||||
#left-pane .fancytree-expander,
|
||||
.nested-note-list-item .note-expander {
|
||||
opacity: 0.65;
|
||||
transition: opacity 150ms ease-in;
|
||||
}
|
||||
|
||||
#left-pane .fancytree-expander:hover {
|
||||
#left-pane .fancytree-expander:hover,
|
||||
.nested-note-list-item .note-expander:hover {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease-out;
|
||||
}
|
||||
|
||||
@@ -2190,8 +2190,9 @@
|
||||
"percentage": "%"
|
||||
},
|
||||
"pagination": {
|
||||
"page_title": "Page of {{startIndex}} - {{endIndex}}",
|
||||
"total_notes": "{{count}} notes"
|
||||
"total_notes": "{{count}} notes",
|
||||
"prev_page": "Previous page",
|
||||
"next_page": "Next page"
|
||||
},
|
||||
"collections": {
|
||||
"rendering_error": "Unable to show content due to an error."
|
||||
|
||||
@@ -19,14 +19,3 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
|
||||
.note-list-widget video {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* #region Pagination */
|
||||
.note-list-pager {
|
||||
font-size: 1rem;
|
||||
|
||||
span.current-page {
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
@@ -4,6 +4,8 @@ import FNote from "../../entities/fnote";
|
||||
import froca from "../../services/froca";
|
||||
import { useNoteLabelInt } from "../react/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import Button from "../react/Button";
|
||||
|
||||
interface PaginationContext {
|
||||
page: number;
|
||||
@@ -17,46 +19,79 @@ interface PaginationContext {
|
||||
export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<PaginationContext, "pageNotes">) {
|
||||
if (pageCount < 2) return;
|
||||
|
||||
let lastPrinted = false;
|
||||
let children: ComponentChildren[] = [];
|
||||
for (let i = 1; i <= pageCount; i++) {
|
||||
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(page - i) <= 2) {
|
||||
lastPrinted = true;
|
||||
|
||||
const startIndex = (i - 1) * pageSize + 1;
|
||||
const endIndex = Math.min(totalNotes, i * pageSize);
|
||||
|
||||
if (i !== page) {
|
||||
children.push((
|
||||
<a
|
||||
href="javascript:"
|
||||
title={t("pagination.page_title", { startIndex, endIndex })}
|
||||
onClick={() => setPage(i)}
|
||||
>
|
||||
{i}
|
||||
</a>
|
||||
))
|
||||
} else {
|
||||
// Current page
|
||||
children.push(<span className="current-page">{i}</span>)
|
||||
}
|
||||
|
||||
children.push(<>{" "} {" "}</>);
|
||||
} else if (lastPrinted) {
|
||||
children.push(<>{"... "} {" "}</>);
|
||||
lastPrinted = false;
|
||||
}
|
||||
}
|
||||
const children = createPageButtons(page, setPage, pageCount);
|
||||
|
||||
return (
|
||||
<div class="note-list-pager">
|
||||
{children}
|
||||
<ActionButton
|
||||
icon="bx bx-caret-left"
|
||||
disabled={(page === 1)}
|
||||
text={t("pagination.prev_page")}
|
||||
onClick={() => {setPage(page - 1)}}
|
||||
/>
|
||||
|
||||
{children}
|
||||
|
||||
<ActionButton
|
||||
icon="bx bx-caret-right"
|
||||
disabled={(page === pageCount)}
|
||||
text={t("pagination.next_page")}
|
||||
onClick={() => {setPage(page + 1)}}
|
||||
/>
|
||||
<span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function createPageButtons(page: number, setPage: Dispatch<StateUpdater<number>>, pageCount: number): ComponentChildren[] {
|
||||
const maxButtonCount = 9;
|
||||
const maxLeftRightSegmentLength = 2;
|
||||
|
||||
// The left-side segment
|
||||
const leftLength = Math.min(pageCount, maxLeftRightSegmentLength);
|
||||
const leftStart = 1;
|
||||
|
||||
// The middle segment
|
||||
const middleMaxLength = maxButtonCount - maxLeftRightSegmentLength * 2;
|
||||
const middleLength = Math.min(pageCount - leftLength, middleMaxLength);
|
||||
let middleStart = page - Math.floor(middleLength / 2);
|
||||
middleStart = Math.max(middleStart, leftLength + 1);
|
||||
|
||||
// The right-side segment
|
||||
const rightLength = Math.min(pageCount - (middleLength + leftLength), maxLeftRightSegmentLength);
|
||||
const rightStart = pageCount - rightLength + 1;
|
||||
middleStart = Math.min(middleStart, rightStart - middleLength);
|
||||
|
||||
return [
|
||||
...createSegment(leftStart, leftLength, page, setPage, false),
|
||||
...createSegment(middleStart, middleLength, page, setPage, (middleStart - leftLength > 1)),
|
||||
...createSegment(rightStart, rightLength, page, setPage, (rightStart - (middleStart + middleLength - 1) > 1)),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
function createSegment(start: number, length: number, currentPage: number, setPage: Dispatch<StateUpdater<number>>, prependEllipsis: boolean): ComponentChildren[] {
|
||||
const children: ComponentChildren[] = [];
|
||||
|
||||
if (prependEllipsis) {
|
||||
children.push("...");
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const pageNum = start + i;
|
||||
|
||||
children.push((
|
||||
<Button
|
||||
text={pageNum.toString()}
|
||||
disabled={(pageNum === currentPage)}
|
||||
onClick={() => setPage(pageNum)}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export function usePagination(note: FNote, noteIds: string[]): PaginationContext {
|
||||
const [ page, setPage ] = useState(1);
|
||||
const [ pageNotes, setPageNotes ] = useState<FNote[]>();
|
||||
|
||||
@@ -100,23 +100,206 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.note-expander {
|
||||
font-size: x-large;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
cursor: pointer;
|
||||
/* #region List view */
|
||||
|
||||
@keyframes note-preview-show {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.note-list-pager {
|
||||
text-align: center;
|
||||
.nested-note-list {
|
||||
--card-nested-section-indent: 25px;
|
||||
|
||||
&.search-results {
|
||||
--card-nested-section-indent: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-list.list-view .note-path {
|
||||
margin-left: 0.5em;
|
||||
vertical-align: middle;
|
||||
opacity: 0.5;
|
||||
/* List item */
|
||||
.nested-note-list-item {
|
||||
h5 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.note-expander {
|
||||
margin-inline-end: 4px;
|
||||
font-size: x-large;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tn-icon {
|
||||
margin-inline-end: 8px;
|
||||
color: var(--note-list-view-icon-color);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.note-book-title {
|
||||
--link-hover-background: transparent;
|
||||
--link-hover-color: currentColor;
|
||||
color: inherit;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.note-path {
|
||||
margin-left: 0.5em;
|
||||
vertical-align: middle;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.note-list-attributes {
|
||||
flex-grow: 1;
|
||||
margin-inline-start: 1em;
|
||||
text-align: right;
|
||||
font-size: .75em;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.nested-note-list-item-menu {
|
||||
margin-inline-start: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.archived {
|
||||
span.tn-icon + span,
|
||||
.tn-icon {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
&.use-note-color {
|
||||
span.tn-icon + span,
|
||||
.nested-note-list:not(.search-results) & .tn-icon,
|
||||
.rendered-note-attributes {
|
||||
color: var(--custom-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nested-note-list:not(.search-results) h5 {
|
||||
span.tn-icon + span,
|
||||
.note-list-attributes {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
/* List item (search results view) */
|
||||
.nested-note-list.search-results .nested-note-list-item {
|
||||
span.tn-icon + span > span {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
small {
|
||||
line-height: .85em;
|
||||
}
|
||||
|
||||
.note-path {
|
||||
margin-left: 0;
|
||||
font-size: .85em;
|
||||
line-height: .85em;
|
||||
font-weight: 500;
|
||||
letter-spacing: .5pt;
|
||||
}
|
||||
|
||||
.tn-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1.75em;
|
||||
height: 1.75em;
|
||||
margin-inline-end: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--note-icon-custom-background-color, var(--note-list-view-large-icon-background));
|
||||
font-size: 1.2em;
|
||||
color: var(--note-icon-custom-color, var(--note-list-view-large-icon-color));
|
||||
}
|
||||
|
||||
h5 .ck-find-result {
|
||||
background: var(--note-list-view-search-result-highlight-background);
|
||||
color: var(--note-list-view-search-result-highlight-color);
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note content preview */
|
||||
.nested-note-list .note-book-content {
|
||||
display: none;
|
||||
outline: 1px solid var(--note-list-view-content-background);
|
||||
border-radius: 8px;
|
||||
background-color: var(--note-list-view-content-background);
|
||||
overflow: hidden;
|
||||
user-select: text;
|
||||
font-size: .85rem;
|
||||
animation: note-preview-show .25s ease-out;
|
||||
will-change: opacity;
|
||||
|
||||
&.note-book-content-ready {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> .rendered-content > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.type-text {
|
||||
padding: 8px 24px;
|
||||
|
||||
.ck-content > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.type-protectedSession {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
&.type-image {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.type-pdf {
|
||||
iframe {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.file-footer {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&.type-webView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.ck-find-result {
|
||||
outline: 2px solid var(--note-list-view-content-search-result-highlight-background);
|
||||
border-radius: 4px;
|
||||
background: var(--note-list-view-content-search-result-highlight-background);
|
||||
color: var(--note-list-view-content-search-result-highlight-color);
|
||||
}
|
||||
}
|
||||
|
||||
.note-content-preview:has(.note-book-content:empty) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region Grid view */
|
||||
.note-list.grid-view .note-list-container {
|
||||
display: flex;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "./ListOrGridView.css";
|
||||
import { Card, CardSection } from "../../react/Card";
|
||||
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -14,6 +15,11 @@ import NoteLink from "../../react/NoteLink";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import { Pager, usePagination } from "../Pagination";
|
||||
import { filterChildNotes, useFilteredNoteIds } from "./utils";
|
||||
import { JSX } from "preact/jsx-runtime";
|
||||
import { clsx } from "clsx";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import linkContextMenuService from "../../../menus/link_context_menu";
|
||||
import { TargetedMouseEvent } from "preact";
|
||||
|
||||
export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
|
||||
const expandDepth = useExpansionDepth(note);
|
||||
@@ -33,7 +39,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
|
||||
{ noteIds.length > 0 && <div class="note-list-wrapper">
|
||||
{!hasCollectionProperties && <Pager {...pagination} />}
|
||||
|
||||
<div class="note-list-container use-tn-links">
|
||||
<Card className={clsx("nested-note-list", {"search-results": (noteType === "search")})}>
|
||||
{pageNotes?.map(childNote => (
|
||||
<ListNoteCard
|
||||
key={childNote.noteId}
|
||||
@@ -41,7 +47,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
|
||||
expandDepth={expandDepth} highlightedTokens={highlightedTokens}
|
||||
currentLevel={1} includeArchived={includeArchived} />
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Pager {...pagination} />
|
||||
</div>}
|
||||
@@ -93,27 +99,52 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
|
||||
// Reset expand state if switching to another note, or if user manually toggled expansion state.
|
||||
useEffect(() => setExpanded(currentLevel <= expandDepth), [ note, currentLevel, expandDepth ]);
|
||||
|
||||
let subSections: JSX.Element | undefined = undefined;
|
||||
if (isExpanded) {
|
||||
subSections = <>
|
||||
<CardSection className="note-content-preview">
|
||||
<NoteContent note={note}
|
||||
highlightedTokens={highlightedTokens}
|
||||
noChildrenList
|
||||
includeArchivedNotes={includeArchived} />
|
||||
</CardSection>
|
||||
|
||||
<NoteChildren note={note}
|
||||
parentNote={parentNote}
|
||||
highlightedTokens={highlightedTokens}
|
||||
currentLevel={currentLevel}
|
||||
expandDepth={expandDepth}
|
||||
includeArchived={includeArchived} />
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""} ${note.isArchived ? "archived" : ""}`}
|
||||
<CardSection
|
||||
className={clsx("nested-note-list-item", "no-tooltip-preview", note.getColorClass(), {
|
||||
"expanded": isExpanded,
|
||||
"archived": note.isArchived
|
||||
})}
|
||||
subSections={subSections}
|
||||
subSectionsVisible={isExpanded}
|
||||
highlightOnHover
|
||||
data-note-id={note.noteId}
|
||||
>
|
||||
<h5 className="note-book-header">
|
||||
<span
|
||||
className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
|
||||
onClick={() => setExpanded(!isExpanded)}
|
||||
/>
|
||||
|
||||
<h5>
|
||||
<span className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
|
||||
onClick={() => setExpanded(!isExpanded)}/>
|
||||
<Icon className="note-icon" icon={note.getIcon()} />
|
||||
<NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={parentNote.type === "search"} highlightedTokens={highlightedTokens} />
|
||||
<NoteLink className="note-book-title"
|
||||
notePath={notePath}
|
||||
noPreview
|
||||
showNotePath={parentNote.type === "search"}
|
||||
highlightedTokens={highlightedTokens} />
|
||||
<NoteAttributes note={note} />
|
||||
<ActionButton className="nested-note-list-item-menu"
|
||||
icon="bx bx-dots-vertical-rounded" text=""
|
||||
onClick={(e) => openNoteMenu(notePath, e)}
|
||||
/>
|
||||
</h5>
|
||||
|
||||
{isExpanded && <>
|
||||
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
|
||||
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
|
||||
</>}
|
||||
</div>
|
||||
</CardSection>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,6 +196,9 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
|
||||
|
||||
const [ready, setReady] = useState(false);
|
||||
const [noteType, setNoteType] = useState<string>("none");
|
||||
|
||||
useEffect(() => {
|
||||
content_renderer.getRenderedContent(note, {
|
||||
trim,
|
||||
@@ -179,17 +213,19 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
|
||||
} else {
|
||||
contentRef.current.replaceChildren();
|
||||
}
|
||||
contentRef.current.classList.add(`type-${type}`);
|
||||
highlightSearch(contentRef.current);
|
||||
setNoteType(type);
|
||||
setReady(true);
|
||||
})
|
||||
.catch(e => {
|
||||
console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
|
||||
console.error(e);
|
||||
contentRef.current?.replaceChildren(t("collections.rendering_error"));
|
||||
setReady(true);
|
||||
});
|
||||
}, [ note, highlightedTokens ]);
|
||||
|
||||
return <div ref={contentRef} className="note-book-content" />;
|
||||
return <div ref={contentRef} className={clsx("note-book-content", `type-${noteType}`, {"note-book-content-ready": ready})} />;
|
||||
}
|
||||
|
||||
function NoteChildren({ note, parentNote, highlightedTokens, currentLevel, expandDepth, includeArchived }: {
|
||||
@@ -238,3 +274,8 @@ function useExpansionDepth(note: FNote) {
|
||||
return parseInt(expandDepth, 10);
|
||||
|
||||
}
|
||||
|
||||
function openNoteMenu(notePath, e: TargetedMouseEvent<HTMLElement>) {
|
||||
linkContextMenuService.openContextMenu(notePath, e);
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
47
apps/client/src/widgets/react/Card.css
Normal file
47
apps/client/src/widgets/react/Card.css
Normal file
@@ -0,0 +1,47 @@
|
||||
:where(.tn-card) {
|
||||
--card-border-radius: 8px;
|
||||
--card-padding-block: 8px;
|
||||
--card-padding-inline: 16px;
|
||||
--card-section-gap: 3px;
|
||||
--card-nested-section-indent: 30px;
|
||||
}
|
||||
|
||||
.tn-card-heading {
|
||||
margin-bottom: 10px;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .4pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tn-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--card-section-gap);
|
||||
|
||||
.tn-card-section {
|
||||
padding: var(--card-padding-block) var(--card-padding-inline);
|
||||
border: 1px solid var(--card-border-color, var(--main-border-color));
|
||||
background: var(--card-background-color);
|
||||
|
||||
&:first-of-type {
|
||||
border-top-left-radius: var(--card-border-radius);
|
||||
border-top-right-radius: var(--card-border-radius);
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom-left-radius: var(--card-border-radius);
|
||||
border-bottom-right-radius: var(--card-border-radius);
|
||||
}
|
||||
|
||||
&.tn-card-section-nested {
|
||||
padding-left: calc(var(--card-padding-inline) + var(--card-nested-section-indent) * var(--tn-card-section-nesting-level));
|
||||
background-color: color-mix(in srgb, var(--card-background-color) calc(100% / (var(--tn-card-section-nesting-level) + 1)) , transparent);
|
||||
}
|
||||
|
||||
&.tn-card-section-highlight-on-hover:hover {
|
||||
background-color: var(--card-background-hover-color);
|
||||
transition: background-color .2s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
apps/client/src/widgets/react/Card.tsx
Normal file
63
apps/client/src/widgets/react/Card.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import "./Card.css";
|
||||
import { ComponentChildren, createContext } from "preact";
|
||||
import { JSX } from "preact";
|
||||
import { useContext } from "preact/hooks";
|
||||
import clsx from "clsx";
|
||||
|
||||
// #region Card
|
||||
|
||||
export interface CardProps {
|
||||
className?: string;
|
||||
heading?: string;
|
||||
}
|
||||
|
||||
export function Card(props: {children: ComponentChildren} & CardProps) {
|
||||
return <div className={clsx("tn-card", props.className)}>
|
||||
{props.heading && <h5 class="tn-card-heading">{props.heading}</h5>}
|
||||
<div className="tn-card-body">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Card Section
|
||||
|
||||
export interface CardSectionProps {
|
||||
className?: string;
|
||||
subSections?: JSX.Element | JSX.Element[];
|
||||
subSectionsVisible?: boolean;
|
||||
highlightOnHover?: boolean;
|
||||
onAction?: () => void;
|
||||
}
|
||||
|
||||
interface CardSectionContextType {
|
||||
nestingLevel: number;
|
||||
}
|
||||
|
||||
const CardSectionContext = createContext<CardSectionContextType | undefined>(undefined);
|
||||
|
||||
export function CardSection(props: {children: ComponentChildren} & CardSectionProps) {
|
||||
const parentContext = useContext(CardSectionContext);
|
||||
const nestingLevel = (parentContext && parentContext.nestingLevel + 1) ?? 0;
|
||||
|
||||
return <>
|
||||
<section className={clsx("tn-card-section", props.className, {
|
||||
"tn-card-section-nested": nestingLevel > 0,
|
||||
"tn-card-section-highlight-on-hover": props.highlightOnHover || props.onAction
|
||||
})}
|
||||
style={{"--tn-card-section-nesting-level": (nestingLevel) ? nestingLevel : null}}
|
||||
onClick={props.onAction}>
|
||||
{props.children}
|
||||
</section>
|
||||
|
||||
{props.subSectionsVisible && props.subSections &&
|
||||
<CardSectionContext.Provider value={{nestingLevel}}>
|
||||
{props.subSections}
|
||||
</CardSectionContext.Provider>
|
||||
}
|
||||
</>;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
Reference in New Issue
Block a user