Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/app/element/markdown-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export const resolveRemoteFile = async (filepath: string, resolveOpts: MarkdownR
const baseDirUri = formatRemoteUri(resolveOpts.baseDir, resolveOpts.connName);
const fileInfo = await RpcApi.FileJoinCommand(TabRpcClient, [baseDirUri, filepath]);
const remoteUri = formatRemoteUri(fileInfo.path, resolveOpts.connName);
console.log("markdown resolve", resolveOpts, filepath, "=>", baseDirUri, remoteUri);
// console.log("markdown resolve", resolveOpts, filepath, "=>", baseDirUri, remoteUri);
const usp = new URLSearchParams();
usp.set("path", remoteUri);
return getWebServerEndpoint() + "/wave/stream-file?" + usp.toString();
Expand Down
43 changes: 28 additions & 15 deletions frontend/app/element/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
transformBlocks,
} from "@/app/element/markdown-util";
import remarkMermaidToTag from "@/app/element/remark-mermaid-to-tag";
import { boundNumber, useAtomValueSafe } from "@/util/util";
import { boundNumber, useAtomValueSafe, cn } from "@/util/util";
import clsx from "clsx";
import { Atom } from "jotai";
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
Expand Down Expand Up @@ -297,6 +297,7 @@ type MarkdownProps = {
showTocAtom?: Atom<boolean>;
style?: React.CSSProperties;
className?: string;
contentClassName?: string;
onClickExecute?: (cmd: string) => void;
resolveOpts?: MarkdownResolveOpts;
scrollable?: boolean;
Expand All @@ -311,6 +312,7 @@ const Markdown = ({
showTocAtom,
style,
className,
contentClassName,
resolveOpts,
fontSizeOverride,
fixedFontSizeOverride,
Expand Down Expand Up @@ -383,19 +385,30 @@ const Markdown = ({
};

const toc = useMemo(() => {
if (showToc && tocRef.current.length > 0) {
return tocRef.current.map((item) => {
if (showToc) {
if (tocRef.current.length > 0) {
return tocRef.current.map((item) => {
return (
<a
key={item.href}
className="toc-item"
style={{ "--indent-factor": item.depth } as React.CSSProperties}
onClick={() => setFocusedHeading(item.href)}
>
{item.value}
</a>
);
});
} else {
return (
<a
key={item.href}
className="toc-item"
style={{ "--indent-factor": item.depth } as React.CSSProperties}
onClick={() => setFocusedHeading(item.href)}
<div
className="toc-item toc-empty text-secondary"
style={{ "--indent-factor": 2 } as React.CSSProperties}
>
{item.value}
</a>
No sub-headings found
</div>
);
});
}
}
}, [showToc, tocRef]);
Comment on lines +388 to 413
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add href to TOC anchors and re-compute TOC when content changes.

Anchors lack href (accessibility/keyboard issue). TOC memo depends only on showToc, so it can go stale when text changes.

Apply this diff:

-    const toc = useMemo(() => {
-        if (showToc) {
-            if (tocRef.current.length > 0) {
-                return tocRef.current.map((item) => {
-                    return (
-                        <a
-                            key={item.href}
-                            className="toc-item"
-                            style={{ "--indent-factor": item.depth } as React.CSSProperties}
-                            onClick={() => setFocusedHeading(item.href)}
-                        >
-                            {item.value}
-                        </a>
-                    );
-                });
-            } else {
+    const toc = useMemo(() => {
+        if (showToc) {
+            if (tocRef.current.length > 0) {
+                return tocRef.current.map((item) => (
+                    <a
+                        key={item.href}
+                        href={item.href}
+                        className="toc-item"
+                        style={{ "--indent-factor": item.depth } as React.CSSProperties}
+                        onClick={(e) => {
+                            e.preventDefault();
+                            setFocusedHeading(item.href);
+                        }}
+                    >
+                        {item.value}
+                    </a>
+                ));
+            } else {
                 return (
                     <div
                         className="toc-item toc-empty text-secondary"
                         style={{ "--indent-factor": 2 } as React.CSSProperties}
                     >
                         No sub-headings found
                     </div>
                 );
-            }
-        }
-    }, [showToc, tocRef]);
+            }
+        }
+    }, [showToc, transformedText]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (showToc) {
if (tocRef.current.length > 0) {
return tocRef.current.map((item) => {
return (
<a
key={item.href}
className="toc-item"
style={{ "--indent-factor": item.depth } as React.CSSProperties}
onClick={() => setFocusedHeading(item.href)}
>
{item.value}
</a>
);
});
} else {
return (
<a
key={item.href}
className="toc-item"
style={{ "--indent-factor": item.depth } as React.CSSProperties}
onClick={() => setFocusedHeading(item.href)}
<div
className="toc-item toc-empty text-secondary"
style={{ "--indent-factor": 2 } as React.CSSProperties}
>
{item.value}
</a>
No sub-headings found
</div>
);
});
}
}
}, [showToc, tocRef]);
const toc = useMemo(() => {
if (showToc) {
if (tocRef.current.length > 0) {
return tocRef.current.map((item) => (
<a
key={item.href}
href={item.href}
className="toc-item"
style={{ "--indent-factor": item.depth } as React.CSSProperties}
onClick={(e) => {
e.preventDefault();
setFocusedHeading(item.href);
}}
>
{item.value}
</a>
));
} else {
return (
<div
className="toc-item toc-empty text-secondary"
style={{ "--indent-factor": 2 } as React.CSSProperties}
>
No sub-headings found
</div>
);
}
}
}, [showToc, transformedText]);


Expand Down Expand Up @@ -444,7 +457,7 @@ const Markdown = ({
return (
<OverlayScrollbarsComponent
ref={contentsOsRef}
className="content"
className={cn("content", contentClassName)}
options={{ scrollbars: { autoHide: "leave" } }}
>
<ReactMarkdown
Expand All @@ -460,7 +473,7 @@ const Markdown = ({

const NonScrollableMarkdown = () => {
return (
<div className="content non-scrollable">
<div className={cn("content non-scrollable", contentClassName)}>
<ReactMarkdown
remarkPlugins={remarkPlugins}
rehypePlugins={rehypePlugins}
Expand All @@ -483,9 +496,9 @@ const Markdown = ({
<div className={clsx("markdown", className)} style={mergedStyle}>
{scrollable ? <ScrollableMarkdown /> : <NonScrollableMarkdown />}
{toc && (
<OverlayScrollbarsComponent className="toc" options={{ scrollbars: { autoHide: "leave" } }}>
<OverlayScrollbarsComponent className="toc mt-1" options={{ scrollbars: { autoHide: "leave" } }}>
<div className="toc-inner">
<h4>Table of Contents</h4>
<h4 className="font-bold">Table of Contents</h4>
{toc}
</div>
</OverlayScrollbarsComponent>
Expand Down
37 changes: 7 additions & 30 deletions frontend/app/view/codeeditor/codeeditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import { configureMonacoYaml } from "monaco-yaml";
import React, { useMemo, useRef } from "react";

import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { boundNumber, makeConnRoute } from "@/util/util";
import { boundNumber } from "@/util/util";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
Expand All @@ -19,7 +17,6 @@ import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"
import { SchemaEndpoints, getSchemaEndpointInfo } from "./schemaendpoints";
import ymlWorker from "./yamlworker?worker";


// there is a global monaco variable (TODO get the correct TS type)
declare var monaco: Monaco;

Expand Down Expand Up @@ -109,23 +106,21 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions {
interface CodeEditorProps {
blockId: string;
text: string;
filename: string;
fileinfo: FileInfo;
readonly: boolean;
language?: string;
meta?: MetaType;
onChange?: (text: string) => void;
onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => () => void;
}

export function CodeEditor({ blockId, text, language, filename, fileinfo, meta, onChange, onMount }: CodeEditorProps) {
export function CodeEditor({ blockId, text, language, readonly, onChange, onMount }: CodeEditorProps) {
const divRef = useRef<HTMLDivElement>(null);
const unmountRef = useRef<() => void>(null);
const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false;
const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false;
const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false;
const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64);
const theme = "wave-theme-dark";
const [absPath, setAbsPath] = React.useState("");
const editorPath = useRef(crypto.randomUUID()).current;

React.useEffect(() => {
return () => {
Expand All @@ -136,24 +131,6 @@ export function CodeEditor({ blockId, text, language, filename, fileinfo, meta,
};
}, []);

React.useEffect(() => {
const inner = async () => {
try {
const fileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [filename], {
route: makeConnRoute(meta.connection ?? ""),
});
setAbsPath(fileInfo.path);
} catch (e) {
setAbsPath(filename);
}
};
inner();
}, [filename]);

React.useEffect(() => {
console.log("abspath is", absPath);
}, [absPath]);

function handleEditorChange(text: string, ev: MonacoTypes.editor.IModelContentChangedEvent) {
if (onChange) {
onChange(text);
Expand All @@ -168,13 +145,13 @@ export function CodeEditor({ blockId, text, language, filename, fileinfo, meta,

const editorOpts = useMemo(() => {
const opts = defaultEditorOptions();
opts.readOnly = fileinfo.readonly;
opts.readOnly = readonly;
opts.minimap.enabled = minimapEnabled;
opts.stickyScroll.enabled = stickyScrollEnabled;
opts.wordWrap = wordWrap ? "on" : "off";
opts.fontSize = fontSize;
return opts;
}, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, fileinfo.readonly]);
}, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, readonly]);

return (
<div className="flex flex-col w-full h-full overflow-hidden items-center justify-center">
Expand All @@ -185,7 +162,7 @@ export function CodeEditor({ blockId, text, language, filename, fileinfo, meta,
options={editorOpts}
onChange={handleEditorChange}
onMount={handleEditorOnMount}
path={absPath}
path={editorPath}
language={language}
/>
</div>
Expand Down
56 changes: 0 additions & 56 deletions frontend/app/view/preview/directorypreview.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,6 @@
display: flex;
flex-direction: column;
padding: 0 5px 5px 5px;
.dir-table-body-search-display {
display: flex;
border-radius: 3px;
padding: 0.25rem 0.5rem;
background-color: var(--warning-color);

.search-display-close-button {
margin-left: auto;
}
}

.dir-table-body-scroll-box {
position: relative;
Expand Down Expand Up @@ -185,52 +175,6 @@
}
}
}

.dir-table-search-line {
display: flex;
justify-content: flex-end;
gap: 0.7rem;

.dir-table-search-box {
width: 0;
height: 0;
opacity: 0;
padding: 0;
border: none;
pointer-events: none;
}
}
}

.dir-table-button {
background-color: transparent;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 0.2rem;
border-radius: 6px;

input {
width: 0;
height: 0;
opacity: 0;
padding: 0;
border: none;
pointer-events: none;
}

&:hover {
background-color: var(--highlight-bg-color);
}

&:focus {
background-color: var(--highlight-bg-color);
}

&:focus-within {
background-color: var(--highlight-bg-color);
}
}

.entry-manager-overlay {
Expand Down
65 changes: 65 additions & 0 deletions frontend/app/view/preview/entry-manager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { Button } from "@/app/element/button";
import { Input } from "@/app/element/input";
import React, { memo, useState } from "react";

export enum EntryManagerType {
NewFile = "New File",
NewDirectory = "New Folder",
EditName = "Rename",
}

export type EntryManagerOverlayProps = {
forwardRef?: React.Ref<HTMLDivElement>;
entryManagerType: EntryManagerType;
startingValue?: string;
onSave: (newValue: string) => void;
onCancel?: () => void;
style?: React.CSSProperties;
getReferenceProps?: () => any;
};

export const EntryManagerOverlay = memo(
({
entryManagerType,
startingValue,
onSave,
onCancel,
forwardRef,
style,
getReferenceProps,
}: EntryManagerOverlayProps) => {
const [value, setValue] = useState(startingValue);
return (
<div className="entry-manager-overlay" ref={forwardRef} style={style} {...(getReferenceProps?.() ?? {})}>
<div className="entry-manager-type">{entryManagerType}</div>
<div className="entry-manager-input">
<Input
value={value}
onChange={setValue}
autoFocus={true}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
onSave(value);
}
}}
/>
</div>
<div className="entry-manager-buttons">
<Button className="vertical-padding-4" onClick={() => onSave(value)}>
Save
</Button>
<Button className="vertical-padding-4 red outlined" onClick={onCancel}>
Cancel
</Button>
</div>
</div>
);
}
);

EntryManagerOverlay.displayName = "EntryManagerOverlay";
Loading
Loading