mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Add markdown alert parsing, fix buffer issue when switching files (#988)
Adds the GitHub alert syntax parsing to the markdown element, fixes an issue where the file edit buffer was not getting unset when the file path changed, continues my crusade on star imports
This commit is contained in:
parent
2e27296677
commit
d2b2491211
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
To report vulnerabilities or security concerns, please email us at: [security@commandline.dev](mailto:security@commandline.dev)
|
To report vulnerabilities or security concerns, please email us at: [security@commandline.dev](mailto:security@commandline.dev)
|
||||||
|
|
||||||
** Please do not report security vulnerabilities through public github issues. **
|
**Please do not report security vulnerabilities through public github issues.**
|
@ -17,6 +17,7 @@ import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
|
|||||||
import rehypeSlug from "rehype-slug";
|
import rehypeSlug from "rehype-slug";
|
||||||
import RemarkFlexibleToc, { TocItem } from "remark-flexible-toc";
|
import RemarkFlexibleToc, { TocItem } from "remark-flexible-toc";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import { remarkAlert } from "remark-github-blockquote-alert";
|
||||||
import { openLink } from "../store/global";
|
import { openLink } from "../store/global";
|
||||||
import { IconButton } from "./iconbutton";
|
import { IconButton } from "./iconbutton";
|
||||||
import "./markdown.less";
|
import "./markdown.less";
|
||||||
@ -258,7 +259,7 @@ const Markdown = ({
|
|||||||
options={{ scrollbars: { autoHide: "leave" } }}
|
options={{ scrollbars: { autoHide: "leave" } }}
|
||||||
>
|
>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm, [RemarkFlexibleToc, { tocRef: tocRef.current }]]}
|
remarkPlugins={[remarkGfm, remarkAlert, [RemarkFlexibleToc, { tocRef: tocRef.current }]]}
|
||||||
rehypePlugins={[
|
rehypePlugins={[
|
||||||
rehypeRaw,
|
rehypeRaw,
|
||||||
rehypeHighlight,
|
rehypeHighlight,
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
// Copyright 2024, Command Line Inc.
|
// Copyright 2024, Command Line Inc.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { CenteredDiv } from "@/app/element/quickelems";
|
||||||
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
|
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
|
||||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||||
import { tryReinjectKey } from "@/app/store/keymodel";
|
import { tryReinjectKey } from "@/app/store/keymodel";
|
||||||
import { RpcApi } from "@/app/store/wshclientapi";
|
import { RpcApi } from "@/app/store/wshclientapi";
|
||||||
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
||||||
|
import { CodeEditor } from "@/app/view/codeeditor/codeeditor";
|
||||||
import { Markdown } from "@/element/markdown";
|
import { Markdown } from "@/element/markdown";
|
||||||
import { NodeModel } from "@/layout/index";
|
import { NodeModel } from "@/layout/index";
|
||||||
import { atoms, createBlock, getConnStatusAtom, getSettingsKeyAtom, globalStore, refocusNode } from "@/store/global";
|
import { atoms, createBlock, getConnStatusAtom, getSettingsKeyAtom, globalStore, refocusNode } from "@/store/global";
|
||||||
import * as services from "@/store/services";
|
import * as services from "@/store/services";
|
||||||
import * as WOS from "@/store/wos";
|
import * as WOS from "@/store/wos";
|
||||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||||
import * as historyutil from "@/util/historyutil";
|
import { goHistory, goHistoryBack, goHistoryForward } from "@/util/historyutil";
|
||||||
import * as keyutil from "@/util/keyutil";
|
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil";
|
||||||
import * as util from "@/util/util";
|
import { base64ToString, isBlank, jotaiLoadableValue, makeConnRoute, stringToBase64 } from "@/util/util";
|
||||||
import { makeConnRoute } from "@/util/util";
|
|
||||||
import { Monaco } from "@monaco-editor/react";
|
import { Monaco } from "@monaco-editor/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import * as jotai from "jotai";
|
import { Atom, atom, Getter, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai";
|
||||||
import { loadable } from "jotai/utils";
|
import { loadable } from "jotai/utils";
|
||||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||||
import * as React from "react";
|
import { createRef, memo, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { createRef, useCallback, useState } from "react";
|
|
||||||
import { CenteredDiv } from "../../element/quickelems";
|
|
||||||
import { CodeEditor } from "../codeeditor/codeeditor";
|
|
||||||
import { CSVView } from "./csvview";
|
import { CSVView } from "./csvview";
|
||||||
import { DirectoryPreview } from "./directorypreview";
|
import { DirectoryPreview } from "./directorypreview";
|
||||||
import "./preview.less";
|
import "./preview.less";
|
||||||
@ -101,70 +99,64 @@ export class PreviewModel implements ViewModel {
|
|||||||
viewType: string;
|
viewType: string;
|
||||||
blockId: string;
|
blockId: string;
|
||||||
nodeModel: NodeModel;
|
nodeModel: NodeModel;
|
||||||
blockAtom: jotai.Atom<Block>;
|
blockAtom: Atom<Block>;
|
||||||
viewIcon: jotai.Atom<string | IconButtonDecl>;
|
viewIcon: Atom<string | IconButtonDecl>;
|
||||||
viewName: jotai.Atom<string>;
|
viewName: Atom<string>;
|
||||||
viewText: jotai.Atom<HeaderElem[]>;
|
viewText: Atom<HeaderElem[]>;
|
||||||
preIconButton: jotai.Atom<IconButtonDecl>;
|
preIconButton: Atom<IconButtonDecl>;
|
||||||
endIconButtons: jotai.Atom<IconButtonDecl[]>;
|
endIconButtons: Atom<IconButtonDecl[]>;
|
||||||
previewTextRef: React.RefObject<HTMLDivElement>;
|
previewTextRef: React.RefObject<HTMLDivElement>;
|
||||||
editMode: jotai.Atom<boolean>;
|
editMode: Atom<boolean>;
|
||||||
canPreview: jotai.PrimitiveAtom<boolean>;
|
canPreview: PrimitiveAtom<boolean>;
|
||||||
specializedView: jotai.Atom<Promise<{ specializedView?: string; errorStr?: string }>>;
|
specializedView: Atom<Promise<{ specializedView?: string; errorStr?: string }>>;
|
||||||
loadableSpecializedView: jotai.Atom<Loadable<{ specializedView?: string; errorStr?: string }>>;
|
loadableSpecializedView: Atom<Loadable<{ specializedView?: string; errorStr?: string }>>;
|
||||||
manageConnection: jotai.Atom<boolean>;
|
manageConnection: Atom<boolean>;
|
||||||
connStatus: jotai.Atom<ConnStatus>;
|
connStatus: Atom<ConnStatus>;
|
||||||
|
|
||||||
metaFilePath: jotai.Atom<string>;
|
metaFilePath: Atom<string>;
|
||||||
statFilePath: jotai.Atom<Promise<string>>;
|
statFilePath: Atom<Promise<string>>;
|
||||||
normFilePath: jotai.Atom<Promise<string>>;
|
normFilePath: Atom<Promise<string>>;
|
||||||
loadableStatFilePath: jotai.Atom<Loadable<string>>;
|
loadableStatFilePath: Atom<Loadable<string>>;
|
||||||
loadableFileInfo: jotai.Atom<Loadable<FileInfo>>;
|
loadableFileInfo: Atom<Loadable<FileInfo>>;
|
||||||
connection: jotai.Atom<string>;
|
connection: Atom<string>;
|
||||||
statFile: jotai.Atom<Promise<FileInfo>>;
|
statFile: Atom<Promise<FileInfo>>;
|
||||||
fullFile: jotai.Atom<Promise<FullFile>>;
|
fullFile: Atom<Promise<FullFile>>;
|
||||||
fileMimeType: jotai.Atom<Promise<string>>;
|
fileMimeType: Atom<Promise<string>>;
|
||||||
fileMimeTypeLoadable: jotai.Atom<Loadable<string>>;
|
fileMimeTypeLoadable: Atom<Loadable<string>>;
|
||||||
fileContentSaved: jotai.PrimitiveAtom<string | null>;
|
fileContentSaved: PrimitiveAtom<string | null>;
|
||||||
fileContent: jotai.WritableAtom<Promise<string>, [string], void>;
|
fileContent: WritableAtom<Promise<string>, [string], void>;
|
||||||
newFileContent: jotai.PrimitiveAtom<string | null>;
|
newFileContent: PrimitiveAtom<string | null>;
|
||||||
|
|
||||||
openFileModal: jotai.PrimitiveAtom<boolean>;
|
openFileModal: PrimitiveAtom<boolean>;
|
||||||
openFileError: jotai.PrimitiveAtom<string>;
|
openFileError: PrimitiveAtom<string>;
|
||||||
openFileModalGiveFocusRef: React.MutableRefObject<() => boolean>;
|
openFileModalGiveFocusRef: React.MutableRefObject<() => boolean>;
|
||||||
|
|
||||||
markdownShowToc: jotai.PrimitiveAtom<boolean>;
|
markdownShowToc: PrimitiveAtom<boolean>;
|
||||||
|
|
||||||
monacoRef: React.MutableRefObject<MonacoTypes.editor.IStandaloneCodeEditor>;
|
monacoRef: React.MutableRefObject<MonacoTypes.editor.IStandaloneCodeEditor>;
|
||||||
|
|
||||||
showHiddenFiles: jotai.PrimitiveAtom<boolean>;
|
showHiddenFiles: PrimitiveAtom<boolean>;
|
||||||
refreshVersion: jotai.PrimitiveAtom<number>;
|
refreshVersion: PrimitiveAtom<number>;
|
||||||
refreshCallback: () => void;
|
refreshCallback: () => void;
|
||||||
directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
|
directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
|
||||||
codeEditKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
|
codeEditKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
|
||||||
|
|
||||||
setPreviewFileName(fileName: string) {
|
|
||||||
globalStore.set(this.fileContentSaved, null);
|
|
||||||
globalStore.set(this.newFileContent, null);
|
|
||||||
services.ObjectService.UpdateObjectMeta(`block:${this.blockId}`, { file: fileName });
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(blockId: string, nodeModel: NodeModel) {
|
constructor(blockId: string, nodeModel: NodeModel) {
|
||||||
this.viewType = "preview";
|
this.viewType = "preview";
|
||||||
this.blockId = blockId;
|
this.blockId = blockId;
|
||||||
this.nodeModel = nodeModel;
|
this.nodeModel = nodeModel;
|
||||||
let showHiddenFiles = globalStore.get(getSettingsKeyAtom("preview:showhiddenfiles")) ?? true;
|
let showHiddenFiles = globalStore.get(getSettingsKeyAtom("preview:showhiddenfiles")) ?? true;
|
||||||
this.showHiddenFiles = jotai.atom<boolean>(showHiddenFiles);
|
this.showHiddenFiles = atom<boolean>(showHiddenFiles);
|
||||||
this.refreshVersion = jotai.atom(0);
|
this.refreshVersion = atom(0);
|
||||||
this.previewTextRef = createRef();
|
this.previewTextRef = createRef();
|
||||||
this.openFileModal = jotai.atom(false);
|
this.openFileModal = atom(false);
|
||||||
this.openFileError = jotai.atom(null) as jotai.PrimitiveAtom<string>;
|
this.openFileError = atom(null) as PrimitiveAtom<string>;
|
||||||
this.openFileModalGiveFocusRef = createRef();
|
this.openFileModalGiveFocusRef = createRef();
|
||||||
this.manageConnection = jotai.atom(true);
|
this.manageConnection = atom(true);
|
||||||
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
||||||
this.markdownShowToc = jotai.atom(false);
|
this.markdownShowToc = atom(false);
|
||||||
this.monacoRef = createRef();
|
this.monacoRef = createRef();
|
||||||
this.viewIcon = jotai.atom((get) => {
|
this.viewIcon = atom((get) => {
|
||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
if (blockData?.meta?.icon) {
|
if (blockData?.meta?.icon) {
|
||||||
return blockData.meta.icon;
|
return blockData.meta.icon;
|
||||||
@ -173,9 +165,8 @@ export class PreviewModel implements ViewModel {
|
|||||||
if (connStatus?.status != "connected") {
|
if (connStatus?.status != "connected") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const fileName = get(this.metaFilePath);
|
|
||||||
const mimeTypeLoadable = get(this.fileMimeTypeLoadable);
|
const mimeTypeLoadable = get(this.fileMimeTypeLoadable);
|
||||||
const mimeType = util.jotaiLoadableValue(mimeTypeLoadable, "");
|
const mimeType = jotaiLoadableValue(mimeTypeLoadable, "");
|
||||||
if (mimeType == "directory") {
|
if (mimeType == "directory") {
|
||||||
return {
|
return {
|
||||||
elemtype: "iconbutton",
|
elemtype: "iconbutton",
|
||||||
@ -208,12 +199,12 @@ export class PreviewModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
return iconForFile(mimeType);
|
return iconForFile(mimeType);
|
||||||
});
|
});
|
||||||
this.editMode = jotai.atom((get) => {
|
this.editMode = atom((get) => {
|
||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
return blockData?.meta?.edit ?? false;
|
return blockData?.meta?.edit ?? false;
|
||||||
});
|
});
|
||||||
this.viewName = jotai.atom("Preview");
|
this.viewName = atom("Preview");
|
||||||
this.viewText = jotai.atom((get) => {
|
this.viewText = atom((get) => {
|
||||||
let headerPath = get(this.metaFilePath);
|
let headerPath = get(this.metaFilePath);
|
||||||
const connStatus = get(this.connStatus);
|
const connStatus = get(this.connStatus);
|
||||||
if (connStatus?.status != "connected") {
|
if (connStatus?.status != "connected") {
|
||||||
@ -282,12 +273,12 @@ export class PreviewModel implements ViewModel {
|
|||||||
},
|
},
|
||||||
] as HeaderElem[];
|
] as HeaderElem[];
|
||||||
});
|
});
|
||||||
this.preIconButton = jotai.atom((get) => {
|
this.preIconButton = atom((get) => {
|
||||||
const connStatus = get(this.connStatus);
|
const connStatus = get(this.connStatus);
|
||||||
if (connStatus?.status != "connected") {
|
if (connStatus?.status != "connected") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const mimeType = util.jotaiLoadableValue(get(this.fileMimeTypeLoadable), "");
|
const mimeType = jotaiLoadableValue(get(this.fileMimeTypeLoadable), "");
|
||||||
const metaPath = get(this.metaFilePath);
|
const metaPath = get(this.metaFilePath);
|
||||||
if (mimeType == "directory" && metaPath == "/") {
|
if (mimeType == "directory" && metaPath == "/") {
|
||||||
return null;
|
return null;
|
||||||
@ -298,12 +289,12 @@ export class PreviewModel implements ViewModel {
|
|||||||
click: this.goParentDirectory.bind(this),
|
click: this.goParentDirectory.bind(this),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.endIconButtons = jotai.atom((get) => {
|
this.endIconButtons = atom((get) => {
|
||||||
const connStatus = get(this.connStatus);
|
const connStatus = get(this.connStatus);
|
||||||
if (connStatus?.status != "connected") {
|
if (connStatus?.status != "connected") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const mimeType = util.jotaiLoadableValue(get(this.fileMimeTypeLoadable), "");
|
const mimeType = jotaiLoadableValue(get(this.fileMimeTypeLoadable), "");
|
||||||
const loadableSV = get(this.loadableSpecializedView);
|
const loadableSV = get(this.loadableSpecializedView);
|
||||||
const isCeView = loadableSV.state == "hasData" && loadableSV.data.specializedView == "codeedit";
|
const isCeView = loadableSV.state == "hasData" && loadableSV.data.specializedView == "codeedit";
|
||||||
if (mimeType == "directory") {
|
if (mimeType == "directory") {
|
||||||
@ -335,18 +326,18 @@ export class PreviewModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
this.metaFilePath = jotai.atom<string>((get) => {
|
this.metaFilePath = atom<string>((get) => {
|
||||||
const file = get(this.blockAtom)?.meta?.file;
|
const file = get(this.blockAtom)?.meta?.file;
|
||||||
if (util.isBlank(file)) {
|
if (isBlank(file)) {
|
||||||
return "~";
|
return "~";
|
||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
});
|
});
|
||||||
this.statFilePath = jotai.atom<Promise<string>>(async (get) => {
|
this.statFilePath = atom<Promise<string>>(async (get) => {
|
||||||
const fileInfo = await get(this.statFile);
|
const fileInfo = await get(this.statFile);
|
||||||
return fileInfo?.path;
|
return fileInfo?.path;
|
||||||
});
|
});
|
||||||
this.normFilePath = jotai.atom<Promise<string>>(async (get) => {
|
this.normFilePath = atom<Promise<string>>(async (get) => {
|
||||||
const fileInfo = await get(this.statFile);
|
const fileInfo = await get(this.statFile);
|
||||||
if (fileInfo == null) {
|
if (fileInfo == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -357,10 +348,10 @@ export class PreviewModel implements ViewModel {
|
|||||||
return fileInfo.dir + "/" + fileInfo.name;
|
return fileInfo.dir + "/" + fileInfo.name;
|
||||||
});
|
});
|
||||||
this.loadableStatFilePath = loadable(this.statFilePath);
|
this.loadableStatFilePath = loadable(this.statFilePath);
|
||||||
this.connection = jotai.atom<string>((get) => {
|
this.connection = atom<string>((get) => {
|
||||||
return get(this.blockAtom)?.meta?.connection;
|
return get(this.blockAtom)?.meta?.connection;
|
||||||
});
|
});
|
||||||
this.statFile = jotai.atom<Promise<FileInfo>>(async (get) => {
|
this.statFile = atom<Promise<FileInfo>>(async (get) => {
|
||||||
const fileName = get(this.metaFilePath);
|
const fileName = get(this.metaFilePath);
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -369,15 +360,15 @@ export class PreviewModel implements ViewModel {
|
|||||||
const statFile = await services.FileService.StatFile(conn, fileName);
|
const statFile = await services.FileService.StatFile(conn, fileName);
|
||||||
return statFile;
|
return statFile;
|
||||||
});
|
});
|
||||||
this.fileMimeType = jotai.atom<Promise<string>>(async (get) => {
|
this.fileMimeType = atom<Promise<string>>(async (get) => {
|
||||||
const fileInfo = await get(this.statFile);
|
const fileInfo = await get(this.statFile);
|
||||||
return fileInfo?.mimetype;
|
return fileInfo?.mimetype;
|
||||||
});
|
});
|
||||||
this.fileMimeTypeLoadable = loadable(this.fileMimeType);
|
this.fileMimeTypeLoadable = loadable(this.fileMimeType);
|
||||||
this.newFileContent = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
|
this.newFileContent = atom(null) as PrimitiveAtom<string | null>;
|
||||||
this.goParentDirectory = this.goParentDirectory.bind(this);
|
this.goParentDirectory = this.goParentDirectory.bind(this);
|
||||||
|
|
||||||
const fullFileAtom = jotai.atom<Promise<FullFile>>(async (get) => {
|
const fullFileAtom = atom<Promise<FullFile>>(async (get) => {
|
||||||
const fileName = get(this.metaFilePath);
|
const fileName = get(this.metaFilePath);
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -387,8 +378,8 @@ export class PreviewModel implements ViewModel {
|
|||||||
return file;
|
return file;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fileContentSaved = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
|
this.fileContentSaved = atom(null) as PrimitiveAtom<string | null>;
|
||||||
const fileContentAtom = jotai.atom(
|
const fileContentAtom = atom(
|
||||||
async (get) => {
|
async (get) => {
|
||||||
const _ = get(this.metaFilePath);
|
const _ = get(this.metaFilePath);
|
||||||
const newContent = get(this.newFileContent);
|
const newContent = get(this.newFileContent);
|
||||||
@ -400,7 +391,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
return savedContent;
|
return savedContent;
|
||||||
}
|
}
|
||||||
const fullFile = await get(fullFileAtom);
|
const fullFile = await get(fullFileAtom);
|
||||||
return util.base64ToString(fullFile?.data64);
|
return base64ToString(fullFile?.data64);
|
||||||
},
|
},
|
||||||
(get, set, update: string) => {
|
(get, set, update: string) => {
|
||||||
set(this.fileContentSaved, update);
|
set(this.fileContentSaved, update);
|
||||||
@ -410,13 +401,13 @@ export class PreviewModel implements ViewModel {
|
|||||||
this.fullFile = fullFileAtom;
|
this.fullFile = fullFileAtom;
|
||||||
this.fileContent = fileContentAtom;
|
this.fileContent = fileContentAtom;
|
||||||
|
|
||||||
this.specializedView = jotai.atom<Promise<{ specializedView?: string; errorStr?: string }>>(async (get) => {
|
this.specializedView = atom<Promise<{ specializedView?: string; errorStr?: string }>>(async (get) => {
|
||||||
return this.getSpecializedView(get);
|
return this.getSpecializedView(get);
|
||||||
});
|
});
|
||||||
this.loadableSpecializedView = loadable(this.specializedView);
|
this.loadableSpecializedView = loadable(this.specializedView);
|
||||||
this.canPreview = jotai.atom(false);
|
this.canPreview = atom(false);
|
||||||
this.loadableFileInfo = loadable(this.statFile);
|
this.loadableFileInfo = loadable(this.statFile);
|
||||||
this.connStatus = jotai.atom((get) => {
|
this.connStatus = atom((get) => {
|
||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
const connName = blockData?.meta?.connection;
|
const connName = blockData?.meta?.connection;
|
||||||
const connAtom = getConnStatusAtom(connName);
|
const connAtom = getConnStatusAtom(connName);
|
||||||
@ -428,7 +419,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
globalStore.set(this.markdownShowToc, !globalStore.get(this.markdownShowToc));
|
globalStore.set(this.markdownShowToc, !globalStore.get(this.markdownShowToc));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSpecializedView(getFn: jotai.Getter): Promise<{ specializedView?: string; errorStr?: string }> {
|
async getSpecializedView(getFn: Getter): Promise<{ specializedView?: string; errorStr?: string }> {
|
||||||
const mimeType = await getFn(this.fileMimeType);
|
const mimeType = await getFn(this.fileMimeType);
|
||||||
const fileInfo = await getFn(this.statFile);
|
const fileInfo = await getFn(this.statFile);
|
||||||
const fileName = await getFn(this.statFilePath);
|
const fileName = await getFn(this.statFilePath);
|
||||||
@ -490,12 +481,16 @@ export class PreviewModel implements ViewModel {
|
|||||||
fileName = "";
|
fileName = "";
|
||||||
}
|
}
|
||||||
const blockMeta = globalStore.get(this.blockAtom)?.meta;
|
const blockMeta = globalStore.get(this.blockAtom)?.meta;
|
||||||
const updateMeta = historyutil.goHistory("file", fileName, newPath, blockMeta);
|
const updateMeta = goHistory("file", fileName, newPath, blockMeta);
|
||||||
if (updateMeta == null) {
|
if (updateMeta == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blockOref = WOS.makeORef("block", this.blockId);
|
const blockOref = WOS.makeORef("block", this.blockId);
|
||||||
services.ObjectService.UpdateObjectMeta(blockOref, updateMeta);
|
services.ObjectService.UpdateObjectMeta(blockOref, updateMeta);
|
||||||
|
|
||||||
|
// Clear the saved file buffers
|
||||||
|
globalStore.set(this.fileContentSaved, null);
|
||||||
|
globalStore.set(this.newFileContent, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getParentInfo(fileInfo: FileInfo): Promise<FileInfo | undefined> {
|
async getParentInfo(fileInfo: FileInfo): Promise<FileInfo | undefined> {
|
||||||
@ -543,7 +538,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
goHistoryBack() {
|
goHistoryBack() {
|
||||||
const blockMeta = globalStore.get(this.blockAtom)?.meta;
|
const blockMeta = globalStore.get(this.blockAtom)?.meta;
|
||||||
const curPath = globalStore.get(this.metaFilePath);
|
const curPath = globalStore.get(this.metaFilePath);
|
||||||
const updateMeta = historyutil.goHistoryBack("file", curPath, blockMeta, true);
|
const updateMeta = goHistoryBack("file", curPath, blockMeta, true);
|
||||||
if (updateMeta == null) {
|
if (updateMeta == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -555,7 +550,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
goHistoryForward() {
|
goHistoryForward() {
|
||||||
const blockMeta = globalStore.get(this.blockAtom)?.meta;
|
const blockMeta = globalStore.get(this.blockAtom)?.meta;
|
||||||
const curPath = globalStore.get(this.metaFilePath);
|
const curPath = globalStore.get(this.metaFilePath);
|
||||||
const updateMeta = historyutil.goHistoryForward("file", curPath, blockMeta);
|
const updateMeta = goHistoryForward("file", curPath, blockMeta);
|
||||||
if (updateMeta == null) {
|
if (updateMeta == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -582,7 +577,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
const conn = globalStore.get(this.connection) ?? "";
|
const conn = globalStore.get(this.connection) ?? "";
|
||||||
try {
|
try {
|
||||||
services.FileService.SaveFile(conn, filePath, util.stringToBase64(newFileContent));
|
services.FileService.SaveFile(conn, filePath, stringToBase64(newFileContent));
|
||||||
globalStore.set(this.fileContent, newFileContent);
|
globalStore.set(this.fileContent, newFileContent);
|
||||||
globalStore.set(this.newFileContent, null);
|
globalStore.set(this.newFileContent, null);
|
||||||
console.log("saved file", filePath);
|
console.log("saved file", filePath);
|
||||||
@ -644,7 +639,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
navigator.clipboard.writeText(fileInfo.name);
|
navigator.clipboard.writeText(fileInfo.name);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const mimeType = util.jotaiLoadableValue(globalStore.get(this.fileMimeTypeLoadable), "");
|
const mimeType = jotaiLoadableValue(globalStore.get(this.fileMimeTypeLoadable), "");
|
||||||
if (mimeType == "directory") {
|
if (mimeType == "directory") {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
label: "Open Terminal in New Block",
|
label: "Open Terminal in New Block",
|
||||||
@ -694,29 +689,29 @@ export class PreviewModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyDownHandler(e: WaveKeyboardEvent): boolean {
|
keyDownHandler(e: WaveKeyboardEvent): boolean {
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:ArrowLeft")) {
|
if (checkKeyPressed(e, "Cmd:ArrowLeft")) {
|
||||||
this.goHistoryBack();
|
this.goHistoryBack();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:ArrowRight")) {
|
if (checkKeyPressed(e, "Cmd:ArrowRight")) {
|
||||||
this.goHistoryForward();
|
this.goHistoryForward();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:ArrowUp")) {
|
if (checkKeyPressed(e, "Cmd:ArrowUp")) {
|
||||||
// handle up directory
|
// handle up directory
|
||||||
this.goParentDirectory({});
|
this.goParentDirectory({});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const openModalOpen = globalStore.get(this.openFileModal);
|
const openModalOpen = globalStore.get(this.openFileModal);
|
||||||
if (!openModalOpen) {
|
if (!openModalOpen) {
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:o")) {
|
if (checkKeyPressed(e, "Cmd:o")) {
|
||||||
this.updateOpenFileModalAndError(true);
|
this.updateOpenFileModalAndError(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const canPreview = globalStore.get(this.canPreview);
|
const canPreview = globalStore.get(this.canPreview);
|
||||||
if (canPreview) {
|
if (canPreview) {
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:e")) {
|
if (checkKeyPressed(e, "Cmd:e")) {
|
||||||
const editMode = globalStore.get(this.editMode);
|
const editMode = globalStore.get(this.editMode);
|
||||||
this.setEditMode(!editMode);
|
this.setEditMode(!editMode);
|
||||||
return true;
|
return true;
|
||||||
@ -744,9 +739,9 @@ function makePreviewModel(blockId: string, nodeModel: NodeModel): PreviewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MarkdownPreview({ model }: SpecializedViewProps) {
|
function MarkdownPreview({ model }: SpecializedViewProps) {
|
||||||
const connName = jotai.useAtomValue(model.connection);
|
const connName = useAtomValue(model.connection);
|
||||||
const fileInfo = jotai.useAtomValue(model.statFile);
|
const fileInfo = useAtomValue(model.statFile);
|
||||||
const resolveOpts: MarkdownResolveOpts = React.useMemo<MarkdownResolveOpts>(() => {
|
const resolveOpts: MarkdownResolveOpts = useMemo<MarkdownResolveOpts>(() => {
|
||||||
return {
|
return {
|
||||||
connName: connName,
|
connName: connName,
|
||||||
baseDir: fileInfo.dir,
|
baseDir: fileInfo.dir,
|
||||||
@ -760,8 +755,8 @@ function MarkdownPreview({ model }: SpecializedViewProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StreamingPreview({ model }: SpecializedViewProps) {
|
function StreamingPreview({ model }: SpecializedViewProps) {
|
||||||
const conn = jotai.useAtomValue(model.connection);
|
const conn = useAtomValue(model.connection);
|
||||||
const fileInfo = jotai.useAtomValue(model.statFile);
|
const fileInfo = useAtomValue(model.statFile);
|
||||||
const filePath = fileInfo.path;
|
const filePath = fileInfo.path;
|
||||||
const usp = new URLSearchParams();
|
const usp = new URLSearchParams();
|
||||||
usp.set("path", filePath);
|
usp.set("path", filePath);
|
||||||
@ -805,27 +800,27 @@ function StreamingPreview({ model }: SpecializedViewProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CodeEditPreview({ model }: SpecializedViewProps) {
|
function CodeEditPreview({ model }: SpecializedViewProps) {
|
||||||
const fileContent = jotai.useAtomValue(model.fileContent);
|
const fileContent = useAtomValue(model.fileContent);
|
||||||
const setNewFileContent = jotai.useSetAtom(model.newFileContent);
|
const setNewFileContent = useSetAtom(model.newFileContent);
|
||||||
const fileName = jotai.useAtomValue(model.statFilePath);
|
const fileName = useAtomValue(model.statFilePath);
|
||||||
|
|
||||||
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
|
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:e")) {
|
if (checkKeyPressed(e, "Cmd:e")) {
|
||||||
model.setEditMode(false);
|
model.setEditMode(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:s")) {
|
if (checkKeyPressed(e, "Cmd:s")) {
|
||||||
model.handleFileSave();
|
model.handleFileSave();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(e, "Cmd:r")) {
|
if (checkKeyPressed(e, "Cmd:r")) {
|
||||||
model.handleFileRevert();
|
model.handleFileRevert();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
model.codeEditKeyDownHandler = codeEditKeyDownHandler;
|
model.codeEditKeyDownHandler = codeEditKeyDownHandler;
|
||||||
return () => {
|
return () => {
|
||||||
model.codeEditKeyDownHandler = null;
|
model.codeEditKeyDownHandler = null;
|
||||||
@ -837,7 +832,7 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
|
|||||||
model.monacoRef.current = editor;
|
model.monacoRef.current = editor;
|
||||||
|
|
||||||
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
||||||
const waveEvent = keyutil.adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
||||||
const handled = tryReinjectKey(waveEvent);
|
const handled = tryReinjectKey(waveEvent);
|
||||||
if (handled) {
|
if (handled) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -864,8 +859,8 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CSVViewPreview({ model, parentRef }: SpecializedViewProps) {
|
function CSVViewPreview({ model, parentRef }: SpecializedViewProps) {
|
||||||
const fileContent = jotai.useAtomValue(model.fileContent);
|
const fileContent = useAtomValue(model.fileContent);
|
||||||
const fileName = jotai.useAtomValue(model.statFilePath);
|
const fileName = useAtomValue(model.statFilePath);
|
||||||
return <CSVView parentRef={parentRef} readonly={true} content={fileContent} filename={fileName} />;
|
return <CSVView parentRef={parentRef} readonly={true} content={fileContent} filename={fileName} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -898,11 +893,11 @@ function iconForFile(mimeType: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SpecializedView({ parentRef, model }: SpecializedViewProps) {
|
function SpecializedView({ parentRef, model }: SpecializedViewProps) {
|
||||||
const specializedView = jotai.useAtomValue(model.specializedView);
|
const specializedView = useAtomValue(model.specializedView);
|
||||||
const mimeType = jotai.useAtomValue(model.fileMimeType);
|
const mimeType = useAtomValue(model.fileMimeType);
|
||||||
const setCanPreview = jotai.useSetAtom(model.canPreview);
|
const setCanPreview = useSetAtom(model.canPreview);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
setCanPreview(canPreview(mimeType));
|
setCanPreview(canPreview(mimeType));
|
||||||
}, [mimeType, setCanPreview]);
|
}, [mimeType, setCanPreview]);
|
||||||
|
|
||||||
@ -927,7 +922,7 @@ function PreviewView({
|
|||||||
contentRef: React.RefObject<HTMLDivElement>;
|
contentRef: React.RefObject<HTMLDivElement>;
|
||||||
model: PreviewModel;
|
model: PreviewModel;
|
||||||
}) {
|
}) {
|
||||||
const connStatus = jotai.useAtomValue(model.connStatus);
|
const connStatus = useAtomValue(model.connStatus);
|
||||||
if (connStatus?.status != "connected") {
|
if (connStatus?.status != "connected") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -943,7 +938,7 @@ function PreviewView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const OpenFileModal = React.memo(
|
const OpenFileModal = memo(
|
||||||
({
|
({
|
||||||
model,
|
model,
|
||||||
blockRef,
|
blockRef,
|
||||||
@ -953,19 +948,19 @@ const OpenFileModal = React.memo(
|
|||||||
blockRef: React.RefObject<HTMLDivElement>;
|
blockRef: React.RefObject<HTMLDivElement>;
|
||||||
blockId: string;
|
blockId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const openFileModal = jotai.useAtomValue(model.openFileModal);
|
const openFileModal = useAtomValue(model.openFileModal);
|
||||||
const curFileName = jotai.useAtomValue(model.metaFilePath);
|
const curFileName = useAtomValue(model.metaFilePath);
|
||||||
const [filePath, setFilePath] = useState("");
|
const [filePath, setFilePath] = useState("");
|
||||||
const isNodeFocused = jotai.useAtomValue(model.nodeModel.isFocused);
|
const isNodeFocused = useAtomValue(model.nodeModel.isFocused);
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
keyutil.keydownWrapper((waveEvent: WaveKeyboardEvent): boolean => {
|
keydownWrapper((waveEvent: WaveKeyboardEvent): boolean => {
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||||
model.updateOpenFileModalAndError(false);
|
model.updateOpenFileModalAndError(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCommandOperations = async () => {
|
const handleCommandOperations = async () => {
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||||
model.handleOpenFile(filePath);
|
model.handleOpenFile(filePath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,7 @@
|
|||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-flexible-toc": "^1.1.1",
|
"remark-flexible-toc": "^1.1.1",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
|
"remark-github-blockquote-alert": "^1.2.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"shell-quote": "^1.8.1",
|
"shell-quote": "^1.8.1",
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -9632,6 +9632,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"remark-github-blockquote-alert@npm:^1.2.1":
|
||||||
|
version: 1.2.1
|
||||||
|
resolution: "remark-github-blockquote-alert@npm:1.2.1"
|
||||||
|
dependencies:
|
||||||
|
unist-util-visit: "npm:^5.0.0"
|
||||||
|
checksum: 10c0/48f70a56347ba6d2649ec647f9b126fe2e22ee4efcbc4962e1645967ac0994859a930a1af3a8b75c2989d55ed5b7ce2df6fc86a4b0f2c0aa39d5ed2162c857ca
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"remark-parse@npm:^11.0.0":
|
"remark-parse@npm:^11.0.0":
|
||||||
version: 11.0.0
|
version: 11.0.0
|
||||||
resolution: "remark-parse@npm:11.0.0"
|
resolution: "remark-parse@npm:11.0.0"
|
||||||
@ -11496,6 +11505,7 @@ __metadata:
|
|||||||
rehype-slug: "npm:^6.0.0"
|
rehype-slug: "npm:^6.0.0"
|
||||||
remark-flexible-toc: "npm:^1.1.1"
|
remark-flexible-toc: "npm:^1.1.1"
|
||||||
remark-gfm: "npm:^4.0.0"
|
remark-gfm: "npm:^4.0.0"
|
||||||
|
remark-github-blockquote-alert: "npm:^1.2.1"
|
||||||
rollup-plugin-flow: "npm:^1.1.1"
|
rollup-plugin-flow: "npm:^1.1.1"
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
semver: "npm:^7.6.3"
|
semver: "npm:^7.6.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user