diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index e3415bf4b..d50486750 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -52,6 +52,7 @@ import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.jsx"; import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx"; import BreadcrumbBadges from "../widgets/BreadcrumbBadges.jsx"; import NoteTitleDetails from "../widgets/NoteTitleDetails.jsx"; +import NoteStatusBar from "../widgets/NoteStatusBar.jsx"; export default class DesktopLayout { @@ -176,7 +177,11 @@ export default class DesktopLayout { ...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC ...this.customWidgets.get("note-detail-pane") ) - .optChild(isNewLayout, ) + .optChild(isNewLayout, ( + + + + )) ) ) .child(...this.customWidgets.get("center-pane")) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 76cca8461..5f7e5d010 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -27,6 +27,16 @@ export function getEnabledExperimentalFeatureIds() { return getEnabledFeatures().values(); } +export async function toggleExperimentalFeature(featureId: ExperimentalFeatureId, enable: boolean) { + const features = new Set(getEnabledFeatures()); + if (enable) { + features.add(featureId); + } else { + features.delete(featureId); + } + await options.save("experimentalFeatures", JSON.stringify(Array.from(features))); +} + function getEnabledFeatures() { if (!enabledFeatures) { let features: ExperimentalFeatureId[] = []; diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 871b84bdc..0a1971f11 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -423,16 +423,16 @@ body.desktop .tabulator-popup-container, pointer-events: none; } -.dropdown-menu .disabled .disabled-tooltip { +.dropdown-menu .disabled .contextual-help { pointer-events: all; margin-inline-start: 8px; font-size: 0.75rem; - color: var(--disabled-tooltip-icon-color); + color: var(--contextual-help-icon-color); cursor: help; opacity: 0.75; } -.dropdown-menu .disabled .disabled-tooltip:hover { +.dropdown-menu .disabled .contextual-help:hover { opacity: 1; } @@ -1315,7 +1315,8 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu { top: 0; inset-inline-start: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */ margin-top: -10px; - min-width: 15rem; + min-width: max-content; + max-width: 300px; /* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */ max-height: 600px; overflow: auto; diff --git a/apps/client/src/stylesheets/theme-dark.css b/apps/client/src/stylesheets/theme-dark.css index 690d49ecd..d4702d6c5 100644 --- a/apps/client/src/stylesheets/theme-dark.css +++ b/apps/client/src/stylesheets/theme-dark.css @@ -19,7 +19,7 @@ --dropdown-border-color: #555; --dropdown-shadow-opacity: 0.4; --dropdown-item-icon-destructive-color: #de6e5b; - --disabled-tooltip-icon-color: #7fd2ef; + --contextual-help-icon-color: #7fd2ef; --accented-background-color: #555; --more-accented-background-color: #777; @@ -114,4 +114,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-light.css b/apps/client/src/stylesheets/theme-light.css index 0c14a2d92..777ccbd61 100644 --- a/apps/client/src/stylesheets/theme-light.css +++ b/apps/client/src/stylesheets/theme-light.css @@ -23,7 +23,7 @@ html { --dropdown-border-color: #ccc; --dropdown-shadow-opacity: 0.2; --dropdown-item-icon-destructive-color: #ec5138; - --disabled-tooltip-icon-color: #004382; + --contextual-help-icon-color: #004382; --accented-background-color: #f5f5f5; --more-accented-background-color: #ddd; @@ -98,4 +98,4 @@ html { .use-note-color { --custom-color: var(--light-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 8358de09b..b4c3def74 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -6,7 +6,7 @@ */ :root { - /* + /* * ⚠️ NOTICE: This theme is currently in the beta stage of development. * The names and purposes of these CSS variables are subject to frequent changes. */ @@ -22,7 +22,7 @@ --dropdown-border-color: #404040; --dropdown-shadow-opacity: 0.6; --dropdown-item-icon-destructive-color: #de6e5b; - --disabled-tooltip-icon-color: #7fd2ef; + --contextual-help-icon-color: #7fd2ef; --accented-background-color: #555; @@ -182,7 +182,7 @@ --tab-close-button-hover-background: #a45353; --tab-close-button-hover-color: white; - + --active-tab-background-color: #ffffff1c; --active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-icon-color: #a9a9a9; @@ -201,7 +201,7 @@ --promoted-attribute-card-background-color: #ffffff21; --promoted-attribute-card-shadow: none; - + --floating-button-shadow-color: #00000080; --floating-button-background-color: #494949d2; --floating-button-color: var(--button-text-color); @@ -226,7 +226,7 @@ --scrollbar-border-color: unset; /* Deprecated */ --selection-background-color: #3399FF70; - + --link-color: lightskyblue; --mermaid-theme: dark; @@ -320,4 +320,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 60e7d55b2..620672499 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -6,7 +6,7 @@ */ :root { - /* + /* * ⚠️ NOTICE: This theme is currently in the beta stage of development. * The names and purposes of these CSS variables are subject to frequent changes. */ @@ -22,7 +22,7 @@ --dropdown-border-color: #ccc; --dropdown-shadow-opacity: 0.2; --dropdown-item-icon-destructive-color: #ec5138; - --disabled-tooltip-icon-color: #004382; + --contextual-help-icon-color: #004382; --accented-background-color: #f5f5f5; @@ -138,7 +138,7 @@ /* Deprecated: now local variables in #launcher, with the values dependent on the current layout. */ --launcher-pane-background-color: unset; --launcher-pane-text-color: unset; - + --launcher-pane-vert-background-color: #e8e8e8; --launcher-pane-vert-text-color: #000000bd; --launcher-pane-vert-button-hover-color: black; @@ -174,7 +174,7 @@ --tab-close-button-hover-background: #c95a5a; --tab-close-button-hover-color: white; - + --active-tab-background-color: white; --active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-icon-color: gray; @@ -291,4 +291,4 @@ --modal-background-color: hsl(var(--custom-color-hue), 56%, 96%); --modal-border-color: hsl(var(--custom-color-hue), 33%, 41%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%); -} \ No newline at end of file +} diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 2298f2f2b..9880e99b5 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1962,8 +1962,9 @@ "unknown_widget": "Unknown widget for \"{{id}}\"." }, "note_language": { - "not_set": "Not set", - "configure-languages": "Configure languages..." + "not_set": "No language set", + "configure-languages": "Configure languages...", + "help-on-languages": "Help on content languages..." }, "content_language": { "title": "Content languages", diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 57377167e..0e3cf986f 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -52,7 +52,7 @@ function ShareBadge() { return (link && + openInAppHelpFromUrl("veGu4faJErEM")} + icon="bx bx-help-circle" + >{t("note_language.help-on-languages")} + )} + /> + + ); +} diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index 12cd870eb..5d7f6d466 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -10,7 +10,8 @@ import { KeyboardActionNames } from "@triliumnext/commons"; import { ComponentChildren } from "preact"; import Component from "../../components/component"; import { ParentComponent } from "../react/react_utils"; -import utils, { dynamicRequire, isElectron, isMobile } from "../../services/utils"; +import utils, { dynamicRequire, isElectron, isMobile, reloadFrontendApp } from "../../services/utils"; +import { isExperimentalFeatureEnabled, toggleExperimentalFeature } from "../../services/experimental_features"; interface MenuItemProps { icon: string, @@ -70,6 +71,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: } {!isElectron() && } + {glob.isDev && } ) } @@ -99,6 +101,21 @@ function BrowserOnlyOptions() { ; } +function DevelopmentOptions() { + const newLayoutEnabled = isExperimentalFeatureEnabled("new-layout"); + + return <> + + { + await toggleExperimentalFeature("new-layout", !newLayoutEnabled); + reloadFrontendApp(); + }} + >{!newLayoutEnabled ? "Switch to new layout" : "Switch to old layout"} + ; +} + function SwitchToOptions() { if (isElectron()) { return; diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css index ae5c3340f..643b5132a 100644 --- a/apps/client/src/widgets/react/FormList.css +++ b/apps/client/src/widgets/react/FormList.css @@ -1,9 +1,29 @@ -.dropdown-item .description { - font-size: small; - color: var(--muted-text-color); - white-space: normal; -} +.dropdown-item { + .description { + font-size: small; + color: var(--muted-text-color); + white-space: normal; + } -.dropdown-item span.bx { - flex-shrink: 0; -} \ No newline at end of file + span.bx { + flex-shrink: 0; + } + + .switch-widget { + flex-grow: 1; + width: 100%; + --switch-track-width: 40px; + --switch-track-height: 20px; + --switch-thumb-width: 12px; + --switch-thumb-height: var(--switch-thumb-width); + + .contextual-help { + margin-inline-start: 0.25em; + cursor: pointer; + } + + .switch-spacer { + flex-grow: 1; + } + } +} diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 0eb6108b8..dd5948cb3 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -5,8 +5,9 @@ import { useEffect, useMemo, useRef, useState, type CSSProperties } from "preact import "./FormList.css"; import { CommandNames } from "../../components/app_context"; import { useStaticTooltip } from "./hooks"; -import { handleRightToLeftPlacement, isMobile } from "../../services/utils"; +import { handleRightToLeftPlacement, isMobile, openInAppHelpFromUrl } from "../../services/utils"; import clsx from "clsx"; +import FormToggle from "./FormToggle"; interface FormListOpts { children: ComponentChildren; @@ -94,12 +95,13 @@ interface FormListItemOpts { description?: string; className?: string; rtl?: boolean; + postContent?: ComponentChildren; } const TOOLTIP_CONFIG: Partial = { placement: handleRightToLeftPlacement("right"), fallbackPlacements: [ handleRightToLeftPlacement("right") ] -} +}; export function FormListItem({ className, icon, value, title, active, disabled, checked, container, onClick, selected, rtl, triggerCommand, description, ...contentProps }: FormListItemOpts) { const itemRef = useRef(null); @@ -132,6 +134,49 @@ export function FormListItem({ className, icon, value, title, active, disabled, ); } +export function FormListToggleableItem({ title, currentValue, onChange, disabled, helpPage, ...props }: Omit & { + title: string; + currentValue: boolean; + helpPage?: string; + onChange(newValue: boolean): void | Promise; +}) { + const isWaiting = useRef(false); + + return ( + { + if ((e.target as HTMLElement | null)?.classList.contains("contextual-help")) { + return; + } + + e.stopPropagation(); + if (!disabled && !isWaiting.current) { + isWaiting.current = true; + await onChange(!currentValue); + isWaiting.current = false; + } + }}> + {}} + afterName={<> + {helpPage && ( + openInAppHelpFromUrl(helpPage)} + /> + )} + + } + /> + + ); +} + function FormListContent({ children, badges, description, disabled, disabledTooltip }: Pick) { return <> {children} @@ -139,7 +184,7 @@ function FormListContent({ children, badges, description, disabled, disabledTool {text} ))} {disabled && disabledTooltip && ( - + )} {description &&
{description}
} ; diff --git a/apps/client/src/widgets/react/FormToggle.css b/apps/client/src/widgets/react/FormToggle.css index 941839143..bfe5bdc61 100644 --- a/apps/client/src/widgets/react/FormToggle.css +++ b/apps/client/src/widgets/react/FormToggle.css @@ -24,6 +24,14 @@ border-radius: 24px; background-color: var(--switch-off-track-background); transition: background 200ms ease-in; + + &.disable-transitions { + transition: none !important; + + &:after { + transition: none !important; + } + } } .switch-widget .switch-button.on { @@ -103,4 +111,4 @@ body[dir=rtl] .switch-widget .switch-button.on:after { .switch-widget .switch-help-button:hover { color: var(--main-text-color); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index 62069b3fe..e08c1e3c2 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -1,25 +1,39 @@ +import clsx from "clsx"; import "./FormToggle.css"; import HelpButton from "./HelpButton"; +import { useEffect, useState } from "preact/hooks"; +import { ComponentChildren } from "preact"; interface FormToggleProps { currentValue: boolean | null; onChange(newValue: boolean): void; switchOnName: string; - switchOnTooltip: string; + switchOnTooltip?: string; switchOffName: string; - switchOffTooltip: string; + switchOffTooltip?: string; helpPage?: string; disabled?: boolean; + afterName?: ComponentChildren; } -export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) { +export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName }: FormToggleProps) { + const [ disableTransition, setDisableTransition ] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => { + setDisableTransition(false); + }, 100); + return () => clearTimeout(timeout); + }, []); + return (
{ currentValue ? switchOffName : switchOnName } + { afterName }
- ) + ); } function BookmarkSwitch({ note }: { note?: FNote | null }) { - const [ isBookmarked, setIsBookmarked ] = useState(false); - const refreshState = useCallback(() => { - const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); - setIsBookmarked(!!isBookmarked); - }, [ note ]); - - useEffect(() => refreshState(), [ note ]); - useTriliumEvent("entitiesReloaded", ({ loadResults }) => { - if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { - refreshState(); - } - }); + const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); return (
@@ -210,18 +219,36 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { switchOnName={t("bookmark_switch.bookmark")} switchOnTooltip={t("bookmark_switch.bookmark_this_note")} switchOffName={t("bookmark_switch.bookmark")} switchOffTooltip={t("bookmark_switch.remove_bookmark")} currentValue={isBookmarked} - onChange={async (shouldBookmark) => { - if (!note) return; - const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); - - if (!resp.success && "message" in resp) { - toast.showError(resp.message); - } - }} + onChange={setIsBookmarked} disabled={["root", "_hidden"].includes(note?.noteId ?? "")} />
- ) + ); +} + +export function useNoteBookmarkState(note: FNote | null | undefined) { + const [ isBookmarked, setIsBookmarked ] = useState(false); + const refreshState = useCallback(() => { + const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); + setIsBookmarked(!!isBookmarked); + }, [ note ]); + + const changeHandler = useCallback(async (shouldBookmark: boolean) => { + if (!note) return; + const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); + + if (!resp.success && "message" in resp) { + toast.showError(resp.message); + } + }, [ note ]); + + useEffect(() => refreshState(), [ refreshState ]); + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { + refreshState(); + } + }); + return [ isBookmarked, changeHandler ] as const; } function TemplateSwitch({ note }: { note?: FNote | null }) { @@ -237,16 +264,33 @@ function TemplateSwitch({ note }: { note?: FNote | null }) { currentValue={isTemplate} onChange={setIsTemplate} /> - ) + ); } function SharedSwitch({ note }: { note?: FNote | null }) { + const [ isShared, switchShareState ] = useShareState(note); + + return ( +
+ +
+ ); +} + +export function useShareState(note: FNote | null | undefined) { const [ isShared, setIsShared ] = useState(false); const refreshState = useCallback(() => { setIsShared(!!note?.hasAncestor("_share")); }, [ note ]); - useEffect(() => refreshState(), [ note ]); + useEffect(() => refreshState(), [ refreshState ]); useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { refreshState(); @@ -271,28 +315,29 @@ function SharedSwitch({ note }: { note?: FNote | null }) { sync.syncNow(true); }, [ note ]); - return ( -
- -
- ) + return [ isShared, switchShareState ] as const; } function NoteLanguageSwitch({ note }: { note?: FNote | null }) { + return ( +
+ {t("basic_properties.language")}: +   + + + +
+ ); +} + +export function NoteLanguageSelector({ note, extraChildren }: { note: FNote | null | undefined, extraChildren?: ComponentChildren }) { + const [ modalShown, setModalShown ] = useState(false); const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { id: "", name: t("note_language.not_set") }; const [ currentNoteLanguage, setCurrentNoteLanguage ] = useNoteLabel(note, "language"); - const [ modalShown, setModalShown ] = useState(false); const locales = useMemo(() => { const enabledLanguages = JSON.parse(languages ?? "[]") as string[]; const filteredLanguages = getAvailableLocales().filter((l) => typeof l !== "object" || enabledLanguages.includes(l.id)); @@ -300,34 +345,37 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { }, [ languages ]); return ( -
- {t("basic_properties.language")}: -   + <> + {extraChildren} setModalShown(true)} icon="bx bx-cog" >{t("note_language.configure-languages")} - )} - > + } + /> + {createPortal( + , + document.body + )} + + ); +} - - - - - setModalShown(false)} - size="lg" scrollable - > - - -
+function ContentLanguagesModal({ modalShown, setModalShown }: { modalShown: boolean, setModalShown: (shown: boolean) => void }) { + return ( + setModalShown(false)} + size="lg" scrollable + > + + ); } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 12655262e..85ce63013 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -1,5 +1,5 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons"; -import { useContext } from "preact/hooks"; +import { useContext, useState } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; @@ -13,10 +13,12 @@ import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/u import ws from "../../services/ws"; import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; -import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList"; -import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; +import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; +import { NoteTypeDropdownContent, useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; +import protected_session from "../../services/protected_session"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -55,8 +57,10 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not const isMac = getIsMac(); const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "aiChat"].includes(noteType); const isSearchOrBook = ["search", "book"].includes(noteType); + const isHelpPage = note.noteId.startsWith("_help"); const [syncServerHost] = useTriliumOption("syncServerHost"); const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext); + const isNormalViewMode = noteContext?.viewScope?.viewMode === "default"; return ( } + {isNewLayout && isNormalViewMode && !isHelpPage && <> + + + } + parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} /> @@ -107,11 +116,82 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not - {glob.isDev && } + {glob.isDev && <> + + + } ); } +function NoteBasicProperties({ note }: { note: FNote }) { + const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); + const [ isShared, switchShareState ] = useShareState(note); + const [ isTemplate, setIsTemplate ] = useNoteLabelBoolean(note, "template"); + const isProtected = useNoteProperty(note, "isProtected"); + + return <> + + + + + protected_session.protectNote(note.noteId, shouldProtect, false)} + /> + + + ; +} + +function EditabilityDropdown({ note }: { note: FNote }) { + const [ readOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); + const [ autoReadOnlyDisabled, setAutoReadOnlyDisabled ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); + + function setState(readOnly: boolean, autoReadOnlyDisabled: boolean) { + setReadOnly(readOnly); + setAutoReadOnlyDisabled(autoReadOnlyDisabled); + } + + return ( + + setState(false, false)} description={t("editability_select.note_is_editable")}>{t("editability_select.auto")} + setState(true, false)} description={t("editability_select.note_is_read_only")}>{t("editability_select.read_only")} + setState(false, true)} description={t("editability_select.note_is_always_editable")}>{t("editability_select.always_editable")} + + ); +} + +function NoteTypeDropdown({ note }: { note: FNote }) { + const currentNoteType = useNoteProperty(note, "type") ?? undefined; + const currentNoteMime = useNoteProperty(note, "mime"); + const [ modalShown, setModalShown ] = useState(false); + + return ( + + + + ); +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index ab351637d..a76e97140 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -1,22 +1,24 @@ -import ScriptTab from "./ScriptTab"; -import EditedNotesTab from "./EditedNotesTab"; -import NotePropertiesTab from "./NotePropertiesTab"; -import NoteInfoTab from "./NoteInfoTab"; -import SimilarNotesTab from "./SimilarNotesTab"; -import FilePropertiesTab from "./FilePropertiesTab"; -import ImagePropertiesTab from "./ImagePropertiesTab"; -import NotePathsTab from "./NotePathsTab"; -import NoteMapTab from "./NoteMapTab"; -import OwnedAttributesTab from "./OwnedAttributesTab"; -import InheritedAttributesTab from "./InheritedAttributesTab"; -import CollectionPropertiesTab from "./CollectionPropertiesTab"; -import SearchDefinitionTab from "./SearchDefinitionTab"; -import BasicPropertiesTab from "./BasicPropertiesTab"; -import FormattingToolbar from "./FormattingToolbar"; -import options from "../../services/options"; -import { t } from "../../services/i18n"; -import { TabConfiguration } from "./ribbon-interface"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; +import { t } from "../../services/i18n"; +import options from "../../services/options"; +import BasicPropertiesTab from "./BasicPropertiesTab"; +import CollectionPropertiesTab from "./CollectionPropertiesTab"; +import EditedNotesTab from "./EditedNotesTab"; +import FilePropertiesTab from "./FilePropertiesTab"; +import FormattingToolbar from "./FormattingToolbar"; +import ImagePropertiesTab from "./ImagePropertiesTab"; +import InheritedAttributesTab from "./InheritedAttributesTab"; +import NoteInfoTab from "./NoteInfoTab"; +import NoteMapTab from "./NoteMapTab"; +import NotePathsTab from "./NotePathsTab"; +import NotePropertiesTab from "./NotePropertiesTab"; +import OwnedAttributesTab from "./OwnedAttributesTab"; +import { TabConfiguration } from "./ribbon-interface"; +import ScriptTab from "./ScriptTab"; +import SearchDefinitionTab from "./SearchDefinitionTab"; +import SimilarNotesTab from "./SimilarNotesTab"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ { @@ -28,7 +30,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ toggleCommand: "toggleRibbonTabClassicEditor", content: FormattingToolbar, activate: ({ note }) => !options.is("editedNotesOpenInRibbon") || !note?.hasOwnedLabel("dateNote"), - stayInDom: !isExperimentalFeatureEnabled("new-layout"), + stayInDom: !isNewLayout, avoidInNewLayout: true }, { @@ -85,11 +87,10 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ activate: true, }, { - // BasicProperties title: t("basic_properties.basic_properties"), icon: "bx bx-slider", content: BasicPropertiesTab, - show: ({note}) => !note?.isLaunchBarConfig(), + show: ({note}) => !isNewLayout && !note?.isLaunchBarConfig(), toggleCommand: "toggleRibbonTabBasicProperties" }, { diff --git a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx index 5628cc798..b9b1d8758 100644 --- a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx +++ b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx @@ -1,8 +1,9 @@ import { Locale } from "@triliumnext/commons"; +import { ComponentChildren } from "preact"; +import { useMemo } from "preact/hooks"; + import Dropdown from "../../../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../../../react/FormList"; -import { ComponentChildren } from "preact"; -import { useMemo, useState } from "preact/hooks"; export function LocaleSelector({ id, locales, currentValue, onChange, defaultLocale, extraChildren }: { id?: string; @@ -12,7 +13,7 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc defaultLocale?: Locale, extraChildren?: ComponentChildren }) { - const [ activeLocale, setActiveLocale ] = useState(defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue)); + const activeLocale = defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue); const processedLocales = useMemo(() => { const leftToRightLanguages = locales.filter((l) => !l.rtl); @@ -48,7 +49,6 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc rtl={locale.rtl} checked={locale.id === currentValue} onClick={() => { - setActiveLocale(locale); onChange(locale.id); }} >{locale.name}