diff --git a/app/src/mobile/settings/appearance_react.tsx b/app/src/mobile/settings/appearance_react.tsx index 77bbb9bd4b..0c0c9012a2 100644 --- a/app/src/mobile/settings/appearance_react.tsx +++ b/app/src/mobile/settings/appearance_react.tsx @@ -1,10 +1,9 @@ -import { Select, Option, selectClasses } from "@mui/joy"; +import { Option } from "@mui/joy"; import { CssVarsProvider, useColorScheme } from "@mui/joy/styles"; import * as React from "react"; import * as Client from "react-dom/client"; import { openModel } from "../menu/model"; -import { fetchGet, fetchPost } from "../../util/fetch"; -import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown"; +import { fetchPost } from "../../util/fetch"; import { Select_KeyboardArrowDown } from "../../sillot/joyUI/base/selector"; interface SharedPropsContextValue { @@ -203,10 +202,13 @@ export const initAppearanceReact = () => { icon: "iconTheme", html: appearanceHTML, bindEvent: modelMainElement => { - // 在模态框中渲染 AppearanceSettingsProvider 组件 const e = modelMainElement.querySelector("#appearanceSettingsContainer"); const root = Client.createRoot(e); root.render(); + if (!window.Sillot.android.roots) { + window.Sillot.android.AppearanceReactRoots = []; // 初始化roots数组 + } + window.Sillot.android.AppearanceReactRoots.push(root); // 在 window.goBakc() 、closePanel 和 closeModel 中 unmount }, }); }; diff --git a/app/src/mobile/util/MobileBackFoward.ts b/app/src/mobile/util/MobileBackFoward.ts index 4ab482fbdc..6955029a13 100644 --- a/app/src/mobile/util/MobileBackFoward.ts +++ b/app/src/mobile/util/MobileBackFoward.ts @@ -13,6 +13,7 @@ import {showMessage} from "../../dialog/message"; import {getCurrentEditor} from "../editor"; import {avRender} from "../../protyle/render/av/render"; import {setTitle} from "../../dialog/processSystem"; +import { unmountReactRootsArray } from "../../sillot/util/react"; const forwardStack: IBackStack[] = []; @@ -143,6 +144,7 @@ export const goForward = () => { export const goBack = () => { window.sout.tracker("invoked"); + unmountReactRootsArray(window.Sillot.android?.AppearanceReactRoots); const editor = getCurrentEditor(); if (window.siyuan.menus.menu.element.classList.contains("b3-menu--fullscreen") && !window.siyuan.menus.menu.element.classList.contains("fn__none")) { diff --git a/app/src/mobile/util/closePanel.ts b/app/src/mobile/util/closePanel.ts index 9e4bb22e87..3d8f4867ea 100644 --- a/app/src/mobile/util/closePanel.ts +++ b/app/src/mobile/util/closePanel.ts @@ -1,19 +1,22 @@ -import {activeBlur, hideKeyboardToolbar} from "./keyboardToolbar"; - -export const closePanel = () => { - window.sout.tracker("invoked"); - document.getElementById("menu").style.transform = ""; - document.getElementById("sidebar").style.transform = ""; - document.getElementById("model").style.transform = ""; - const maskElement = document.querySelector(".side-mask") as HTMLElement; - maskElement.classList.add("fn__none"); - maskElement.style.opacity = ""; - window.siyuan.menus.menu.remove(); -}; - -export const closeModel = () => { - window.sout.tracker("invoked"); - document.getElementById("model").style.transform = ""; - activeBlur(); - hideKeyboardToolbar(); -}; +import { unmountReactRootsArray } from "../../sillot/util/react"; +import {activeBlur, hideKeyboardToolbar} from "./keyboardToolbar"; + +export const closePanel = () => { + window.sout.tracker("invoked"); + document.getElementById("menu").style.transform = ""; + document.getElementById("sidebar").style.transform = ""; + document.getElementById("model").style.transform = ""; + const maskElement = document.querySelector(".side-mask") as HTMLElement; + maskElement.classList.add("fn__none"); + maskElement.style.opacity = ""; + window.siyuan.menus.menu.remove(); + unmountReactRootsArray(window.Sillot.android?.AppearanceReactRoots); +}; + +export const closeModel = () => { + window.sout.tracker("invoked"); + document.getElementById("model").style.transform = ""; + activeBlur(); + hideKeyboardToolbar(); + unmountReactRootsArray(window.Sillot.android?.AppearanceReactRoots); +}; diff --git a/app/src/sillot/index.ts b/app/src/sillot/index.ts index b283829660..a66874e8d1 100644 --- a/app/src/sillot/index.ts +++ b/app/src/sillot/index.ts @@ -24,7 +24,7 @@ export class SillotEnv { status: { IDBloaded: false, disableDocSetPadding: false }, funs: { hljsRender: highlightRender }, lute: null, - android: null, + android: {}, }; window.__ = { ace: null, diff --git a/app/src/sillot/joyUI/base/selector.tsx b/app/src/sillot/joyUI/base/selector.tsx index d131f55dc1..d4c5a6f6a7 100644 --- a/app/src/sillot/joyUI/base/selector.tsx +++ b/app/src/sillot/joyUI/base/selector.tsx @@ -1,17 +1,28 @@ import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown"; import { Select, selectClasses } from "@mui/joy"; -export const Select_KeyboardArrowDown = ({ ...props }) => ( - } + sx={mergedSx} // 使用合并后的sx + /> + ); +}; diff --git a/app/src/sillot/joyUI/com_/monaco-dailog-editor.tsx b/app/src/sillot/joyUI/com_/monaco-dailog-editor.tsx index 579c412452..04e8813053 100644 --- a/app/src/sillot/joyUI/com_/monaco-dailog-editor.tsx +++ b/app/src/sillot/joyUI/com_/monaco-dailog-editor.tsx @@ -1,859 +1,829 @@ -import * as React from "react"; -import * as Client from "react-dom/client"; -import { CssVarsProvider, useColorScheme } from "@mui/joy/styles"; -import Button from "@mui/joy/Button"; -import Modal from "@mui/joy/Modal"; -import Checkbox from "@mui/joy/Checkbox"; -import Box from "@mui/joy/Box"; -import Slider from "@mui/joy/Slider"; -import Stack from "@mui/joy/Stack"; -import Drawer from "@mui/joy/Drawer"; -import DialogTitle from "@mui/joy/DialogTitle"; -import DialogContent from "@mui/joy/DialogContent"; -import Divider from "@mui/joy/Divider"; -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import FormHelperText from "@mui/joy/FormHelperText"; -import ModalClose from "@mui/joy/ModalClose"; -import Typography from "@mui/joy/Typography"; -import Sheet from "@mui/joy/Sheet"; -import Switch from "@mui/joy/Switch"; -import Select, { selectClasses } from "@mui/joy/Select"; -import Option, { optionClasses } from "@mui/joy/Option"; -import Chip from "@mui/joy/Chip"; -import List from "@mui/joy/List"; -import ListItemDecorator, { listItemDecoratorClasses } from "@mui/joy/ListItemDecorator"; -import ListDivider from "@mui/joy/ListDivider"; -import ListItem from "@mui/joy/ListItem"; -import Check from "@mui/icons-material/Check"; -import CircularProgress from "@mui/joy/CircularProgress"; -import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown"; -import TuneIcon from "@mui/icons-material/TuneRounded"; -import SaveIcon from "@mui/icons-material/SaveOutlined"; -import loader from "@monaco-editor/loader"; -import ModalDialog, { type ModalDialogProps } from "@mui/joy/ModalDialog"; -import { uriFromPath } from "../../util/path"; -import { fetchPost } from "../../../util/fetch"; -const path = require("path"); -import { isMobile } from "sofill/api"; -import { - initEditorOptions, - groupTheme, - colorsTheme, - groupLang, - groupLangText, - colorsLang, - isLightTheme, - fontSliderMasks, - type TypeGroupList, - type TypeStringKV, - type OverridableColorTheme, - type OverridableColorLang, -} from "./monaco-editor-confige"; -import type * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; -// theme -import BirdsofParadise from "./monacoThemes/Birds-of-Paradise"; -import Blackboard from "./monacoThemes/Blackboard"; -import Cobalt from "./monacoThemes/Cobalt"; -import Cobalt2 from "./monacoThemes/Cobalt2"; -import Dracula from "./monacoThemes/Dracula"; -import IdleFingers from "./monacoThemes/idleFingers"; -import IPlastic from "./monacoThemes/iPlastic"; -import Katzenmilch from "./monacoThemes/Katzenmilch"; -import Monokai from "./monacoThemes/Monokai"; -import NightOwl from "./monacoThemes/Night-Owl"; -import Solarizedlight from "./monacoThemes/Solarized-light"; -import Sunburst from "./monacoThemes/Sunburst"; -import TomorrowNightEighties from "./monacoThemes/Tomorrow-Night-Eighties"; -import Zenburnesque from "./monacoThemes/Zenburnesque"; - -type InitConfig = { - lang: string; - theme: "vs" | "vs-dark"; - readonly: boolean; - editable: boolean; -}; -interface SharedPropsContextValue { - nodeID: string; - editor: monaco.editor.IStandaloneCodeEditor | null; - mmodel: monaco.editor.ITextModel | null; - monacoIns: typeof monaco | null; - initConfig: InitConfig; -} -const SharedProps = React.createContext(null); - -export default function MDDialog(props: { - id: string; - nodeID: string; - lang: string; - theme: "vs" | "vs-dark"; - readonly: boolean; - editable: boolean; -}) { - const id = props.id; - const nodeID = props.nodeID; - const initConfig: InitConfig = { - lang: props.lang, - theme: props.theme, - readonly: props.readonly, - editable: props.editable, - }; - const e = document.getElementById(id); - if (!e) { - return; - } - const root = Client.createRoot(e); - // 在 React 中, <> 是 的语法糖 - root.render( - <> - - - ); -} - -function Loader(props: { nodeID: string; initConfig: InitConfig }) { - const [open, setOpen] = React.useState(true); // react hooks - const [editor, setEditor] = React.useState(null); - const [mmodel, setmmodel] = React.useState(null); - const [monacoIns, setmonacoIns] = React.useState(null); - const [layout, setLayout] = React.useState(undefined); - const [modelPadding, setmodelPadding] = React.useState("var(--Card-padding)"); - const [modelWidth, setmodelWidth] = React.useState("85vw"); - const nodeID = props.nodeID; - const initConfig = props.initConfig; - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect( - () => { - /// #if !BROWSER - const pp = path.join(__dirname, "../../app/node_modules/monaco-editor/min/vs"); // 思源路径特殊 - // console.log(pp) - loader.config({ - paths: { - vs: uriFromPath(pp), - }, - "vs/nls": { - availableLanguages: { - "*": "zh-cn", - }, - }, - }); - /// #endif - loader.init().then(monacoInstance => { - monacoInstance.editor.defineTheme("Birds-of-Paradise", BirdsofParadise); // 不要放到箭头函数里 - monacoInstance.editor.defineTheme("Blackboard", Blackboard); - monacoInstance.editor.defineTheme("Cobalt", Cobalt); - monacoInstance.editor.defineTheme("Cobalt2", Cobalt2); - monacoInstance.editor.defineTheme("Dracula", Dracula); - monacoInstance.editor.defineTheme("IdleFingers", IdleFingers); - monacoInstance.editor.defineTheme("IPlastic", IPlastic); - monacoInstance.editor.defineTheme("Katzenmilch", Katzenmilch); - monacoInstance.editor.defineTheme("Monokai", Monokai); - monacoInstance.editor.defineTheme("Night-Owl", NightOwl); - monacoInstance.editor.defineTheme("Solarized-light", Solarizedlight); - monacoInstance.editor.defineTheme("Sunburst", Sunburst); - monacoInstance.editor.defineTheme("Tomorrow-Night-Eighties", TomorrowNightEighties); - monacoInstance.editor.defineTheme("Zenburnesque", Zenburnesque); - const model_x = monacoInstance.editor.createModel("", initConfig.lang); - const _editor = monacoInstance.editor.create(document.getElementById("monaco-editor"), { - ...initConfig, - ...initEditorOptions, - model: model_x, - }); - // 添加保存命令 - _editor.addAction({ - id: "save-code", - label: "Save Code", - // keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], - run: (e, payload) => { - console.log(e); - const rawOpts = e.getRawOptions() as any; - console.log(rawOpts.lang); - fetchPost( - payload.api, - { - dataType: payload.dataType, - data: payload.data, - id: payload.nodeID, - }, - res => { - if (res.code === 0) { - window.__.toastify.success({ message: "已保存", position: "bottom-center", duration: 1 }); - } else { - window.__.toastify.error({ message: res.msg, position: "bottom-center", duration: 1 }); - } - } - ); - }, - }); - setEditor(_editor); - setmmodel(model_x); - setmonacoIns(monacoInstance); - _editor.updateOptions({ readOnly: initConfig.readonly }); - _editor.onDidChangeModelContent(() => { - // window.sout.slog(_editor.getValue()); - }); - window.sout.tracker(model_x); - window.sout.tracker(monacoInstance.editor); - if (isMobile()) { - setLayout("fullscreen"); - setmodelPadding("0px"); - setmodelWidth("initial"); // 移动端已经全屏宽度,不要设置宽度 - } - }); - }, - [] // 空数组保证只执行一次 - ); - const [loading, setLoading] = React.useState(true); // 顺序重要 - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect(() => { - if (!editor) return; // 第一次初始化时不执行 - fetchPost( - "/api/block/getBlockKramdown", - { - id: nodeID, - }, - res => { - if (res.code === 0 && editor) { - window.sout.info(nodeID); - window.sout.success(res); - // editor.setValue(res.data.kramdown); - window.sout.tracker(monacoIns.editor.getModels()[0]); - mmodel.setValue(res.data.kramdown); - // editor.updateOptions({ model: IEditor.createModel(res.data.kramdown,initConfig.lang) }) - setLoading(false); - setOpen(true); - document.getElementById("monaco-editor-container").style.display = "inherit"; - window.sout.tracker(editor); - } else { - setOpen(false); - window.__.toastify.error({ - message: res.msg, - position: "bottom-center", - duration: 1, - }); - } - } - ); - }, [editor]); - return ( - - {/* 必须使用CssVarsProvider包裹,样式才会生效 */} - - { - window.sout.tracker(`Reason: ${reason}`); - setOpen(false); - mmodel.dispose(); // 手动创建的需要手动销毁 - }} - sx={{ - display: "flex", - alignItems: "center", - justifyContent: "center", - }} - > - - - -
- -
-
-
-
-
-
- ); -} - -// 渲染下拉菜单选项 -function renderOptions( - group: TypeGroupList, - groupColorMap: OverridableColorTheme | OverridableColorLang, - groupSVText: TypeStringKV | null = null -) { - return Object.entries(group).map(([name, items], index) => ( - - {index !== 0 && } - - - - {name} ({items.length}) - - - {items.map(item => ( - - } - sx={{ - [`&.${optionClasses.selected} .${listItemDecoratorClasses.root}`]: { - opacity: 1, - }, - }} - > - - - - {groupSVText ? groupSVText[item] : item} {/*在下拉框里显示的值*/} - - ))} - -
- )); -} - -function LangSelector() { - const _props = React.useContext(SharedProps); - return ( - - ); -} - -function LangSelectorMobile() { - const _props = React.useContext(SharedProps); - return ( - - ); -} - -function ThemeSelector() { - const _props = React.useContext(SharedProps); - const { mode, setMode } = useColorScheme(); - return ( - - ); -} - -function ThemeSelectorMobile() { - const _props = React.useContext(SharedProps); - const { mode, setMode } = useColorScheme(); - return ( - - ); -} - -function Configer() { - const _props = React.useContext(SharedProps); - const [readonly, setReadonly] = React.useState(false); - const [showFullyWidth, setShowFullyWidth] = React.useState(false); - const { mode, setMode } = useColorScheme(); - console.log(_props.initConfig.lang, _props.initConfig.theme); - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect( - () => { - setMode(_props.initConfig.theme === "vs" ? "light" : "dark"); - setReadonly(_props.initConfig.readonly); - }, - [] // 只运行一次 - ); - - return ( - <> - - - - { - const isChecked = event.target.checked; - _props.editor.updateOptions({ wordWrap: isChecked ? "on" : "off" }); - }} - /> - - { - const isChecked = event.target.checked; - _props.editor.updateOptions({ readOnly: isChecked }); // https://www.cnblogs.com/zzsdream/p/14055963.html - setReadonly(isChecked); - }} - /> - - - ); -} - -function ConfigerDrawer() { - const [open, setOpen] = React.useState(false); - const _props = React.useContext(SharedProps); - const [readonly, setReadonly] = React.useState(false); - const [autoWrap, setAutoWrap] = React.useState(true); - const { mode, setMode } = useColorScheme(); - console.log(_props.initConfig.lang, _props.initConfig.theme); - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect( - () => { - setMode(_props.initConfig.theme === "vs" ? "light" : "dark"); - setReadonly(_props.initConfig.readonly); - }, - [] // 只运行一次 - ); - return ( - - - - - - - setOpen(false)} - slotProps={{ - content: { - sx: { - bgcolor: "transparent", - p: { md: 3, sm: 0 }, - boxShadow: "none", - width: "100vw", - }, - }, - }} - style={{ - width: "100vw", - }} - > - - 设置 - - - - - 选择语言 - - - - 选择主题 - - - - - 只读模式 - 写入模式将在后续版本就绪 - - { - const isChecked = event.target.checked; - _props.editor.updateOptions({ readOnly: isChecked }); // https://www.cnblogs.com/zzsdream/p/14055963.html - setReadonly(isChecked); - }} - /> - - - - - 及时保存 - 该功能将在后续版本就绪 - - - - - - 自动换行 - 自动将过长的代码行分割成多个较短的行 - - { - const isChecked = event.target.checked; - _props.editor.updateOptions({ wordWrap: isChecked ? "on" : "off" }); - setAutoWrap(isChecked); - }} - /> - - - - - - - - - - - - ); -} - -function EditorContainer() { - const _props = React.useContext(SharedProps); - /// #if MOBILE - return ( - <> - - - {_props.nodeID} - - -
- - { - _props.editor.updateOptions({ fontSize: value as number }); - }} - sx={{ - "--Slider-trackSize": "10px", - "--Slider-markSize": "6px", - "--Slider-thumbSize": "20px", - }} - /> - - - ); - /// #else - // biome-ignore lint/correctness/noUnreachable: - return ( - <> - - - {_props.nodeID} - - -
- - ); - /// #endif -} +import * as React from "react"; +import * as Client from "react-dom/client"; +import { CssVarsProvider, useColorScheme } from "@mui/joy/styles"; +import Button from "@mui/joy/Button"; +import Modal from "@mui/joy/Modal"; +import Checkbox from "@mui/joy/Checkbox"; +import Box from "@mui/joy/Box"; +import Slider from "@mui/joy/Slider"; +import Stack from "@mui/joy/Stack"; +import Drawer from "@mui/joy/Drawer"; +import DialogTitle from "@mui/joy/DialogTitle"; +import DialogContent from "@mui/joy/DialogContent"; +import Divider from "@mui/joy/Divider"; +import FormControl from "@mui/joy/FormControl"; +import FormLabel from "@mui/joy/FormLabel"; +import FormHelperText from "@mui/joy/FormHelperText"; +import ModalClose from "@mui/joy/ModalClose"; +import Typography from "@mui/joy/Typography"; +import Sheet from "@mui/joy/Sheet"; +import Option, { optionClasses } from "@mui/joy/Option"; +import Chip from "@mui/joy/Chip"; +import List from "@mui/joy/List"; +import ListItemDecorator, { listItemDecoratorClasses } from "@mui/joy/ListItemDecorator"; +import ListDivider from "@mui/joy/ListDivider"; +import ListItem from "@mui/joy/ListItem"; +import Check from "@mui/icons-material/Check"; +import CircularProgress from "@mui/joy/CircularProgress"; +import TuneIcon from "@mui/icons-material/TuneRounded"; +import SaveIcon from "@mui/icons-material/SaveOutlined"; +import loader from "@monaco-editor/loader"; +import ModalDialog, { type ModalDialogProps } from "@mui/joy/ModalDialog"; +import { uriFromPath } from "../../util/path"; +import { fetchPost } from "../../../util/fetch"; +const path = require("path"); +import { isMobile } from "sofill/api"; +import { + initEditorOptions, + groupTheme, + colorsTheme, + groupLang, + groupLangText, + colorsLang, + isLightTheme, + fontSliderMasks, + type TypeGroupList, + type TypeStringKV, + type OverridableColorTheme, + type OverridableColorLang, +} from "./monaco-editor-confige"; +import type * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; +// theme +import BirdsofParadise from "./monacoThemes/Birds-of-Paradise"; +import Blackboard from "./monacoThemes/Blackboard"; +import Cobalt from "./monacoThemes/Cobalt"; +import Cobalt2 from "./monacoThemes/Cobalt2"; +import Dracula from "./monacoThemes/Dracula"; +import IdleFingers from "./monacoThemes/idleFingers"; +import IPlastic from "./monacoThemes/iPlastic"; +import Katzenmilch from "./monacoThemes/Katzenmilch"; +import Monokai from "./monacoThemes/Monokai"; +import NightOwl from "./monacoThemes/Night-Owl"; +import Solarizedlight from "./monacoThemes/Solarized-light"; +import Sunburst from "./monacoThemes/Sunburst"; +import TomorrowNightEighties from "./monacoThemes/Tomorrow-Night-Eighties"; +import Zenburnesque from "./monacoThemes/Zenburnesque"; +import { Select_KeyboardArrowDown } from "../base/selector"; + +type InitConfig = { + lang: string; + theme: "vs" | "vs-dark"; + readonly: boolean; + editable: boolean; +}; +interface SharedPropsContextValue { + nodeID: string; + editor: monaco.editor.IStandaloneCodeEditor | null; + mmodel: monaco.editor.ITextModel | null; + monacoIns: typeof monaco | null; + initConfig: InitConfig; +} +const SharedProps = React.createContext(null); + +export default function MDDialog(props: { + id: string; + nodeID: string; + lang: string; + theme: "vs" | "vs-dark"; + readonly: boolean; + editable: boolean; +}) { + const id = props.id; + const nodeID = props.nodeID; + const initConfig: InitConfig = { + lang: props.lang, + theme: props.theme, + readonly: props.readonly, + editable: props.editable, + }; + const e = document.getElementById(id); + if (!e) { + return; + } + const root = Client.createRoot(e); + // 在 React 中, <> 是 的语法糖 + root.render( + <> + + + ); +} + +function Loader(props: { nodeID: string; initConfig: InitConfig }) { + const [open, setOpen] = React.useState(true); // react hooks + const [editor, setEditor] = React.useState(null); + const [mmodel, setmmodel] = React.useState(null); + const [monacoIns, setmonacoIns] = React.useState(null); + const [layout, setLayout] = React.useState(undefined); + const [modelPadding, setmodelPadding] = React.useState("var(--Card-padding)"); + const [modelWidth, setmodelWidth] = React.useState("85vw"); + const nodeID = props.nodeID; + const initConfig = props.initConfig; + // biome-ignore lint/correctness/useExhaustiveDependencies: + React.useEffect( + () => { + /// #if !BROWSER + const pp = path.join(__dirname, "../../app/node_modules/monaco-editor/min/vs"); // 思源路径特殊 + // console.log(pp) + loader.config({ + paths: { + vs: uriFromPath(pp), + }, + "vs/nls": { + availableLanguages: { + "*": "zh-cn", + }, + }, + }); + /// #endif + loader.init().then(monacoInstance => { + monacoInstance.editor.defineTheme("Birds-of-Paradise", BirdsofParadise); // 不要放到箭头函数里 + monacoInstance.editor.defineTheme("Blackboard", Blackboard); + monacoInstance.editor.defineTheme("Cobalt", Cobalt); + monacoInstance.editor.defineTheme("Cobalt2", Cobalt2); + monacoInstance.editor.defineTheme("Dracula", Dracula); + monacoInstance.editor.defineTheme("IdleFingers", IdleFingers); + monacoInstance.editor.defineTheme("IPlastic", IPlastic); + monacoInstance.editor.defineTheme("Katzenmilch", Katzenmilch); + monacoInstance.editor.defineTheme("Monokai", Monokai); + monacoInstance.editor.defineTheme("Night-Owl", NightOwl); + monacoInstance.editor.defineTheme("Solarized-light", Solarizedlight); + monacoInstance.editor.defineTheme("Sunburst", Sunburst); + monacoInstance.editor.defineTheme("Tomorrow-Night-Eighties", TomorrowNightEighties); + monacoInstance.editor.defineTheme("Zenburnesque", Zenburnesque); + const model_x = monacoInstance.editor.createModel("", initConfig.lang); + const _editor = monacoInstance.editor.create(document.getElementById("monaco-editor"), { + ...initConfig, + ...initEditorOptions, + model: model_x, + }); + // 添加保存命令 + _editor.addAction({ + id: "save-code", + label: "Save Code", + // keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], + run: (e, payload) => { + console.log(e); + const rawOpts = e.getRawOptions() as any; + console.log(rawOpts.lang); + fetchPost( + payload.api, + { + dataType: payload.dataType, + data: payload.data, + id: payload.nodeID, + }, + res => { + if (res.code === 0) { + window.__.toastify.success({ message: "已保存", position: "bottom-center", duration: 1 }); + } else { + window.__.toastify.error({ message: res.msg, position: "bottom-center", duration: 1 }); + } + } + ); + }, + }); + setEditor(_editor); + setmmodel(model_x); + setmonacoIns(monacoInstance); + _editor.updateOptions({ readOnly: initConfig.readonly }); + _editor.onDidChangeModelContent(() => { + // window.sout.slog(_editor.getValue()); + }); + window.sout.tracker(model_x); + window.sout.tracker(monacoInstance.editor); + if (isMobile()) { + setLayout("fullscreen"); + setmodelPadding("0px"); + setmodelWidth("initial"); // 移动端已经全屏宽度,不要设置宽度 + } + }); + }, + [] // 空数组保证只执行一次 + ); + const [loading, setLoading] = React.useState(true); // 顺序重要 + // biome-ignore lint/correctness/useExhaustiveDependencies: + React.useEffect(() => { + if (!editor) return; // 第一次初始化时不执行 + fetchPost( + "/api/block/getBlockKramdown", + { + id: nodeID, + }, + res => { + if (res.code === 0 && editor) { + window.sout.info(nodeID); + window.sout.success(res); + // editor.setValue(res.data.kramdown); + window.sout.tracker(monacoIns.editor.getModels()[0]); + mmodel.setValue(res.data.kramdown); + // editor.updateOptions({ model: IEditor.createModel(res.data.kramdown,initConfig.lang) }) + setLoading(false); + setOpen(true); + document.getElementById("monaco-editor-container").style.display = "inherit"; + window.sout.tracker(editor); + } else { + setOpen(false); + window.__.toastify.error({ + message: res.msg, + position: "bottom-center", + duration: 1, + }); + } + } + ); + }, [editor]); + return ( + + {/* 必须使用CssVarsProvider包裹,样式才会生效 */} + + { + window.sout.tracker(`Reason: ${reason}`); + setOpen(false); + mmodel.dispose(); // 手动创建的需要手动销毁 + }} + sx={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > + + + +
+ +
+
+
+
+
+
+ ); +} + +// 渲染下拉菜单选项 +function renderOptions( + group: TypeGroupList, + groupColorMap: OverridableColorTheme | OverridableColorLang, + groupSVText: TypeStringKV | null = null +) { + return Object.entries(group).map(([name, items], index) => ( + + {index !== 0 && } + + + + {name} ({items.length}) + + + {items.map(item => ( + + } + sx={{ + [`&.${optionClasses.selected} .${listItemDecoratorClasses.root}`]: { + opacity: 1, + }, + }} + > + + + + {groupSVText ? groupSVText[item] : item} {/*在下拉框里显示的值*/} + + ))} + +
+ )); +} + +function LangSelector() { + const _props = React.useContext(SharedProps); + return ( + + // _props.editor.updateOptions({ language: newValue }) 不行 + { + _props.monacoIns.editor.setModelLanguage(_props.mmodel, newValue); + } + } + slotProps={{ + listbox: { + component: "div", + sx: { + maxHeight: 360, + overflow: "auto", + "--List-padding": "0px", + }, + }, + }} + endDecorator={ + + {window._.sum( + window._.map(groupLang, (i: string | any[]) => { + return i.length; + }) + )} + + } + sx={{ + width: 240, + flex: 1, + }} + style={{ minWidth: "16em", margin: "1% 0.3%" }} // 方便调试,写在joyUI提供的 sx 里也行 + > + {renderOptions(groupLang, colorsLang, groupLangText)} + + ); +} + +function LangSelectorMobile() { + const _props = React.useContext(SharedProps); + return ( + + // _props.editor.updateOptions({ language: newValue }) 不行 + { + _props.monacoIns.editor.setModelLanguage(_props.mmodel, newValue); + } + } + slotProps={{ + listbox: { + component: "div", + sx: { + maxHeight: 360, + overflow: "auto", + "--List-padding": "0px", + }, + }, + }} + endDecorator={ + + {window._.sum( + window._.map(groupLang, (i: string | any[]) => { + return i.length; + }) + )} + + } + sx={{ + width: 240, + flex: 1, + }} + style={{ minWidth: "16em", margin: "1% 0.3%", maxHeight: "3.5em", width: "100%" }} // 方便调试,写在joyUI提供的 sx 里也行 + > + {renderOptions(groupLang, colorsLang, groupLangText)} + + ); +} + +function ThemeSelector() { + const _props = React.useContext(SharedProps); + const { mode, setMode } = useColorScheme(); + return ( + + {window._.sum( + window._.map(groupTheme, (i: string | any[]) => { + return i.length; + }) + )} + + } + sx={{ + width: 240, + flex: 1, + }} + style={{ minWidth: "20em", margin: "1% 0.3%" }} + onChange={(e, newValue) => { + _props.editor.updateOptions({ theme: newValue }); + setMode(isLightTheme(newValue) ? "light" : "dark"); + }} + > + {renderOptions(groupTheme, colorsTheme)} + + ); +} + +function ThemeSelectorMobile() { + const _props = React.useContext(SharedProps); + const { mode, setMode } = useColorScheme(); + return ( + + {window._.sum( + window._.map(groupTheme, (i: string | any[]) => { + return i.length; + }) + )} + + } + sx={{ + flex: 1, + }} + style={{ minWidth: "20em", margin: "1% 0.3%", maxHeight: "3.5em", width: "100%" }} + onChange={(e, newValue) => { + _props.editor.updateOptions({ theme: newValue }); + setMode(isLightTheme(newValue) ? "light" : "dark"); + }} + > + {renderOptions(groupTheme, colorsTheme)} + + ); +} + +function Configer() { + const _props = React.useContext(SharedProps); + const [readonly, setReadonly] = React.useState(false); + const [showFullyWidth, setShowFullyWidth] = React.useState(false); + const { mode, setMode } = useColorScheme(); + console.log(_props.initConfig.lang, _props.initConfig.theme); + // biome-ignore lint/correctness/useExhaustiveDependencies: + React.useEffect( + () => { + setMode(_props.initConfig.theme === "vs" ? "light" : "dark"); + setReadonly(_props.initConfig.readonly); + }, + [] // 只运行一次 + ); + + return ( + <> + + + + { + const isChecked = event.target.checked; + _props.editor.updateOptions({ wordWrap: isChecked ? "on" : "off" }); + }} + /> + + { + const isChecked = event.target.checked; + _props.editor.updateOptions({ readOnly: isChecked }); // https://www.cnblogs.com/zzsdream/p/14055963.html + setReadonly(isChecked); + }} + /> + + + ); +} + +function ConfigerDrawer() { + const [open, setOpen] = React.useState(false); + const _props = React.useContext(SharedProps); + const [readonly, setReadonly] = React.useState(false); + const [autoWrap, setAutoWrap] = React.useState(true); + const { mode, setMode } = useColorScheme(); + console.log(_props.initConfig.lang, _props.initConfig.theme); + // biome-ignore lint/correctness/useExhaustiveDependencies: + React.useEffect( + () => { + setMode(_props.initConfig.theme === "vs" ? "light" : "dark"); + setReadonly(_props.initConfig.readonly); + }, + [] // 只运行一次 + ); + return ( + + + + + + + setOpen(false)} + slotProps={{ + content: { + sx: { + bgcolor: "transparent", + p: { md: 3, sm: 0 }, + boxShadow: "none", + width: "100vw", + }, + }, + }} + style={{ + width: "100vw", + }} + > + + 设置 + + + + + 选择语言 + + + + 选择主题 + + + + + 只读模式 + 写入模式将在后续版本就绪 + + { + const isChecked = event.target.checked; + _props.editor.updateOptions({ readOnly: isChecked }); // https://www.cnblogs.com/zzsdream/p/14055963.html + setReadonly(isChecked); + }} + /> + + + + + 及时保存 + 该功能将在后续版本就绪 + + + + + + 自动换行 + 自动将过长的代码行分割成多个较短的行 + + { + const isChecked = event.target.checked; + _props.editor.updateOptions({ wordWrap: isChecked ? "on" : "off" }); + setAutoWrap(isChecked); + }} + /> + + + + + + + + + + + + ); +} + +function EditorContainer() { + const _props = React.useContext(SharedProps); + /// #if MOBILE + return ( + <> + + + {_props.nodeID} + + +
+ + { + _props.editor.updateOptions({ fontSize: value as number }); + }} + sx={{ + "--Slider-trackSize": "10px", + "--Slider-markSize": "6px", + "--Slider-thumbSize": "20px", + }} + /> + + + ); + /// #else + // biome-ignore lint/correctness/noUnreachable: + return ( + <> + + + {_props.nodeID} + + +
+ + ); + /// #endif +} diff --git a/app/src/sillot/util/react.ts b/app/src/sillot/util/react.ts new file mode 100644 index 0000000000..edf5ed9200 --- /dev/null +++ b/app/src/sillot/util/react.ts @@ -0,0 +1,10 @@ +export const unmountReactRootsArray = (roots) => { + if (!roots || roots.length === 0) { return; } + for (let i = roots.length - 1; i >= 0; i--) { + const root = roots[i]; + if (root?.unmount) { + root.unmount(); + roots.splice(i, 1); + } + } +}