diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 6a089f0ed3..dfdc209040 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -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%); diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index ac50453d09..4b6b37718c 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -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%); diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 0df9d6686a..1ac2f13e77 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -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; } diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.css b/apps/client/src/widgets/collections/legacy/ListOrGridView.css index 722e747ef0..d0b3a7e773 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.css +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.css @@ -100,23 +100,210 @@ overflow: auto; } -.note-expander { - font-size: x-large; - position: relative; - top: 3px; - cursor: pointer; -} - .note-list-pager { text-align: center; } -.note-list.list-view .note-path { - margin-left: 0.5em; - vertical-align: middle; - opacity: 0.5; +/* #region List view */ + +@keyframes note-preview-show { + from { + opacity: 0; + } to { + opacity: 1; + } } +.nested-note-list { + --card-nested-section-indent: 25px; + + &.search-results { + --card-nested-section-indent: 32px; + } +} + +/* 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; diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx index 61c7193a6e..cdde8a6087 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx @@ -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 &&
{!hasCollectionProperties && } - +
} @@ -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 = <> + + + + + + + } + return ( -
-
- setExpanded(!isExpanded)} - /> - +
+ setExpanded(!isExpanded)}/> - + + openNoteMenu(notePath, e)} + />
- - {isExpanded && <> - - - } -
+ ); } @@ -165,6 +196,9 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc const contentRef = useRef(null); const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens); + const [ready, setReady] = useState(false); + const [noteType, setNoteType] = useState("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
; + return
; } 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) { + linkContextMenuService.openContextMenu(notePath, e); + e.stopPropagation() +} diff --git a/apps/client/src/widgets/react/Card.css b/apps/client/src/widgets/react/Card.css new file mode 100644 index 0000000000..ce6bf6dc61 --- /dev/null +++ b/apps/client/src/widgets/react/Card.css @@ -0,0 +1,40 @@ +: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 { + 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; + } + + } +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/Card.tsx b/apps/client/src/widgets/react/Card.tsx new file mode 100644 index 0000000000..26b52040be --- /dev/null +++ b/apps/client/src/widgets/react/Card.tsx @@ -0,0 +1,51 @@ +import "./Card.css"; +import { ComponentChildren, createContext } from "preact"; +import { JSX } from "preact"; +import { useContext } from "preact/hooks"; +import clsx from "clsx"; + +interface CardProps { + className?: string; +} + +export function Card(props: {children: ComponentChildren} & CardProps) { + return
+ {props.children} +
; +} + +interface CardSectionProps { + className?: string; + subSections?: JSX.Element | JSX.Element[]; + subSectionsVisible?: boolean; + highlightOnHover?: boolean; + onAction?: () => void; +} + +export function CardSection(props: {children: ComponentChildren} & CardSectionProps) { + const parentContext = useContext(CardSectionContext); + const nestingLevel = (parentContext && parentContext.nestingLevel + 1) ?? 0; + + return <> +
0, + "tn-card-section-highlight-on-hover": props.highlightOnHover || props.onAction + })} + style={{"--tn-card-section-nesting-level": nestingLevel}} + onClick={props.onAction}> + {props.children} +
+ + {props.subSectionsVisible && + + {props.subSections} + + } + ; +} + +interface CardSectionContextType { + nestingLevel: number; +} + +export const CardSectionContext = createContext(undefined); \ No newline at end of file