mirror of
https://github.com/zadam/trilium.git
synced 2025-12-12 18:34:24 +01:00
New layout: Integrate Basic properties (#8014)
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
This commit is contained in:
commit
19b32dd3a6
@ -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, <Ribbon />)
|
||||
.optChild(isNewLayout, (
|
||||
<Ribbon>
|
||||
<NoteStatusBar />
|
||||
</Ribbon>
|
||||
))
|
||||
)
|
||||
)
|
||||
.child(...this.customWidgets.get("center-pane"))
|
||||
|
||||
@ -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[] = [];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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%);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -52,7 +52,7 @@ function ShareBadge() {
|
||||
|
||||
return (link &&
|
||||
<Badge
|
||||
icon={isSharedExternally ? "bx bx-world" : "bx bx-link"}
|
||||
icon={isSharedExternally ? "bx bx-world" : "bx bx-share-alt"}
|
||||
text={isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")}
|
||||
tooltip={isSharedExternally ?
|
||||
t("breadcrumb_badges.shared_publicly_description", { link }) :
|
||||
|
||||
13
apps/client/src/widgets/NoteStatusBar.css
Normal file
13
apps/client/src/widgets/NoteStatusBar.css
Normal file
@ -0,0 +1,13 @@
|
||||
.note-status-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-inline: 1em;
|
||||
|
||||
.dropdown {
|
||||
font-size: 0.85em;
|
||||
|
||||
.dropdown-toggle {
|
||||
padding: 0.1em 0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
apps/client/src/widgets/NoteStatusBar.tsx
Normal file
25
apps/client/src/widgets/NoteStatusBar.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import "./NoteStatusBar.css";
|
||||
|
||||
import { t } from "../services/i18n";
|
||||
import { openInAppHelpFromUrl } from "../services/utils";
|
||||
import { FormListItem } from "./react/FormList";
|
||||
import { useNoteContext } from "./react/hooks";
|
||||
import { NoteLanguageSelector } from "./ribbon/BasicPropertiesTab";
|
||||
|
||||
export default function NoteStatusBar() {
|
||||
const { note } = useNoteContext();
|
||||
|
||||
return (
|
||||
<div className="note-status-bar">
|
||||
<NoteLanguageSelector
|
||||
note={note}
|
||||
extraChildren={(
|
||||
<FormListItem
|
||||
onClick={() => openInAppHelpFromUrl("veGu4faJErEM")}
|
||||
icon="bx bx-help-circle"
|
||||
>{t("note_language.help-on-languages")}</FormListItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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<T> {
|
||||
icon: string,
|
||||
@ -70,6 +71,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
</>}
|
||||
|
||||
{!isElectron() && <BrowserOnlyOptions />}
|
||||
{glob.isDev && <DevelopmentOptions />}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
@ -99,6 +101,21 @@ function BrowserOnlyOptions() {
|
||||
</>;
|
||||
}
|
||||
|
||||
function DevelopmentOptions() {
|
||||
const newLayoutEnabled = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
return <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
icon={newLayoutEnabled ? "bx bx-layout" : "bx bxs-layout"}
|
||||
onClick={async () => {
|
||||
await toggleExperimentalFeature("new-layout", !newLayoutEnabled);
|
||||
reloadFrontendApp();
|
||||
}}
|
||||
>{!newLayoutEnabled ? "Switch to new layout" : "Switch to old layout"}</FormListItem>
|
||||
</>;
|
||||
}
|
||||
|
||||
function SwitchToOptions() {
|
||||
if (isElectron()) {
|
||||
return;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Tooltip.Options> = {
|
||||
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<HTMLLIElement>(null);
|
||||
@ -132,6 +134,49 @@ export function FormListItem({ className, icon, value, title, active, disabled,
|
||||
);
|
||||
}
|
||||
|
||||
export function FormListToggleableItem({ title, currentValue, onChange, disabled, helpPage, ...props }: Omit<FormListItemOpts, "onClick" | "children"> & {
|
||||
title: string;
|
||||
currentValue: boolean;
|
||||
helpPage?: string;
|
||||
onChange(newValue: boolean): void | Promise<void>;
|
||||
}) {
|
||||
const isWaiting = useRef(false);
|
||||
|
||||
return (
|
||||
<FormListItem
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
onClick={async (e) => {
|
||||
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;
|
||||
}
|
||||
}}>
|
||||
<FormToggle
|
||||
switchOnName={title}
|
||||
switchOffName={title}
|
||||
currentValue={currentValue}
|
||||
onChange={() => {}}
|
||||
afterName={<>
|
||||
{helpPage && (
|
||||
<span
|
||||
class="bx bx-help-circle contextual-help"
|
||||
onClick={() => openInAppHelpFromUrl(helpPage)}
|
||||
/>
|
||||
)}
|
||||
<span class="switch-spacer" />
|
||||
</>}
|
||||
/>
|
||||
</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function FormListContent({ children, badges, description, disabled, disabledTooltip }: Pick<FormListItemOpts, "children" | "badges" | "description" | "disabled" | "disabledTooltip">) {
|
||||
return <>
|
||||
{children}
|
||||
@ -139,7 +184,7 @@ function FormListContent({ children, badges, description, disabled, disabledTool
|
||||
<span className={`badge ${className ?? ""}`}>{text}</span>
|
||||
))}
|
||||
{disabled && disabledTooltip && (
|
||||
<span class="bx bx-info-circle disabled-tooltip" title={disabledTooltip} />
|
||||
<span class="bx bx-info-circle contextual-help" title={disabledTooltip} />
|
||||
)}
|
||||
{description && <div className="description">{description}</div>}
|
||||
</>;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<div className="switch-widget">
|
||||
<span className="switch-name">{ currentValue ? switchOffName : switchOnName }</span>
|
||||
{ afterName }
|
||||
|
||||
<label>
|
||||
<div
|
||||
className={`switch-button ${currentValue ? "on" : ""} ${disabled ? "disabled" : ""}`}
|
||||
className={clsx("switch-button", { "on": currentValue, disabled, "disable-transitions": disableTransition })}
|
||||
title={currentValue ? switchOffTooltip : switchOnTooltip }
|
||||
>
|
||||
<input
|
||||
@ -37,5 +51,5 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
|
||||
|
||||
{ helpPage && <HelpButton className="switch-help-button" helpPage={helpPage} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -845,6 +845,8 @@ export function useGlobalShortcut(keyboardShortcut: string | null | undefined, h
|
||||
*/
|
||||
export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) {
|
||||
const [ isReadOnly, setIsReadOnly ] = useState<boolean | undefined>(undefined);
|
||||
const [ readOnlyAttr ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const [ autoReadOnlyDisabledAttr ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled");
|
||||
|
||||
const enableEditing = useCallback((enabled = true) => {
|
||||
if (noteContext?.viewScope) {
|
||||
@ -859,7 +861,7 @@ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: N
|
||||
setIsReadOnly(readOnly);
|
||||
});
|
||||
}
|
||||
}, [ note, noteContext, noteContext?.viewScope ]);
|
||||
}, [ note, noteContext, noteContext?.viewScope, readOnlyAttr, autoReadOnlyDisabledAttr ]);
|
||||
|
||||
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
|
||||
if (noteContext?.ntxId === eventNoteContext.ntxId) {
|
||||
|
||||
@ -1,26 +1,29 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { NOTE_TYPES } from "../../services/note_types";
|
||||
import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList";
|
||||
import { getAvailableLocales, t } from "../../services/i18n";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import mime_types from "../../services/mime_types";
|
||||
import { Locale, LOCALES, NoteType, ToggleInParentResponse } from "@triliumnext/commons";
|
||||
import server from "../../services/server";
|
||||
import dialog from "../../services/dialog";
|
||||
import FormToggle from "../react/FormToggle";
|
||||
import { NoteType, ToggleInParentResponse } from "@triliumnext/commons";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import protected_session from "../../services/protected_session";
|
||||
import FormDropdownList from "../react/FormDropdownList";
|
||||
import toast from "../../services/toast";
|
||||
import branches from "../../services/branches";
|
||||
import dialog from "../../services/dialog";
|
||||
import { getAvailableLocales, t } from "../../services/i18n";
|
||||
import mime_types from "../../services/mime_types";
|
||||
import { NOTE_TYPES } from "../../services/note_types";
|
||||
import protected_session from "../../services/protected_session";
|
||||
import server from "../../services/server";
|
||||
import sync from "../../services/sync";
|
||||
import toast from "../../services/toast";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import FormDropdownList from "../react/FormDropdownList";
|
||||
import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList";
|
||||
import FormToggle from "../react/FormToggle";
|
||||
import HelpButton from "../react/HelpButton";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import Modal from "../react/Modal";
|
||||
import { CodeMimeTypesList } from "../type_widgets/options/code_notes";
|
||||
import { ContentLanguagesList } from "../type_widgets/options/i18n";
|
||||
import { LocaleSelector } from "../type_widgets/options/components/LocaleSelector";
|
||||
import { ContentLanguagesList } from "../type_widgets/options/i18n";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
|
||||
export default function BasicPropertiesTab({ note }: TabContext) {
|
||||
return (
|
||||
@ -37,18 +40,38 @@ export default function BasicPropertiesTab({ note }: TabContext) {
|
||||
}
|
||||
|
||||
function NoteTypeWidget({ note }: { note?: FNote | null }) {
|
||||
const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []);
|
||||
const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes");
|
||||
const mimeTypes = useMemo(() => {
|
||||
mime_types.loadMimeTypes();
|
||||
return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled)
|
||||
}, [ codeNotesMimeTypes ]);
|
||||
const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []);
|
||||
|
||||
const currentNoteType = useNoteProperty(note, "type") ?? undefined;
|
||||
const currentNoteMime = useNoteProperty(note, "mime");
|
||||
const [ modalShown, setModalShown ] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="note-type-container">
|
||||
<span>{t("basic_properties.note_type")}:</span>
|
||||
<Dropdown
|
||||
dropdownContainerClassName="note-type-dropdown"
|
||||
text={<span className="note-type-desc">{findTypeTitle(currentNoteType, currentNoteMime)}</span>}
|
||||
disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")}
|
||||
>
|
||||
<NoteTypeDropdownContent currentNoteType={currentNoteType} currentNoteMime={currentNoteMime} note={note} setModalShown={setModalShown} />
|
||||
</Dropdown>
|
||||
|
||||
{createPortal(
|
||||
<NoteTypeOptionsModal modalShown={modalShown} setModalShown={setModalShown} />,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note, setModalShown }: { currentNoteType?: NoteType, currentNoteMime?: string | null, note?: FNote | null, setModalShown: Dispatch<StateUpdater<boolean>> }) {
|
||||
const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes");
|
||||
const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []);
|
||||
const mimeTypes = useMemo(() => {
|
||||
mime_types.loadMimeTypes();
|
||||
return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled);
|
||||
}, [ codeNotesMimeTypes ]);
|
||||
const changeNoteType = useCallback(async (type: NoteType, mime?: string) => {
|
||||
if (!note || (type === currentNoteType && mime === currentNoteMime)) {
|
||||
return;
|
||||
@ -68,71 +91,68 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) {
|
||||
}, [ note, currentNoteType, currentNoteMime ]);
|
||||
|
||||
return (
|
||||
<div className="note-type-container">
|
||||
<span>{t("basic_properties.note_type")}:</span>
|
||||
<Dropdown
|
||||
dropdownContainerClassName="note-type-dropdown"
|
||||
text={<span className="note-type-desc">{findTypeTitle(currentNoteType, currentNoteMime)}</span>}
|
||||
disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")}
|
||||
>
|
||||
{noteTypes.map(({ isNew, isBeta, type, mime, title }) => {
|
||||
const badges: FormListBadge[] = [];
|
||||
if (isNew) {
|
||||
badges.push({
|
||||
className: "new-note-type-badge",
|
||||
text: t("note_types.new-feature")
|
||||
});
|
||||
}
|
||||
if (isBeta) {
|
||||
badges.push({
|
||||
text: t("note_types.beta-feature")
|
||||
});
|
||||
}
|
||||
<>
|
||||
{noteTypes.map(({ isNew, isBeta, type, mime, title }) => {
|
||||
const badges: FormListBadge[] = [];
|
||||
if (isNew) {
|
||||
badges.push({
|
||||
className: "new-note-type-badge",
|
||||
text: t("note_types.new-feature")
|
||||
});
|
||||
}
|
||||
if (isBeta) {
|
||||
badges.push({
|
||||
text: t("note_types.beta-feature")
|
||||
});
|
||||
}
|
||||
|
||||
const checked = (type === currentNoteType);
|
||||
if (type !== "code") {
|
||||
return (
|
||||
const checked = (type === currentNoteType);
|
||||
if (type !== "code") {
|
||||
return (
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
badges={badges}
|
||||
onClick={() => changeNoteType(type, mime)}
|
||||
>{title}</FormListItem>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
badges={badges}
|
||||
onClick={() => changeNoteType(type, mime)}
|
||||
>{title}</FormListItem>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
disabled
|
||||
>
|
||||
<strong>{title}</strong>
|
||||
</FormListItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})}
|
||||
disabled
|
||||
>
|
||||
<strong>{title}</strong>
|
||||
</FormListItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
||||
{mimeTypes.map(({ title, mime }) => (
|
||||
<FormListItem onClick={() => changeNoteType("code", mime)}>
|
||||
{title}
|
||||
</FormListItem>
|
||||
))}
|
||||
{mimeTypes.map(({ title, mime }) => (
|
||||
<FormListItem onClick={() => changeNoteType("code", mime)}>
|
||||
{title}
|
||||
</FormListItem>
|
||||
))}
|
||||
|
||||
<FormDropdownDivider />
|
||||
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
|
||||
</Dropdown>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
<Modal
|
||||
className="code-mime-types-modal"
|
||||
title={t("code_mime_types.title")}
|
||||
show={modalShown} onHidden={() => setModalShown(false)}
|
||||
size="xl" scrollable
|
||||
>
|
||||
<CodeMimeTypesList />
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
function NoteTypeOptionsModal({ modalShown, setModalShown }: { modalShown: boolean, setModalShown: (shown: boolean) => void }) {
|
||||
return (
|
||||
<Modal
|
||||
className="code-mime-types-modal"
|
||||
title={t("code_mime_types.title")}
|
||||
show={modalShown} onHidden={() => setModalShown(false)}
|
||||
size="xl" scrollable
|
||||
>
|
||||
<CodeMimeTypesList />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function ProtectedNoteSwitch({ note }: { note?: FNote | null }) {
|
||||
@ -187,22 +207,11 @@ function EditabilitySelect({ note }: { note?: FNote | null }) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BookmarkSwitch({ note }: { note?: FNote | null }) {
|
||||
const [ isBookmarked, setIsBookmarked ] = useState<boolean>(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 (
|
||||
<div className="bookmark-switch-container">
|
||||
@ -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<ToggleInParentResponse>(`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 ?? "")}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function useNoteBookmarkState(note: FNote | null | undefined) {
|
||||
const [ isBookmarked, setIsBookmarked ] = useState<boolean>(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<ToggleInParentResponse>(`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}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SharedSwitch({ note }: { note?: FNote | null }) {
|
||||
const [ isShared, switchShareState ] = useShareState(note);
|
||||
|
||||
return (
|
||||
<div className="shared-switch-container">
|
||||
<FormToggle
|
||||
currentValue={isShared}
|
||||
onChange={switchShareState}
|
||||
switchOnName={t("shared_switch.shared")} switchOnTooltip={t("shared_switch.toggle-on-title")}
|
||||
switchOffName={t("shared_switch.shared")} switchOffTooltip={t("shared_switch.toggle-off-title")}
|
||||
helpPage="R9pX4DGra2Vt"
|
||||
disabled={["root", "_share", "_hidden"].includes(note?.noteId ?? "") || note?.noteId.startsWith("_options")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="shared-switch-container">
|
||||
<FormToggle
|
||||
currentValue={isShared}
|
||||
onChange={switchShareState}
|
||||
switchOnName={t("shared_switch.shared")} switchOnTooltip={t("shared_switch.toggle-on-title")}
|
||||
switchOffName={t("shared_switch.shared")} switchOffTooltip={t("shared_switch.toggle-off-title")}
|
||||
helpPage="R9pX4DGra2Vt"
|
||||
disabled={["root", "_share", "_hidden"].includes(note?.noteId ?? "") || note?.noteId.startsWith("_options")}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
return [ isShared, switchShareState ] as const;
|
||||
}
|
||||
|
||||
function NoteLanguageSwitch({ note }: { note?: FNote | null }) {
|
||||
return (
|
||||
<div className="note-language-container">
|
||||
<span>{t("basic_properties.language")}:</span>
|
||||
|
||||
|
||||
<NoteLanguageSelector note={note} />
|
||||
<HelpButton helpPage="veGu4faJErEM" style={{ marginInlineStart: "4px" }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="note-language-container">
|
||||
<span>{t("basic_properties.language")}:</span>
|
||||
|
||||
<>
|
||||
<LocaleSelector
|
||||
locales={locales}
|
||||
defaultLocale={DEFAULT_LOCALE}
|
||||
currentValue={currentNoteLanguage ?? ""} onChange={setCurrentNoteLanguage}
|
||||
extraChildren={(
|
||||
extraChildren={<>
|
||||
{extraChildren}
|
||||
<FormListItem
|
||||
onClick={() => setModalShown(true)}
|
||||
icon="bx bx-cog"
|
||||
>{t("note_language.configure-languages")}</FormListItem>
|
||||
)}
|
||||
>
|
||||
</>}
|
||||
/>
|
||||
{createPortal(
|
||||
<ContentLanguagesModal modalShown={modalShown} setModalShown={setModalShown} />,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
</LocaleSelector>
|
||||
|
||||
<HelpButton helpPage="B0lcI9xz1r8K" style={{ marginInlineStart: "4px" }} />
|
||||
|
||||
<Modal
|
||||
className="content-languages-modal"
|
||||
title={t("content_language.title")}
|
||||
show={modalShown} onHidden={() => setModalShown(false)}
|
||||
size="lg" scrollable
|
||||
>
|
||||
<ContentLanguagesList />
|
||||
</Modal>
|
||||
</div>
|
||||
function ContentLanguagesModal({ modalShown, setModalShown }: { modalShown: boolean, setModalShown: (shown: boolean) => void }) {
|
||||
return (
|
||||
<Modal
|
||||
className="content-languages-modal"
|
||||
title={t("content_language.title")}
|
||||
show={modalShown} onHidden={() => setModalShown(false)}
|
||||
size="lg" scrollable
|
||||
>
|
||||
<ContentLanguagesList />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
<Dropdown
|
||||
@ -79,6 +83,11 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
{isElectron && <CommandItem command="exportAsPdf" icon="bx bxs-file-pdf" disabled={!isPrintable} text={t("note_actions.print_pdf")} />}
|
||||
<FormDropdownDivider />
|
||||
|
||||
{isNewLayout && isNormalViewMode && !isHelpPage && <>
|
||||
<NoteBasicProperties note={note} />
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
||||
disabled={isInOptionsOrHelp || note.type === "search"}
|
||||
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
||||
@ -107,11 +116,82 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
<FormDropdownDivider />
|
||||
|
||||
<CommandItem command="showAttachments" icon="bx bx-paperclip" disabled={isInOptionsOrHelp} text={t("note_actions.note_attachments")} />
|
||||
{glob.isDev && <DevelopmentActions note={note} noteContext={noteContext} />}
|
||||
{glob.isDev && <>
|
||||
<FormDropdownDivider />
|
||||
<DevelopmentActions note={note} noteContext={noteContext} />
|
||||
</>}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
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 <>
|
||||
<FormListToggleableItem
|
||||
icon="bx bx-bookmark"
|
||||
title={t("bookmark_switch.bookmark")}
|
||||
currentValue={isBookmarked} onChange={setIsBookmarked}
|
||||
disabled={["root", "_hidden"].includes(note?.noteId ?? "")}
|
||||
/>
|
||||
<FormListToggleableItem
|
||||
icon="bx bx-copy-alt"
|
||||
title={t("template_switch.template")}
|
||||
currentValue={isTemplate} onChange={setIsTemplate}
|
||||
helpPage="KC1HB96bqqHX"
|
||||
disabled={note?.noteId.startsWith("_options")}
|
||||
/>
|
||||
<FormListToggleableItem
|
||||
icon="bx bx-share-alt"
|
||||
title={t("shared_switch.shared")}
|
||||
currentValue={isShared} onChange={switchShareState}
|
||||
helpPage="R9pX4DGra2Vt"
|
||||
disabled={["root", "_share", "_hidden"].includes(note?.noteId ?? "") || note?.noteId.startsWith("_options")}
|
||||
/>
|
||||
<EditabilityDropdown note={note} />
|
||||
<FormListToggleableItem
|
||||
icon="bx bx-lock-alt"
|
||||
title={t("protect_note.toggle-on")}
|
||||
currentValue={!!isProtected} onChange={shouldProtect => protected_session.protectNote(note.noteId, shouldProtect, false)}
|
||||
/>
|
||||
<FormDropdownDivider />
|
||||
<NoteTypeDropdown note={note} />
|
||||
</>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<FormDropdownSubmenu title={t("basic_properties.editable")} icon="bx bx-edit-alt" dropStart>
|
||||
<FormListItem checked={!readOnly && !autoReadOnlyDisabled} onClick={() => setState(false, false)} description={t("editability_select.note_is_editable")}>{t("editability_select.auto")}</FormListItem>
|
||||
<FormListItem checked={readOnly && !autoReadOnlyDisabled} onClick={() => setState(true, false)} description={t("editability_select.note_is_read_only")}>{t("editability_select.read_only")}</FormListItem>
|
||||
<FormListItem checked={!readOnly && autoReadOnlyDisabled} onClick={() => setState(false, true)} description={t("editability_select.note_is_always_editable")}>{t("editability_select.always_editable")}</FormListItem>
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteTypeDropdown({ note }: { note: FNote }) {
|
||||
const currentNoteType = useNoteProperty(note, "type") ?? undefined;
|
||||
const currentNoteMime = useNoteProperty(note, "mime");
|
||||
const [ modalShown, setModalShown ] = useState(false);
|
||||
|
||||
return (
|
||||
<FormDropdownSubmenu title={t("basic_properties.note_type")} icon="bx bx-file" dropStart>
|
||||
<NoteTypeDropdownContent currentNoteType={currentNoteType} currentNoteMime={currentNoteMime} note={note} setModalShown={setModalShown} />
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) {
|
||||
return (
|
||||
<FormDropdownSubmenu title="Development Actions" icon="bx bx-wrench" dropStart>
|
||||
|
||||
@ -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"
|
||||
},
|
||||
{
|
||||
|
||||
@ -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}</FormListItem>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user