feat: 保存文集

This commit is contained in:
shixiaohua 2024-01-27 18:56:17 +08:00
parent b7ac57f6a8
commit 1982910745
11 changed files with 205 additions and 105 deletions

22
main.js
View File

@ -2,6 +2,8 @@
const { app,Menu, BrowserWindow } = require('electron') const { app,Menu, BrowserWindow } = require('electron')
const path = require('path') const path = require('path')
const {menuRebuild} = require('./src/comment/TopMenu.js') const {menuRebuild} = require('./src/comment/TopMenu.js')
// const {SAVE} = require("./src/utils/HotkeyConst");
// const {PUSH_HOTKEY} = require("./src/utils/MainContentsChannel");
const createWindow = () => { const createWindow = () => {
// Create the browser window. // Create the browser window.
const win = new BrowserWindow({ const win = new BrowserWindow({
@ -20,15 +22,23 @@ const createWindow = () => {
// 打开开发工具 // 打开开发工具
win.webContents.openDevTools() win.webContents.openDevTools()
win.webContents.on('before-input-event', (event, input) => { win.webContents.on('before-input-event', (event, input) => {
// if (input.control && input.key.toLowerCase() === 's') { if (input.control && input.key.toLowerCase() === 's') {
// console.log('Pressed Control+s') console.log("win.webContents.send(\"pushHotkeys\",\"CTRL+S\")")
// event.preventDefault() win.webContents.send("pushHotkeys","CTRL+S")
// } // event.preventDefault()
}
console.log('Pressed ',input.key) console.log('Pressed ',input.key)
if (input.key==='f5'){ if (input.key.toUpperCase()==='F5'){
console.log('Pressed f5')
win.reload() win.reload()
} }
if (input.key.toUpperCase()==='F12'){
if (win.webContents.isDevToolsOpened()){
win.webContents.closeDevTools()
}else {
win.webContents.openDevTools()
}
}
}) })
Menu.setApplicationMenu(Menu.buildFromTemplate(menuRebuild(win))) Menu.setApplicationMenu(Menu.buildFromTemplate(menuRebuild(win)))
} }

32
package-lock.json generated
View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"md5": "^2.3.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"node-sass": "^7.0.3" "node-sass": "^7.0.3"
}, },
@ -8490,6 +8491,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"engines": {
"node": "*"
}
},
"node_modules/check-types": { "node_modules/check-types": {
"version": "11.2.3", "version": "11.2.3",
"resolved": "https://registry.npmmirror.com/check-types/-/check-types-11.2.3.tgz", "resolved": "https://registry.npmmirror.com/check-types/-/check-types-11.2.3.tgz",
@ -9667,6 +9676,14 @@
"node": ">=12.10" "node": ">=12.10"
} }
}, },
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"engines": {
"node": "*"
}
},
"node_modules/crypto-random-string": { "node_modules/crypto-random-string": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@ -14346,6 +14363,11 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/is-callable": { "node_modules/is-callable": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz",
@ -17928,6 +17950,16 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz", "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz",

View File

@ -51,6 +51,7 @@
"dependencies": { "dependencies": {
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"md5": "^2.3.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"node-sass": "^7.0.3" "node-sass": "^7.0.3"
}, },

View File

@ -3,7 +3,7 @@ import routes from './routes'
import {Outlet, useNavigate, useRoutes} from 'react-router-dom' import {Outlet, useNavigate, useRoutes} from 'react-router-dom'
import useIpcRenderer from '../src/utils/useIpcRenderer' import useIpcRenderer from '../src/utils/useIpcRenderer'
import {dirAdd} from "./redux/dirMessage_reducer"; import {dirAdd} from "./redux/dirMessage_reducer";
import {openFile} from "./redux/clickFile_reducer" import {pushHotkeys} from "./redux/pushHotkeys_reducer";
import {store} from "./redux/store"; import {store} from "./redux/store";
function App() { function App() {
@ -17,11 +17,16 @@ function App() {
const addNewDir =(event,args)=> { const addNewDir =(event,args)=> {
store.dispatch(dirAdd(args)) store.dispatch(dirAdd(args))
} }
const pushHotkeysAction =(event,args)=> {
console.log("store.dispatch(pushHotkeys(args))",args)
store.dispatch(pushHotkeys(args))
}
console.log("routes",routes) console.log("routes",routes)
const element = useRoutes(routes) const element = useRoutes(routes)
useIpcRenderer({'redirectUrl':redirectUrl}) useIpcRenderer({'redirectUrl':redirectUrl})
useIpcRenderer({'openDirectory':addNewDir}) useIpcRenderer({'openDirectory':addNewDir})
useIpcRenderer({'pushHotkeys':pushHotkeysAction})
return ( return (
<> <>
{/* 注册路由 */} {/* 注册路由 */}

View File

@ -1,84 +1,109 @@
import ExampleTheme from "./themes/ExampleTheme"; import ExampleTheme from "./themes/ExampleTheme";
import { LexicalComposer } from "@lexical/react/LexicalComposer"; import {LexicalComposer} from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; import {RichTextPlugin} from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable"; import {ContentEditable} from "@lexical/react/LexicalContentEditable";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; import {HistoryPlugin} from "@lexical/react/LexicalHistoryPlugin";
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin"; import {AutoFocusPlugin} from "@lexical/react/LexicalAutoFocusPlugin";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import TreeViewPlugin from "./plugins/TreeViewPlugin"; import TreeViewPlugin from "./plugins/TreeViewPlugin";
import ToolbarPlugin from "./plugins/ToolbarPlugin"; import ToolbarPlugin from "./plugins/ToolbarPlugin";
import {Button} from "antd"; import {Button} from "antd";
import { HeadingNode, QuoteNode } from "@lexical/rich-text"; import {HeadingNode, QuoteNode} from "@lexical/rich-text";
import { TableCellNode, TableNode, TableRowNode } from "@lexical/table"; import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
import { ListItemNode, ListNode } from "@lexical/list"; import {ListItemNode, ListNode} from "@lexical/list";
import { CodeHighlightNode, CodeNode } from "@lexical/code"; import {CodeHighlightNode, CodeNode} from "@lexical/code";
import { AutoLinkNode, LinkNode } from "@lexical/link"; import {AutoLinkNode, LinkNode} from "@lexical/link";
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin"; import {LinkPlugin} from "@lexical/react/LexicalLinkPlugin";
import { ListPlugin } from "@lexical/react/LexicalListPlugin"; import {ListPlugin} from "@lexical/react/LexicalListPlugin";
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin"; import {MarkdownShortcutPlugin} from "@lexical/react/LexicalMarkdownShortcutPlugin";
import { TRANSFORMERS } from "@lexical/markdown"; import {TRANSFORMERS} from "@lexical/markdown";
import "./index.less" import "./index.less"
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin"; import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin"; import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin";
import AutoLinkPlugin from "./plugins/AutoLinkPlugin"; import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
import {useEffect,useState} from 'react'; import {useEffect, useState} from 'react';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {importFile} from "../../../utils/File" import {importFile, overWriteFile} from "../../../utils/File"
import {CLEAR_HISTORY_COMMAND} from "lexical"; import {CLEAR_HISTORY_COMMAND} from "lexical";
import ActionPlugin from "../../../components/ActionPlug" import ActionPlugin from "../../../components/ActionPlug"
import {func} from "prop-types"; import {func} from "prop-types";
import store from "../../../redux/store";
import {useSelector} from 'react-redux'
import {SAVE} from "../../../utils/HotkeyConst";
import md5 from "md5"
function Placeholder() { function Placeholder() {
return <div className="editor-placeholder">Enter some rich text...</div>; return <div className="editor-placeholder">Enter some rich text...</div>;
} }
const editorConfig = { const editorConfig = {
// The editor theme // The editor theme
theme: ExampleTheme, theme: ExampleTheme,
// Handling of errors during update // Handling of errors during update
onError(error) { onError(error) {
throw error; throw error;
}, },
// Any custom nodes go here // Any custom nodes go here
nodes: [ nodes: [
HeadingNode, HeadingNode,
ListNode, ListNode,
ListItemNode, ListItemNode,
QuoteNode, QuoteNode,
CodeNode, CodeNode,
CodeHighlightNode, CodeHighlightNode,
TableNode, TableNode,
TableCellNode, TableCellNode,
TableRowNode, TableRowNode,
AutoLinkNode, AutoLinkNode,
LinkNode LinkNode
] ]
}; };
function OnChangePlugin({ onChange }) { function OnChangePlugin({onChange}) {
const [editor] = useLexicalComposerContext(); const [editor] = useLexicalComposerContext();
console.log('editor',editor) console.log('editor', editor)
useEffect(() => { useEffect(() => {
return editor.registerUpdateListener(({editorState}) => { return editor.registerUpdateListener(({editorState}) => {
onChange(editorState); onChange(editorState);
}); });
}, [editor, onChange]); }, [editor, onChange]);
} }
function ImportFilePlugin(props) { function ImportFilePlugin(props) {
const [editor] = useLexicalComposerContext(); const [editor] = useLexicalComposerContext();
useEffect(()=>{ useEffect(() => {
importFile(props.filePath).then(value=>{ importFile(props.filePath).then(value => {
console.log('value.toString()',value.toString()) const editorState = editor.parseEditorState(
const editorState = editor.parseEditorState( JSON.stringify(JSON.parse(value.toString()).editorState)
JSON.stringify(JSON.parse(value.toString()).editorState) );
); editor.setEditorState(editorState);
editor.setEditorState(editorState); editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined);
editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined); }).catch(error =>
}).catch(error=> console.error(error)
console.error(error) )
) }, [])
},[]) }
function SaveFilePlugin(props) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
store.subscribe(() => {
let data = store.getState().pushHotkeys.data;
const editorState = {"editorState":editor.getEditorState()};
let resultSave = JSON.stringify(editorState);
if (data === SAVE) {
importFile(props.filePath).then(value => {
if (md5(resultSave)!==md5(JSON.stringify(JSON.parse(value.toString())))){
console.log("保存重写")
overWriteFile(props.filePath, resultSave)
}
}).catch(error =>
console.error(error)
)
}
})
}, editor)
} }
// JSON // JSON
@ -86,41 +111,44 @@ function ImportFilePlugin(props) {
// editor.setEditorState(editorState); // editor.setEditorState(editorState);
export default function Hlexical(props) { export default function Hlexical(props) {
console.log("this.props.filePath:",props.filePath) console.log("this.props.filePath:", props.filePath)
const [editorState, setEditorState] = useState(); const [editorState, setEditorState] = useState();
function onChange(editorState) {
// Call toJSON on the EditorState object, which produces a serialization safe string function onChange(editorState) {
const editorStateJSON = editorState.toJSON(); // Call toJSON on the EditorState object, which produces a serialization safe string
console.log('editorStateJSON',editorStateJSON) const editorStateJSON = editorState.toJSON();
// However, we still have a JavaScript object, so we need to convert it to an actual string with JSON.stringify console.log('editorStateJSON', editorStateJSON)
setEditorState(JSON.stringify(editorStateJSON)); // However, we still have a JavaScript object, so we need to convert it to an actual string with JSON.stringify
} setEditorState(JSON.stringify(editorStateJSON));
return ( }
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container"> return (
{/* 富文本插件 */} <LexicalComposer initialConfig={editorConfig}>
<ToolbarPlugin /> <div className="editor-container">
<div className="editor-inner"> {/* 富文本插件 */}
<RichTextPlugin <ToolbarPlugin/>
contentEditable={<ContentEditable className="editor-input"/>} <div className="editor-inner">
placeholder={<Placeholder/>} <RichTextPlugin
ErrorBoundary={LexicalErrorBoundary} contentEditable={<ContentEditable className="editor-input"/>}
/> placeholder={<Placeholder/>}
<HistoryPlugin/> ErrorBoundary={LexicalErrorBoundary}
{/*黑窗口动态记录当前操作*/} />
<TreeViewPlugin/> <HistoryPlugin/>
<AutoFocusPlugin /> {/*黑窗口动态记录当前操作*/}
{/*<CodeHighlightPlugin />*/} <TreeViewPlugin/>
{/*<ListPlugin />*/} <AutoFocusPlugin/>
{/*<LinkPlugin />*/} {/*<CodeHighlightPlugin />*/}
{/*<AutoLinkPlugin />*/} {/*<ListPlugin />*/}
{/*<ListMaxIndentLevelPlugin maxDepth={7} />*/} {/*<LinkPlugin />*/}
<MarkdownShortcutPlugin transformers={TRANSFORMERS} /> {/*<AutoLinkPlugin />*/}
<OnChangePlugin onChange={onChange}/> {/*<ListMaxIndentLevelPlugin maxDepth={7} />*/}
<ImportFilePlugin filePath={props.filePath}/> <MarkdownShortcutPlugin transformers={TRANSFORMERS}/>
{/*<ActionPlugin/>*/} <OnChangePlugin onChange={onChange}/>
</div> <ImportFilePlugin filePath={props.filePath}/>
</div> <SaveFilePlugin filePath={props.filePath}/>
</LexicalComposer> {/*<ActionPlugin/>*/}
); </div>
</div>
</LexicalComposer>
);
} }

