mirror of
https://github.com/zadam/trilium.git
synced 2025-12-16 21:29:56 +01:00
Merge branch 'main' into feat/restyle/backlinks-panel
This commit is contained in:
@@ -521,9 +521,7 @@ body.mobile .dropdown .dropdown-submenu > span {
|
|||||||
.cm-editor {
|
.cm-editor {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 4px;
|
|
||||||
font-size: var(--monospace-font-size);
|
font-size: var(--monospace-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,6 +627,11 @@ pre:not(.hljs) {
|
|||||||
padding: var(--padding-size);
|
padding: var(--padding-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre:has(> .cm-editor) {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
pre > button.copy-button {
|
pre > button.copy-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--copy-button-margin-size);
|
top: var(--copy-button-margin-size);
|
||||||
@@ -2471,6 +2474,11 @@ footer.webview-footer button {
|
|||||||
inset-inline-start: 10px;
|
inset-inline-start: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-floating-buttons.top-right {
|
||||||
|
top: 10px;
|
||||||
|
inset-inline-end: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.content-floating-buttons.bottom-left {
|
.content-floating-buttons.bottom-left {
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
inset-inline-start: 10px;
|
inset-inline-start: 10px;
|
||||||
|
|||||||
@@ -166,17 +166,30 @@ body.desktop .dropdown-submenu .dropdown-menu {
|
|||||||
--menu-item-end-padding: 22px;
|
--menu-item-end-padding: 22px;
|
||||||
--menu-item-vertical-padding: 2px;
|
--menu-item-vertical-padding: 2px;
|
||||||
|
|
||||||
padding-top: var(--menu-item-vertical-padding) !important;
|
|
||||||
padding-bottom: var(--menu-item-vertical-padding) !important;
|
|
||||||
padding-inline-start: var(--menu-item-start-padding) !important;
|
|
||||||
padding-inline-end: var(--menu-item-end-padding) !important;
|
|
||||||
|
|
||||||
/* Note: the right padding should also accommodate the submenu arrow. */
|
/* Note: the right padding should also accommodate the submenu arrow. */
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item {
|
.dropdown-item:not(.dropdown-submenu),
|
||||||
|
body.desktop .dropdown-item.dropdown-submenu .dropdown-toggle,
|
||||||
|
.excalidraw .context-menu .context-menu-item {
|
||||||
|
padding-top: var(--menu-item-vertical-padding) !important;
|
||||||
|
padding-bottom: var(--menu-item-vertical-padding) !important;
|
||||||
|
padding-inline-start: var(--menu-item-start-padding) !important;
|
||||||
|
padding-inline-end: var(--menu-item-end-padding) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.dropdown-submenu {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.dropdown-toggle {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop .dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item:not(.dropdown-submenu),
|
||||||
|
body.desktop .dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item.dropdown-submenu .dropdown-toggle {
|
||||||
padding-inline-end: var(--menu-item-start-padding) !important;
|
padding-inline-end: var(--menu-item-start-padding) !important;
|
||||||
padding-inline-start: var(--menu-item-end-padding) !important;
|
padding-inline-start: var(--menu-item-end-padding) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -696,6 +696,9 @@
|
|||||||
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
|
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
|
||||||
"convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?",
|
"convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?",
|
||||||
"print_pdf": "Export as PDF...",
|
"print_pdf": "Export as PDF...",
|
||||||
|
"export_as_image": "Export as image",
|
||||||
|
"export_as_image_png": "PNG (raster)",
|
||||||
|
"export_as_image_svg": "SVG (vector)",
|
||||||
"note_map": "Note map"
|
"note_map": "Note map"
|
||||||
},
|
},
|
||||||
"onclick_button": {
|
"onclick_button": {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const POPUP_HIDDEN_FLOATING_BUTTONS: FloatingButtonsList = [
|
|||||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||||
|
|
||||||
function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||||
const isEnabled = (note.noteId === "_backendLog" || note.type === "render") && isDefaultViewMode;
|
const isEnabled = !isNewLayout && (note.noteId === "_backendLog" || note.type === "render") && isDefaultViewMode;
|
||||||
return isEnabled && <FloatingButton
|
return isEnabled && <FloatingButton
|
||||||
text={t("backend_log.refresh")}
|
text={t("backend_log.refresh")}
|
||||||
icon="bx bx-refresh"
|
icon="bx bx-refresh"
|
||||||
@@ -90,7 +90,7 @@ function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefault
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: FloatingButtonContext) {
|
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: FloatingButtonContext) {
|
||||||
const isEnabled = note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode;
|
const isEnabled = !isNewLayout && note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode;
|
||||||
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
||||||
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
|
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
|
|||||||
|
|
||||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
|
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
|
||||||
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||||
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
|
const isEnabled = !isNewLayout && ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
|
||||||
&& note.isContentAvailable() && isDefaultViewMode;
|
&& note.isContentAvailable() && isDefaultViewMode;
|
||||||
|
|
||||||
return isEnabled && <FloatingButton
|
return isEnabled && <FloatingButton
|
||||||
@@ -173,7 +173,7 @@ function ShowHighlightsListWidgetButton({ note, noteContext, isDefaultViewMode }
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RunActiveNoteButton({ note }: FloatingButtonContext) {
|
function RunActiveNoteButton({ note }: FloatingButtonContext) {
|
||||||
const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium";
|
const isEnabled = !isNewLayout && (note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium");
|
||||||
return isEnabled && <FloatingButton
|
return isEnabled && <FloatingButton
|
||||||
icon="bx bx-play"
|
icon="bx bx-play"
|
||||||
text={t("code_buttons.execute_button_title")}
|
text={t("code_buttons.execute_button_title")}
|
||||||
@@ -182,7 +182,7 @@ function RunActiveNoteButton({ note }: FloatingButtonContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
|
function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
|
||||||
const isEnabled = note.mime.startsWith("application/javascript;env=");
|
const isEnabled = !isNewLayout && note.mime.startsWith("application/javascript;env=");
|
||||||
return isEnabled && <FloatingButton
|
return isEnabled && <FloatingButton
|
||||||
icon="bx bx-help-circle"
|
icon="bx bx-help-circle"
|
||||||
text={t("code_buttons.trilium_api_docs_button_title")}
|
text={t("code_buttons.trilium_api_docs_button_title")}
|
||||||
@@ -191,25 +191,29 @@ function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SaveToNoteButton({ note }: FloatingButtonContext) {
|
function SaveToNoteButton({ note }: FloatingButtonContext) {
|
||||||
const isEnabled = note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely();
|
const isEnabled = !isNewLayout && note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely();
|
||||||
return isEnabled && <FloatingButton
|
return isEnabled && <FloatingButton
|
||||||
icon="bx bx-save"
|
icon="bx bx-save"
|
||||||
text={t("code_buttons.save_to_note_button_title")}
|
text={t("code_buttons.save_to_note_button_title")}
|
||||||
onClick={async (e) => {
|
onClick={buildSaveSqlToNoteHandler(note)}
|
||||||
e.preventDefault();
|
|
||||||
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId });
|
|
||||||
if (notePath) {
|
|
||||||
toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) }));
|
|
||||||
// TODO: This hangs the navigation, for some reason.
|
|
||||||
//await ws.waitForMaxKnownEntityChangeId();
|
|
||||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildSaveSqlToNoteHandler(note: FNote) {
|
||||||
|
return async (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId });
|
||||||
|
if (notePath) {
|
||||||
|
toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) }));
|
||||||
|
// TODO: This hangs the navigation, for some reason.
|
||||||
|
//await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingButtonContext) {
|
function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingButtonContext) {
|
||||||
const isEnabled = (note.type === "relationMap" && isDefaultViewMode);
|
const isEnabled = (!isNewLayout && note.type === "relationMap" && isDefaultViewMode);
|
||||||
return isEnabled && (
|
return isEnabled && (
|
||||||
<>
|
<>
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
@@ -242,7 +246,7 @@ function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingB
|
|||||||
}
|
}
|
||||||
|
|
||||||
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
|
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
|
||||||
const isEnabled = viewType === "geoMap" && !isReadOnly;
|
const isEnabled = !isNewLayout && viewType === "geoMap" && !isReadOnly;
|
||||||
return isEnabled && (
|
return isEnabled && (
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
icon="bx bx-plus-circle"
|
icon="bx bx-plus-circle"
|
||||||
@@ -283,7 +287,7 @@ function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) {
|
function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) {
|
||||||
const isEnabled = ["mermaid", "mindMap"].includes(note?.type ?? "")
|
const isEnabled = !isNewLayout && ["mermaid", "mindMap"].includes(note?.type ?? "")
|
||||||
&& note?.isContentAvailable() && isDefaultViewMode;
|
&& note?.isContentAvailable() && isDefaultViewMode;
|
||||||
return isEnabled && (
|
return isEnabled && (
|
||||||
<>
|
<>
|
||||||
@@ -304,7 +308,7 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
|
|||||||
|
|
||||||
function InAppHelpButton({ note }: FloatingButtonContext) {
|
function InAppHelpButton({ note }: FloatingButtonContext) {
|
||||||
const helpUrl = getHelpUrlForNote(note);
|
const helpUrl = getHelpUrlForNote(note);
|
||||||
const isEnabled = !!helpUrl && (!isNewLayout || (note?.type !== "book"));
|
const isEnabled = !!helpUrl && !isNewLayout;
|
||||||
|
|
||||||
return isEnabled && (
|
return isEnabled && (
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
|||||||
text={<>
|
text={<>
|
||||||
{isVerticalLayout && <VerticalLayoutIcon />}
|
{isVerticalLayout && <VerticalLayoutIcon />}
|
||||||
{isUpdateAvailable && <div class="global-menu-button-update-available">
|
{isUpdateAvailable && <div class="global-menu-button-update-available">
|
||||||
<span className="bx bxs-down-arrow-alt global-menu-button-update-available-button" title={t("update_available.update_available")}></span>
|
<span className="bx bxs-down-arrow-alt global-menu-button-update-available-button" title={t("update_available.update_available")} />
|
||||||
</div>}
|
</div>}
|
||||||
</>}
|
</>}
|
||||||
noDropdownListStyle
|
noDropdownListStyle
|
||||||
@@ -57,7 +57,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
|||||||
|
|
||||||
<SwitchToOptions />
|
<SwitchToOptions />
|
||||||
<MenuItem command="showLaunchBarSubtree" icon={`bx ${isMobile() ? "bx-mobile" : "bx-sidebar"}`} text={t("global_menu.configure_launchbar")} />
|
<MenuItem command="showLaunchBarSubtree" icon={`bx ${isMobile() ? "bx-mobile" : "bx-sidebar"}`} text={t("global_menu.configure_launchbar")} />
|
||||||
<AdvancedMenu />
|
<AdvancedMenu dropStart={!isVerticalLayout} />
|
||||||
<MenuItem command="showOptions" icon="bx bx-cog" text={t("global_menu.options")} />
|
<MenuItem command="showOptions" icon="bx bx-cog" text={t("global_menu.options")} />
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
@@ -68,19 +68,19 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
|||||||
{isUpdateAvailable && <>
|
{isUpdateAvailable && <>
|
||||||
<FormListHeader text={t("global_menu.new-version-available")} />
|
<FormListHeader text={t("global_menu.new-version-available")} />
|
||||||
<MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")}
|
<MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")}
|
||||||
icon="bx bx-download"
|
icon="bx bx-download"
|
||||||
text={t("global_menu.download-update", {latestVersion})} />
|
text={t("global_menu.download-update", {latestVersion})} />
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
{!isElectron() && <BrowserOnlyOptions />}
|
{!isElectron() && <BrowserOnlyOptions />}
|
||||||
{glob.isDev && <DevelopmentOptions />}
|
{glob.isDev && <DevelopmentOptions dropStart={!isVerticalLayout} />}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AdvancedMenu() {
|
function AdvancedMenu({ dropStart }: { dropStart: boolean }) {
|
||||||
return (
|
return (
|
||||||
<FormDropdownSubmenu icon="bx bx-chip" title={t("global_menu.advanced")}>
|
<FormDropdownSubmenu icon="bx bx-chip" title={t("global_menu.advanced")} dropStart={dropStart}>
|
||||||
<MenuItem command="showHiddenSubtree" icon="bx bx-hide" text={t("global_menu.show_hidden_subtree")} />
|
<MenuItem command="showHiddenSubtree" icon="bx bx-hide" text={t("global_menu.show_hidden_subtree")} />
|
||||||
<MenuItem command="showSearchHistory" icon="bx bx-search-alt" text={t("global_menu.open_search_history")} />
|
<MenuItem command="showSearchHistory" icon="bx bx-search-alt" text={t("global_menu.open_search_history")} />
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
@@ -103,13 +103,11 @@ function BrowserOnlyOptions() {
|
|||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DevelopmentOptions() {
|
function DevelopmentOptions({ dropStart }: { dropStart: boolean }) {
|
||||||
const [ layoutOrientation ] = useTriliumOption("layoutOrientation");
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
<FormListItem disabled>Development Options</FormListItem>
|
<FormListItem disabled>Development Options</FormListItem>
|
||||||
<FormDropdownSubmenu icon="bx bx-test-tube" title="Experimental features" dropStart={layoutOrientation === "horizontal"}>
|
<FormDropdownSubmenu icon="bx bx-test-tube" title="Experimental features" dropStart={dropStart}>
|
||||||
{experimentalFeatures.map((feature) => (
|
{experimentalFeatures.map((feature) => (
|
||||||
<ExperimentalFeatureToggle key={feature.id} experimentalFeature={feature as ExperimentalFeature} />
|
<ExperimentalFeatureToggle key={feature.id} experimentalFeature={feature as ExperimentalFeature} />
|
||||||
))}
|
))}
|
||||||
@@ -136,10 +134,10 @@ function SwitchToOptions() {
|
|||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
return;
|
return;
|
||||||
} else if (!isMobile()) {
|
} else if (!isMobile()) {
|
||||||
return <MenuItem command="switchToMobileVersion" icon="bx bx-mobile" text={t("global_menu.switch_to_mobile_version")} />
|
return <MenuItem command="switchToMobileVersion" icon="bx bx-mobile" text={t("global_menu.switch_to_mobile_version")} />;
|
||||||
} else {
|
}
|
||||||
return <MenuItem command="switchToDesktopVersion" icon="bx bx-desktop" text={t("global_menu.switch_to_desktop_version")} />
|
return <MenuItem command="switchToDesktopVersion" icon="bx bx-desktop" text={t("global_menu.switch_to_desktop_version")} />;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps<KeyboardActionNames | CommandNames | (() => void)>) {
|
function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps<KeyboardActionNames | CommandNames | (() => void)>) {
|
||||||
@@ -150,7 +148,7 @@ function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProp
|
|||||||
onClick={typeof command === "function" ? command : undefined}
|
onClick={typeof command === "function" ? command : undefined}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
active={active}
|
active={active}
|
||||||
>{text}</FormListItem>
|
>{text}</FormListItem>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<KeyboardActionNames>) {
|
function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<KeyboardActionNames>) {
|
||||||
@@ -158,7 +156,7 @@ function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<Keybo
|
|||||||
{...props}
|
{...props}
|
||||||
command={command}
|
command={command}
|
||||||
text={<>{text} <KeyboardShortcut actionName={command as KeyboardActionNames} /></>}
|
text={<>{text} <KeyboardShortcut actionName={command as KeyboardActionNames} /></>}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function VerticalLayoutIcon() {
|
function VerticalLayoutIcon() {
|
||||||
@@ -181,7 +179,7 @@ function VerticalLayoutIcon() {
|
|||||||
<path className="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
|
<path className="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
|
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
|
||||||
@@ -205,7 +203,7 @@ function ZoomControls({ parentComponent }: { parentComponent?: Component | null
|
|||||||
}}
|
}}
|
||||||
className={`dropdown-item-button ${icon}`}
|
className={`dropdown-item-button ${icon}`}
|
||||||
>{children}</a>
|
>{children}</a>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isElectron() ? (
|
return isElectron() ? (
|
||||||
@@ -246,7 +244,7 @@ function ToggleWindowOnTop() {
|
|||||||
setIsAlwaysOnTop(newState);
|
setIsAlwaysOnTop(newState);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useTriliumUpdateStatus() {
|
function useTriliumUpdateStatus() {
|
||||||
@@ -257,7 +255,7 @@ function useTriliumUpdateStatus() {
|
|||||||
async function updateVersionStatus() {
|
async function updateVersionStatus() {
|
||||||
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
|
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
|
||||||
|
|
||||||
let latestVersion: string | undefined = undefined;
|
let latestVersion: string | undefined;
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(RELEASES_API_URL);
|
const resp = await fetch(RELEASES_API_URL);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|||||||
@@ -217,22 +217,19 @@ export function FormDropdownSubmenu({ icon, title, children, dropStart, onDropdo
|
|||||||
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li className={clsx("dropdown-item dropdown-submenu", { "submenu-open": openOnMobile, "dropstart": dropStart })}>
|
||||||
className={clsx("dropdown-item dropdown-submenu", { "submenu-open": openOnMobile, "dropstart": dropStart })}
|
<span
|
||||||
onClick={(e) => {
|
className="dropdown-toggle"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
|
|
||||||
if (!isMobile() && onDropdownToggleClicked) {
|
|
||||||
onDropdownToggleClicked();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="dropdown-toggle" onClick={(e) => {
|
|
||||||
if (isMobile()) {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setOpenOnMobile(!openOnMobile);
|
|
||||||
}
|
if (isMobile()) {
|
||||||
}}>
|
setOpenOnMobile(!openOnMobile);
|
||||||
|
} else if (onDropdownToggleClicked) {
|
||||||
|
onDropdownToggleClicked();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Icon icon={icon} />{" "}
|
<Icon icon={icon} />{" "}
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons";
|
|||||||
import { useContext } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
import appContext, { CommandNames } from "../../components/app_context";
|
import appContext, { CommandNames } from "../../components/app_context";
|
||||||
|
import Component from "../../components/component";
|
||||||
import NoteContext from "../../components/note_context";
|
import NoteContext from "../../components/note_context";
|
||||||
import FNote from "../../entities/fnote";
|
import FNote from "../../entities/fnote";
|
||||||
import branches from "../../services/branches";
|
import branches from "../../services/branches";
|
||||||
@@ -32,7 +33,7 @@ export default function NoteActions() {
|
|||||||
<div className="ribbon-button-container" style={{ contain: "none" }}>
|
<div className="ribbon-button-container" style={{ contain: "none" }}>
|
||||||
{isNewLayout && (
|
{isNewLayout && (
|
||||||
<>
|
<>
|
||||||
{note && ntxId && <NoteActionsCustom note={note} ntxId={ntxId} />}
|
{note && ntxId && noteContext && <NoteActionsCustom note={note} ntxId={ntxId} noteContext={noteContext} />}
|
||||||
<MovePaneButton direction="left" />
|
<MovePaneButton direction="left" />
|
||||||
<MovePaneButton direction="right" />
|
<MovePaneButton direction="right" />
|
||||||
<ClosePaneButton />
|
<ClosePaneButton />
|
||||||
@@ -66,6 +67,8 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(noteType);
|
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(noteType);
|
||||||
const isInOptionsOrHelp = note?.noteId.startsWith("_options") || note?.noteId.startsWith("_help");
|
const isInOptionsOrHelp = note?.noteId.startsWith("_options") || note?.noteId.startsWith("_help");
|
||||||
const isPrintable = ["text", "code"].includes(noteType) || (noteType === "book" && ["presentation", "list", "table"].includes(viewType ?? ""));
|
const isPrintable = ["text", "code"].includes(noteType) || (noteType === "book" && ["presentation", "list", "table"].includes(viewType ?? ""));
|
||||||
|
const isExportableToImage = ["mermaid", "mindMap"].includes(noteType);
|
||||||
|
const isContentAvailable = note.isContentAvailable();
|
||||||
const isElectron = getIsElectron();
|
const isElectron = getIsElectron();
|
||||||
const isMac = getIsMac();
|
const isMac = getIsMac();
|
||||||
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "aiChat"].includes(noteType);
|
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "aiChat"].includes(noteType);
|
||||||
@@ -110,6 +113,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
defaultType: "single"
|
defaultType: "single"
|
||||||
})} />
|
})} />
|
||||||
{isElectron && <CommandItem command="exportAsPdf" icon="bx bxs-file-pdf" disabled={!isPrintable} text={t("note_actions.print_pdf")} />}
|
{isElectron && <CommandItem command="exportAsPdf" icon="bx bxs-file-pdf" disabled={!isPrintable} text={t("note_actions.print_pdf")} />}
|
||||||
|
{isExportableToImage && isNormalViewMode && isContentAvailable && <ExportAsImage ntxId={noteContext.ntxId} parentComponent={parentComponent} />}
|
||||||
<CommandItem command="printActiveNote" icon="bx bx-printer" disabled={!isPrintable} text={t("note_actions.print_note")} />
|
<CommandItem command="printActiveNote" icon="bx bx-printer" disabled={!isPrintable} text={t("note_actions.print_note")} />
|
||||||
|
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
@@ -280,3 +284,23 @@ function ConvertToAttachment({ note }: { note: FNote }) {
|
|||||||
>{t("note_actions.convert_into_attachment")}</FormListItem>
|
>{t("note_actions.convert_into_attachment")}</FormListItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ExportAsImage({ ntxId, parentComponent }: { ntxId: string | null | undefined, parentComponent: Component | null | undefined }) {
|
||||||
|
return (
|
||||||
|
<FormDropdownSubmenu
|
||||||
|
icon="bx bxs-file-image"
|
||||||
|
title={t("note_actions.export_as_image")}
|
||||||
|
dropStart
|
||||||
|
>
|
||||||
|
<FormListItem
|
||||||
|
icon="bx bxs-file-png"
|
||||||
|
onClick={() => parentComponent?.triggerEvent("exportPng", { ntxId })}
|
||||||
|
>{t("note_actions.export_as_image_png")}</FormListItem>
|
||||||
|
|
||||||
|
<FormListItem
|
||||||
|
icon="bx bx-shape-polygon"
|
||||||
|
onClick={() => parentComponent?.triggerEvent("exportSvg", { ntxId })}
|
||||||
|
>{t("note_actions.export_as_image_svg")}</FormListItem>
|
||||||
|
</FormDropdownSubmenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { NoteType } from "@triliumnext/commons";
|
import { NoteType } from "@triliumnext/commons";
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import Component from "../../components/component";
|
||||||
|
import NoteContext from "../../components/note_context";
|
||||||
import FNote from "../../entities/fnote";
|
import FNote from "../../entities/fnote";
|
||||||
import { t } from "../../services/i18n";
|
import { t } from "../../services/i18n";
|
||||||
|
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||||
import { downloadFileNote, openNoteExternally } from "../../services/open";
|
import { downloadFileNote, openNoteExternally } from "../../services/open";
|
||||||
|
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||||
|
import { ViewTypeOptions } from "../collections/interface";
|
||||||
|
import { buildSaveSqlToNoteHandler } from "../FloatingButtonsDefinitions";
|
||||||
import ActionButton from "../react/ActionButton";
|
import ActionButton from "../react/ActionButton";
|
||||||
import { FormFileUploadActionButton } from "../react/FormFileUpload";
|
import { FormFileUploadActionButton } from "../react/FormFileUpload";
|
||||||
import { useNoteProperty } from "../react/hooks";
|
import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||||
import { ParentComponent } from "../react/react_utils";
|
import { ParentComponent } from "../react/react_utils";
|
||||||
import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab";
|
import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab";
|
||||||
import { buildUploadNewImageRevisionListener } from "./ImagePropertiesTab";
|
import { buildUploadNewImageRevisionListener } from "./ImagePropertiesTab";
|
||||||
@@ -14,10 +20,16 @@ import { buildUploadNewImageRevisionListener } from "./ImagePropertiesTab";
|
|||||||
interface NoteActionsCustomProps {
|
interface NoteActionsCustomProps {
|
||||||
note: FNote;
|
note: FNote;
|
||||||
ntxId: string;
|
ntxId: string;
|
||||||
|
noteContext: NoteContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NoteActionsCustomInnerProps extends NoteActionsCustomProps {
|
interface NoteActionsCustomInnerProps extends NoteActionsCustomProps {
|
||||||
|
noteMime: string;
|
||||||
noteType: NoteType;
|
noteType: NoteType;
|
||||||
|
isReadOnly: boolean;
|
||||||
|
isDefaultViewMode: boolean;
|
||||||
|
parentComponent: Component;
|
||||||
|
viewType: ViewTypeOptions | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,15 +37,33 @@ interface NoteActionsCustomInnerProps extends NoteActionsCustomProps {
|
|||||||
* from the rest of the note items and the buttons differ based on the note type.
|
* from the rest of the note items and the buttons differ based on the note type.
|
||||||
*/
|
*/
|
||||||
export default function NoteActionsCustom(props: NoteActionsCustomProps) {
|
export default function NoteActionsCustom(props: NoteActionsCustomProps) {
|
||||||
const noteType = useNoteProperty(props.note, "type");
|
const { note } = props;
|
||||||
const innerProps: NoteActionsCustomInnerProps | undefined = noteType && {
|
const noteType = useNoteProperty(note, "type");
|
||||||
|
const noteMime = useNoteProperty(note, "mime");
|
||||||
|
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||||
|
const parentComponent = useContext(ParentComponent);
|
||||||
|
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||||
|
const innerProps: NoteActionsCustomInnerProps | false = !!noteType && noteMime !== undefined && !!parentComponent && {
|
||||||
...props,
|
...props,
|
||||||
noteType
|
noteType,
|
||||||
|
noteMime,
|
||||||
|
viewType: viewType as ViewTypeOptions | null | undefined,
|
||||||
|
isDefaultViewMode: props.noteContext.viewScope?.viewMode === "default",
|
||||||
|
parentComponent,
|
||||||
|
isReadOnly
|
||||||
};
|
};
|
||||||
|
|
||||||
return (innerProps &&
|
return (innerProps &&
|
||||||
<div className="note-actions-custom">
|
<div className="note-actions-custom">
|
||||||
|
<AddChildButton {...innerProps} />
|
||||||
|
<RunActiveNoteButton {...innerProps } />
|
||||||
|
<OpenTriliumApiDocsButton {...innerProps} />
|
||||||
|
<SwitchSplitOrientationButton {...innerProps} />
|
||||||
|
<ToggleReadOnlyButton {...innerProps} />
|
||||||
|
<SaveToNoteButton {...innerProps} />
|
||||||
|
<RefreshButton {...innerProps} />
|
||||||
<CopyReferenceToClipboardButton {...innerProps} />
|
<CopyReferenceToClipboardButton {...innerProps} />
|
||||||
|
<InAppHelpButton {...innerProps} />
|
||||||
<NoteActionsCustomInner {...innerProps} />
|
<NoteActionsCustomInner {...innerProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -86,13 +116,13 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OpenExternallyButton({ note }: NoteActionsCustomInnerProps) {
|
function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon="bx bx-link-external"
|
icon="bx bx-link-external"
|
||||||
text={t("file_properties.open")}
|
text={t("file_properties.open")}
|
||||||
disabled={note.isProtected}
|
disabled={note.isProtected}
|
||||||
onClick={() => openNoteExternally(note.noteId, note.mime)}
|
onClick={() => openNoteExternally(note.noteId, noteMime)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -108,9 +138,8 @@ function DownloadFileButton({ note }: NoteActionsCustomInnerProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CopyReferenceToClipboardButton({ ntxId, noteType }: NoteActionsCustomInnerProps) {
|
//#region Floating buttons
|
||||||
const parentComponent = useContext(ParentComponent);
|
function CopyReferenceToClipboardButton({ ntxId, noteType, parentComponent }: NoteActionsCustomInnerProps) {
|
||||||
|
|
||||||
return (["mermaid", "canvas", "mindMap", "image"].includes(noteType) &&
|
return (["mermaid", "canvas", "mindMap", "image"].includes(noteType) &&
|
||||||
<ActionButton
|
<ActionButton
|
||||||
text={t("image_properties.copy_reference_to_clipboard")}
|
text={t("image_properties.copy_reference_to_clipboard")}
|
||||||
@@ -119,4 +148,111 @@ function CopyReferenceToClipboardButton({ ntxId, noteType }: NoteActionsCustomIn
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, noteContext }: NoteActionsCustomInnerProps) {
|
||||||
|
const isEnabled = (note.noteId === "_backendLog" || noteType === "render") && isDefaultViewMode;
|
||||||
|
|
||||||
|
return (isEnabled &&
|
||||||
|
<ActionButton
|
||||||
|
text={t("backend_log.refresh")}
|
||||||
|
icon="bx bx-refresh"
|
||||||
|
onClick={() => parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: NoteActionsCustomInnerProps) {
|
||||||
|
const isShown = note.type === "mermaid" && note.isContentAvailable() && isDefaultViewMode;
|
||||||
|
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
||||||
|
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
|
||||||
|
|
||||||
|
return isShown && <ActionButton
|
||||||
|
text={upcomingOrientation === "vertical" ? t("switch_layout_button.title_vertical") : t("switch_layout_button.title_horizontal")}
|
||||||
|
icon={upcomingOrientation === "vertical" ? "bx bxs-dock-bottom" : "bx bxs-dock-left"}
|
||||||
|
onClick={() => setSplitEditorOrientation(upcomingOrientation)}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActionsCustomInnerProps) {
|
||||||
|
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||||
|
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
|
||||||
|
&& note.isContentAvailable() && isDefaultViewMode;
|
||||||
|
|
||||||
|
return isEnabled && <ActionButton
|
||||||
|
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
|
||||||
|
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
|
||||||
|
onClick={() => setReadOnly(!isReadOnly)}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RunActiveNoteButton({ noteMime }: NoteActionsCustomInnerProps) {
|
||||||
|
const isEnabled = noteMime.startsWith("application/javascript") || noteMime === "text/x-sqlite;schema=trilium";
|
||||||
|
return isEnabled && <ActionButton
|
||||||
|
icon="bx bx-play"
|
||||||
|
text={t("code_buttons.execute_button_title")}
|
||||||
|
triggerCommand="runActiveNote"
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||||
|
const [ isEnabled, setIsEnabled ] = useState(false);
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
setIsEnabled(noteMime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely());
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(refresh, [ note, noteMime ]);
|
||||||
|
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||||
|
if (loadResults.getBranchRows().find(b => b.noteId === note.noteId)) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return isEnabled && <ActionButton
|
||||||
|
icon="bx bx-save"
|
||||||
|
text={t("code_buttons.save_to_note_button_title")}
|
||||||
|
onClick={buildSaveSqlToNoteHandler(note)}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) {
|
||||||
|
const isEnabled = noteMime.startsWith("application/javascript;env=");
|
||||||
|
return isEnabled && <ActionButton
|
||||||
|
icon="bx bx-help-circle"
|
||||||
|
text={t("code_buttons.trilium_api_docs_button_title")}
|
||||||
|
onClick={() => openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InAppHelpButton({ note, noteType }: NoteActionsCustomInnerProps) {
|
||||||
|
const helpUrl = getHelpUrlForNote(note);
|
||||||
|
const isEnabled = !!helpUrl && (noteType !== "book");
|
||||||
|
|
||||||
|
return isEnabled && (
|
||||||
|
<ActionButton
|
||||||
|
icon="bx bx-help-circle"
|
||||||
|
text={t("help-button.title")}
|
||||||
|
onClick={() => helpUrl && openInAppHelpFromUrl(helpUrl)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddChildButton({ parentComponent, noteType, viewType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) {
|
||||||
|
if (noteType === "book" && viewType === "geoMap") {
|
||||||
|
return <ActionButton
|
||||||
|
icon="bx bx-plus-circle"
|
||||||
|
text={t("geo-map.create-child-note-title")}
|
||||||
|
onClick={() => parentComponent.triggerEvent("geoMapCreateChildNote", { ntxId })}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
/>;
|
||||||
|
} else if (noteType === "relationMap") {
|
||||||
|
return <ActionButton
|
||||||
|
icon="bx bx-folder-plus"
|
||||||
|
text={t("relation_map_buttons.create_child_note_title")}
|
||||||
|
onClick={() => parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@@ -451,6 +451,14 @@ body.experimental-feature-new-layout {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
gap: var(--button-gap);
|
gap: var(--button-gap);
|
||||||
|
|
||||||
|
button {
|
||||||
|
transition: opacity 250ms ease-in;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.note-actions-custom {
|
.note-actions-custom {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
|
||||||
import "./code.css";
|
import "./code.css";
|
||||||
import { CodeEditor } from "./Code";
|
|
||||||
import CodeMirror from "@triliumnext/codemirror";
|
import CodeMirror from "@triliumnext/codemirror";
|
||||||
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
|
|
||||||
import server from "../../../services/server";
|
import server from "../../../services/server";
|
||||||
import { useTriliumEvent } from "../../react/hooks";
|
import { useTriliumEvent } from "../../react/hooks";
|
||||||
import { TypeWidgetProps } from "../type_widget";
|
import { TypeWidgetProps } from "../type_widget";
|
||||||
|
import { CodeEditor } from "./Code";
|
||||||
|
|
||||||
export default function BackendLog({ ntxId, parentComponent }: TypeWidgetProps) {
|
export default function BackendLog({ ntxId, parentComponent }: TypeWidgetProps) {
|
||||||
const [ content, setContent ] = useState<string>();
|
const [ content, setContent ] = useState<string>();
|
||||||
@@ -40,5 +42,5 @@ export default function BackendLog({ ntxId, parentComponent }: TypeWidgetProps)
|
|||||||
preferPerformance
|
preferPerformance
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,13 +23,9 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
|
||||||
|
|
||||||
.backend-log-editor {
|
.cm-editor {
|
||||||
flex-grow: 1;
|
font-size: 0.85em;
|
||||||
width: 100%;
|
}
|
||||||
border: none;
|
|
||||||
resize: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|||||||
@@ -43,44 +43,48 @@
|
|||||||
|
|
||||||
/* Horizontal layout */
|
/* Horizontal layout */
|
||||||
|
|
||||||
.note-detail-split.split-horizontal > .note-detail-split-preview-col {
|
.note-detail-split.split-horizontal:not(.split-read-only) {
|
||||||
border-inline-start: 1px solid var(--main-border-color);
|
&> .note-detail-split-preview-col {
|
||||||
}
|
border-inline-start: 1px solid var(--main-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
.note-detail-split.split-horizontal > .note-detail-split-editor-col,
|
&> .note-detail-split-editor-col,
|
||||||
.note-detail-split.split-horizontal > .note-detail-split-preview-col {
|
&> .note-detail-split-preview-col {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-split.split-horizontal .note-detail-split-preview {
|
.note-detail-split-preview {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Vertical layout */
|
/* Vertical layout */
|
||||||
|
|
||||||
.note-detail-split.split-vertical {
|
.note-detail-split.split-vertical {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
&> .note-detail-split-editor-col,
|
||||||
|
&> .note-detail-split-preview-col {
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&> .note-detail-split-editor-col {
|
||||||
|
border-top: 1px solid var(--main-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&> .note-detail-split-preview-col {
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-split.split-vertical > .note-detail-split-editor-col,
|
|
||||||
.note-detail-split.split-vertical > .note-detail-split-preview-col {
|
|
||||||
width: 100%;
|
|
||||||
height: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-split.split-vertical > .note-detail-split-editor-col {
|
|
||||||
border-top: 1px solid var(--main-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-split.split-vertical .note-detail-split-preview-col {
|
|
||||||
order: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read-only view */
|
/* Read-only view */
|
||||||
|
|
||||||
.note-detail-split.split-read-only .note-detail-split-preview-col {
|
.note-detail-split.split-read-only .note-detail-split-preview-col {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #region SVG */
|
/* #region SVG */
|
||||||
@@ -93,4 +97,4 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
||||||
import { TypeWidgetProps } from "../type_widget";
|
|
||||||
import { jsPlumbInstance, OnConnectionBindInfo } from "jsplumb";
|
|
||||||
import { useEditorSpacedUpdate, useTriliumEvent, useTriliumEvents } from "../../react/hooks";
|
|
||||||
import FNote from "../../../entities/fnote";
|
|
||||||
import { RefObject } from "preact";
|
|
||||||
import "./RelationMap.css";
|
import "./RelationMap.css";
|
||||||
import { t } from "../../../services/i18n";
|
|
||||||
|
import { CreateChildrenResponse, RelationMapPostResponse } from "@triliumnext/commons";
|
||||||
|
import { jsPlumbInstance, OnConnectionBindInfo } from "jsplumb";
|
||||||
import panzoom, { PanZoomOptions } from "panzoom";
|
import panzoom, { PanZoomOptions } from "panzoom";
|
||||||
|
import { RefObject } from "preact";
|
||||||
|
import { HTMLProps } from "preact/compat";
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import FNote from "../../../entities/fnote";
|
||||||
|
import attribute_autocomplete from "../../../services/attribute_autocomplete";
|
||||||
import dialog from "../../../services/dialog";
|
import dialog from "../../../services/dialog";
|
||||||
|
import { isExperimentalFeatureEnabled } from "../../../services/experimental_features";
|
||||||
|
import { t } from "../../../services/i18n";
|
||||||
import server from "../../../services/server";
|
import server from "../../../services/server";
|
||||||
import toast from "../../../services/toast";
|
import toast from "../../../services/toast";
|
||||||
import { CreateChildrenResponse, RelationMapPostResponse } from "@triliumnext/commons";
|
|
||||||
import RelationMapApi, { ClientRelation, MapData, MapDataNoteEntry, RelationType } from "./api";
|
|
||||||
import setupOverlays, { uniDirectionalOverlays } from "./overlays";
|
|
||||||
import { JsPlumb } from "./jsplumb";
|
|
||||||
import { getMousePosition, getZoom, idToNoteId, noteIdToId } from "./utils";
|
|
||||||
import { NoteBox } from "./NoteBox";
|
|
||||||
import utils from "../../../services/utils";
|
import utils from "../../../services/utils";
|
||||||
import attribute_autocomplete from "../../../services/attribute_autocomplete";
|
import ActionButton from "../../react/ActionButton";
|
||||||
|
import { useEditorSpacedUpdate, useTriliumEvent, useTriliumEvents } from "../../react/hooks";
|
||||||
|
import { TypeWidgetProps } from "../type_widget";
|
||||||
|
import RelationMapApi, { ClientRelation, MapData, MapDataNoteEntry, RelationType } from "./api";
|
||||||
import { buildRelationContextMenuHandler } from "./context_menu";
|
import { buildRelationContextMenuHandler } from "./context_menu";
|
||||||
import { HTMLProps } from "preact/compat";
|
import { JsPlumb } from "./jsplumb";
|
||||||
|
import { NoteBox } from "./NoteBox";
|
||||||
|
import setupOverlays, { uniDirectionalOverlays } from "./overlays";
|
||||||
|
import { getMousePosition, getZoom, idToNoteId, noteIdToId } from "./utils";
|
||||||
|
|
||||||
|
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||||
|
|
||||||
interface Clipboard {
|
interface Clipboard {
|
||||||
noteId: string;
|
noteId: string;
|
||||||
@@ -43,7 +49,7 @@ declare module "jsplumb" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RelationMap({ note, noteContext, ntxId }: TypeWidgetProps) {
|
export default function RelationMap({ note, noteContext, ntxId, parentComponent }: TypeWidgetProps) {
|
||||||
const [ data, setData ] = useState<MapData>();
|
const [ data, setData ] = useState<MapData>();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const mapApiRef = useRef<RelationMapApi>(null);
|
const mapApiRef = useRef<RelationMapApi>(null);
|
||||||
@@ -119,9 +125,9 @@ export default function RelationMap({ note, noteContext, ntxId }: TypeWidgetProp
|
|||||||
options: {
|
options: {
|
||||||
maxZoom: 2,
|
maxZoom: 2,
|
||||||
minZoom: 0.3,
|
minZoom: 0.3,
|
||||||
smoothScroll: false,
|
smoothScroll: false,
|
||||||
//@ts-expect-error Upstream incorrectly mentions no arguments.
|
//@ts-expect-error Upstream incorrectly mentions no arguments.
|
||||||
filterKey: function (e: KeyboardEvent) {
|
filterKey (e: KeyboardEvent) {
|
||||||
// if ALT is pressed, then panzoom should bubble the event up
|
// if ALT is pressed, then panzoom should bubble the event up
|
||||||
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
|
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
|
||||||
return e.altKey;
|
return e.altKey;
|
||||||
@@ -156,6 +162,34 @@ export default function RelationMap({ note, noteContext, ntxId }: TypeWidgetProp
|
|||||||
<NoteBox {...note} mapApiRef={mapApiRef} />
|
<NoteBox {...note} mapApiRef={mapApiRef} />
|
||||||
))}
|
))}
|
||||||
</JsPlumb>
|
</JsPlumb>
|
||||||
|
|
||||||
|
{isNewLayout && (
|
||||||
|
<div className="btn-group btn-group-sm content-floating-buttons bottom-right">
|
||||||
|
<ActionButton
|
||||||
|
icon="bx bx-zoom-in"
|
||||||
|
text={t("relation_map_buttons.zoom_in_title")}
|
||||||
|
onClick={() => parentComponent?.triggerEvent("relationMapResetZoomIn", { ntxId })}
|
||||||
|
className="tn-tool-button"
|
||||||
|
noIconActionClass
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon="bx bx-zoom-out"
|
||||||
|
text={t("relation_map_buttons.zoom_out_title")}
|
||||||
|
onClick={() => parentComponent?.triggerEvent("relationMapResetZoomOut", { ntxId })}
|
||||||
|
className="tn-tool-button"
|
||||||
|
noIconActionClass
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
icon="bx bx-crop"
|
||||||
|
text={t("relation_map_buttons.reset_pan_zoom_title")}
|
||||||
|
onClick={() => parentComponent?.triggerEvent("relationMapResetPanZoom", { ntxId })}
|
||||||
|
className="tn-tool-button"
|
||||||
|
noIconActionClass
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -380,7 +414,7 @@ function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObjec
|
|||||||
// if there's no event, then this has been triggered programmatically
|
// if there's no event, then this has been triggered programmatically
|
||||||
if (!originalEvent || !mapApiRef.current) return;
|
if (!originalEvent || !mapApiRef.current) return;
|
||||||
|
|
||||||
let name = await dialog.prompt({
|
const name = await dialog.prompt({
|
||||||
message: t("relation_map.specify_new_relation_name"),
|
message: t("relation_map.specify_new_relation_name"),
|
||||||
shown: ({ $answer }) => {
|
shown: ({ $answer }) => {
|
||||||
if (!$answer) {
|
if (!$answer) {
|
||||||
|
|||||||
Reference in New Issue
Block a user