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={{
- [`& .${selectClasses.indicator}`]: {
- transition: "0.2s",
- [`&.${selectClasses.expanded}`]: {
- transform: "rotate(-180deg)",
- },
+export const Select_KeyboardArrowDown = (props) => {
+ // 定义默认的sx样式
+ const defaultSx = {
+ [`& .${selectClasses.indicator}`]: {
+ transition: "0.2s",
+ [`&.${selectClasses.expanded}`]: {
+ transform: "rotate(-180deg)",
},
- }}
- />
-);
+ },
+ };
+
+ // 检查是否有传入的sx样式,并进行合并
+ const { sx, ...otherProps } = props;
+ const mergedSx = sx
+ ? Object.assign({}, defaultSx, sx)
+ : defaultSx;
+
+ return (
+ }
+ 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 (
- }
- onChange={(e, newValue) =>
- // _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,
- [`& .${selectClasses.indicator}`]: {
- transition: "0.2s",
- [`&.${selectClasses.expanded}`]: {
- transform: "rotate(-180deg)",
- },
- },
- }}
- style={{ minWidth: "16em", margin: "1% 0.3%" }} // 方便调试,写在joyUI提供的 sx 里也行
- >
- {renderOptions(groupLang, colorsLang, groupLangText)}
-
- );
-}
-
-function LangSelectorMobile() {
- const _props = React.useContext(SharedProps);
- return (
- }
- onChange={(e, newValue) =>
- // _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,
- [`& .${selectClasses.indicator}`]: {
- transition: "0.2s",
- [`&.${selectClasses.expanded}`]: {
- transform: "rotate(-180deg)",
- },
- },
- }}
- 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 (
- }
- slotProps={{
- listbox: {
- component: "div",
- sx: {
- maxHeight: 360,
- overflow: "auto",
- "--List-padding": "0px",
- },
- },
- }}
- endDecorator={
-
- {window._.sum(
- window._.map(groupTheme, (i: string | any[]) => {
- return i.length;
- })
- )}
-
- }
- sx={{
- width: 240,
- flex: 1,
- [`& .${selectClasses.indicator}`]: {
- transition: "0.2s",
- [`&.${selectClasses.expanded}`]: {
- transform: "rotate(-180deg)",
- },
- },
- }}
- 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 (
- }
- slotProps={{
- listbox: {
- component: "div",
- sx: {
- maxHeight: 360,
- overflow: "auto",
- "--List-padding": "0px",
- },
- },
- }}
- endDecorator={
-
- {window._.sum(
- window._.map(groupTheme, (i: string | any[]) => {
- return i.length;
- })
- )}
-
- }
- sx={{
- flex: 1,
- [`& .${selectClasses.indicator}`]: {
- transition: "0.2s",
- [`&.${selectClasses.expanded}`]: {
- transform: "rotate(-180deg)",
- },
- },
- }}
- 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 (
-
-
- }
- sx={{
- width: "27%",
- }}
- onClick={() => setOpen(true)}
- >
- 设置
-
- }
- sx={{
- width: "46%",
- }}
- onClick={() => {
- const _lang = _props.mmodel.getLanguageId();
- const _content = "````" + _lang + "\n" + _props.editor.getValue();
- _props.editor.trigger("save-code", "save-code", {
- api: "/api/block/updateBlock",
- data: _content,
- lang: _lang,
- nodeID: _props.nodeID,
- dataType: "markdown",
- });
- }}
- >
- 保存为代码块
-
- }
- sx={{
- width: "27%",
- }}
- onClick={() => {
- const _lang = _props.mmodel.getLanguageId();
- const _content = _props.editor.getValue();
- _props.editor.trigger("save-code", "save-code", {
- api: "/api/block/updateBlock",
- data: _content,
- lang: _lang,
- nodeID: _props.nodeID,
- dataType: "markdown",
- });
- }}
- >
- 保存
-
-
- 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 (
+
+
+ }
+ sx={{
+ width: "27%",
+ }}
+ onClick={() => setOpen(true)}
+ >
+ 设置
+
+ }
+ sx={{
+ width: "46%",
+ }}
+ onClick={() => {
+ const _lang = _props.mmodel.getLanguageId();
+ const _content = "````" + _lang + "\n" + _props.editor.getValue();
+ _props.editor.trigger("save-code", "save-code", {
+ api: "/api/block/updateBlock",
+ data: _content,
+ lang: _lang,
+ nodeID: _props.nodeID,
+ dataType: "markdown",
+ });
+ }}
+ >
+ 保存为代码块
+
+ }
+ sx={{
+ width: "27%",
+ }}
+ onClick={() => {
+ const _lang = _props.mmodel.getLanguageId();
+ const _content = _props.editor.getValue();
+ _props.editor.trigger("save-code", "save-code", {
+ api: "/api/block/updateBlock",
+ data: _content,
+ lang: _lang,
+ nodeID: _props.nodeID,
+ dataType: "markdown",
+ });
+ }}
+ >
+ 保存
+
+
+ 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);
+ }
+ }
+}