View File

@ -0,0 +1,19 @@
import { createSlice } from '@reduxjs/toolkit'
export const pushHotkeysSlice = createSlice({
name: 'pushHotkeys',
initialState: {
data: ""
},
reducers: {
pushHotkeys: (state, action) => {
console.log("pushHotkeys:pushHotkeys", state, action)
if(action.payload){
state.data = action.payload;
}
}
}
})
export const { pushHotkeys } = pushHotkeysSlice.actions
export default pushHotkeysSlice.reducer

View File

@ -3,12 +3,14 @@ import historyReducer from './historyRecord_reducer'
import redirectReducer from './redirectUrl_reducer' import redirectReducer from './redirectUrl_reducer'
import dirMessageReducer from './dirMessage_reducer' import dirMessageReducer from './dirMessage_reducer'
import clickFileReducer from './clickFile_reducer' import clickFileReducer from './clickFile_reducer'
import pushHotkeysReducer from "./pushHotkeys_reducer";
// 用于支持异步函数 // 用于支持异步函数
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
historyRecord:historyReducer, historyRecord:historyReducer,
redirectUrl:redirectReducer, redirectUrl:redirectReducer,
dirMessage:dirMessageReducer, dirMessage:dirMessageReducer,
pushHotkeys:pushHotkeysReducer,
clickFileMessage:clickFileReducer clickFileMessage:clickFileReducer
} }
}) })

View File

@ -7,6 +7,6 @@ export async function importFile(pathName) {
return await fs.readFile(pathName) return await fs.readFile(pathName)
} }
export function overWriteFile(filePath) { export async function overWriteFile(filePath,jsonText) {
fs.writeFile(filePath,) await fs.writeFile(filePath,jsonText,{"encoding":"utf-8"})
} }

1
src/utils/HotkeyConst.js Normal file
View File

@ -0,0 +1 @@
export const SAVE = 'CTRL+S'

View File

@ -0,0 +1 @@
export const PUSH_HOTKEY = 'pushHotkeys'

View File

@ -5,6 +5,7 @@ const { ipcRenderer } = window.require('electron')
const useIpcRenderer = (keyCallbackMap) => { const useIpcRenderer = (keyCallbackMap) => {
useEffect(() => { useEffect(() => {
Object.keys(keyCallbackMap).forEach(key => { Object.keys(keyCallbackMap).forEach(key => {
console.log("ipcRenderer.on(key, keyCallbackMap[key])",key)
ipcRenderer.on(key, keyCallbackMap[key]) ipcRenderer.on(key, keyCallbackMap[key])
}) })
return () => { return () => {