Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directly edit and save local files #10

Open
wants to merge 6 commits into
base: source
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"codemirror": "^5.65.0",
"github-markdown-css": "3.0.1",
"highlight.js": "9.15.8",
"idb-keyval": "^6.2.1",
"nonaction": "0.0.5",
"normalize.css": "8.0.1",
"react": "^16.14.0",
Expand Down Expand Up @@ -41,7 +42,7 @@
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts --openssl-legacy-provider start",
"start": "PORT=3005 react-scripts --openssl-legacy-provider start",
"build": "react-scripts --openssl-legacy-provider build",
"test": "react-scripts test",
"eject": "react-scripts eject"
Expand Down
69 changes: 30 additions & 39 deletions src/App/Components/Header/Upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,39 @@ import React from 'react';
import uploadFile from '../../Lib/uploadFile.js';

export default props => {
const onChange = e => {
const files = e.currentTarget.files;
if (files.length > 0) {
const reader = new FileReader();
reader.onload = loadEvent => {
if (loadEvent.target.readyState !== 2) return;
if (loadEvent.target.error) {
alert('Error while reading file');
return;
}
const content = loadEvent.target.result;
uploadFile(content);
};
reader.readAsText(e.target.files[0]);

const { openFile, className } = props

const onChange = async () => {

try {
const fileContext = await openFile();

if (fileContext) {
// 保存文件内容
uploadFile(fileContext);

console.log('File opened and saved successfully');
}
} catch (error) {
console.error('Error reading or saving file:', error);
}
};

return (
<p {...props} style={{ position: 'relative' }}>
<input
id="mdFile"
type="file"
style={{ display: 'none' }}
onChange={onChange}
accept=".md"
/>
<label
htmlFor="mdFile"
style={{
position: 'absolute',
opacity: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 2,
cursor: 'pointer'
}}
/>
<span role="img" aria-label="upload">
📁
</span>
<span>Open File</span>
<p className={className} style={{ position: 'relative' }}>
<button onClick={onChange} style={{
background: 'none',
border: 'none',
padding: 0,
font: 'inherit',
cursor: 'pointer'
}}>
<span role="img" aria-label="upload">
📁
</span>
<span>Open File</span>
</button>
</p>
);
};
35 changes: 33 additions & 2 deletions src/App/Components/Header/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import React from "react";
import { useProvided } from 'nonaction';
import styled from "styled-components";
import UploadButton from "./Upload.js";
const Header = ({ className }) => {
import { FileContainer } from "../../Container";
import { initialText } from "../../Container/Hooks/InitialText";

const Header = ({ className }) => {
const { openFile, saveFile, getFileStatus } = useProvided(FileContainer);
const [fileName, setFileName] = React.useState(null);
const [isFileSaved, setIsFileSaved] = React.useState(false);
const onTransfrom = () => {
// get the file name
let candidateTitle = "";
Expand All @@ -20,12 +27,36 @@ const Header = ({ className }) => {
}
window.print();
};

React.useEffect(() => {
// 动态获取文件名
const fetchFileName = async () => {
const { name, isSaved } = await getFileStatus();
setFileName(name);
setIsFileSaved(isSaved);
};
fetchFileName();

// 保存文件
const handleSaveLocalFile = () => {
const content = localStorage.getItem('editor.content') ?? initialText;
saveFile(content, true); // focus on local file
};

document.addEventListener('save:localFile', handleSaveLocalFile);

return () => {
document.removeEventListener('save:localFile', handleSaveLocalFile);
};
}, [saveFile, getFileStatus]);

return (
<header className={className + " no-print"}>
<p className="project"> Markdown Editor </p>

<div className="menu">
<UploadButton className="button upload" />
<p style={{ fontSize: '12px', color: isFileSaved ? 'gray' : '#FF6666', marginRight: '10px' }}>{fileName}</p>
<UploadButton className="button upload" openFile={openFile} />
<p className="button download" onClick={onTransfrom}>
<span role="img" aria-label="download">
🖨️
Expand Down
3 changes: 2 additions & 1 deletion src/App/Components/Markdown/Editor/MirrorEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { initialText } from '../../../Container/Hooks/InitialText';

const editorContent = localStorage.getItem('editor.content') ?? initialText;

const Editor = ({ className, setText }) => {
const Editor = ({ className, setText, saveFile }) => {
return (
<CodeMirror
className={className}
Expand All @@ -22,6 +22,7 @@ const Editor = ({ className, setText }) => {
onChange={(editor, data, value) => {
localStorage.setItem('editor.content', value);
setText(value);
saveFile(value);
}}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions src/App/Components/Markdown/Editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React from 'react';
import styled from 'styled-components';
// import EditArea from './EditArea.js';
import Mirror from './MirrorEditor.js';
const Editor = ({ className, setText, width }) => {
const Editor = ({ className, setText, saveFile, width }) => {
return (
<div style={{ width }} className={className}>
<Mirror setText={setText}/>
<Mirror setText={setText} saveFile={saveFile}/>
</div>
);
};
Expand Down
5 changes: 3 additions & 2 deletions src/App/Components/Markdown/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react';
import styled from 'styled-components';
import { useProvided } from 'nonaction';
import { TextContainer } from '../../Container';
import { TextContainer, FileContainer } from '../../Container';
import Previewer from './Previewer';
import Editor from './Editor';
import DragBar from './DragBar.js';
Expand All @@ -11,6 +11,7 @@ import uploadFile from '../../Lib/uploadFile.js';

const Markdown = ({ className }) => {
const [text, setText] = useProvided(TextContainer);
const { saveDebounceFile } = useProvided(FileContainer);
const [isDrag, setDrag] = useState(false);
const [startX, setStartX] = useState(0);
const [width, setWidth] = useState(window.innerWidth / 2);
Expand Down Expand Up @@ -38,7 +39,7 @@ const Markdown = ({ className }) => {
setWidth(pageX - startX);
}}
>
<Editor className="no-print" width={width} setText={setText} />
<Editor className="no-print" width={width} setText={setText} saveFile={saveDebounceFile} />
<DragBar
className="no-print"
isDrag={isDrag}
Expand Down
130 changes: 130 additions & 0 deletions src/App/Container/Hooks/useFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { useState, useCallback, useRef } from "react";
import { get, set } from 'idb-keyval';

const STORAGE_KEY = 'editor.FileHandle';

const useFile = () => {
const [fileHandle, setFileHandle] = useState(null);
const [isSaved, setIsSaved] = useState(true);
const saveTimeoutRef = useRef(null);

const loadStoredFileHandle = useCallback(async () => {
if (fileHandle) return; // 如果内存中已有文件句柄,就不需要从 IndexedDB 加载

try {
const storedHandle = await get(STORAGE_KEY);
if (storedHandle) {
const permission = await storedHandle.requestPermission({ mode: 'readwrite' });
if (permission === 'granted') {
setFileHandle(storedHandle);
}
}
} catch (error) {
console.error('Error accessing stored file handle:', error);
await set(STORAGE_KEY, null);
}
}, [fileHandle]);

const openFile = useCallback(async () => {
try {
const [handle] = await window.showOpenFilePicker({
types: [
{
description: 'Markdown Files',
accept: { 'text/markdown': ['.md'] }
}
]
});

const options = {
mode: 'readwrite',
};

if ((await handle.queryPermission(options)) !== 'granted'
&& (await handle.requestPermission(options)) !== 'granted') {
alert('Please grant permissions to read & write this file.');
return;
}

setFileHandle(handle);
await set(STORAGE_KEY, handle);

const file = await handle.getFile();
const content = await file.text();
return content;
} catch (error) {
console.error('Error opening new file:', error);
return null;
}
}, []);

const saveFile = useCallback(async (content, isFocusSaveFile = false) => {
let handle = fileHandle;

if (!handle) {
handle = await get(STORAGE_KEY);
if (handle) {
setFileHandle(handle);
}
}

if (!handle && !isFocusSaveFile) {
return;
}

if (!handle) {
try {
handle = await window.showSaveFilePicker({
suggestedName: 'chrome-markdown-editor.md',
types: [
{
description: 'Markdown Files',
accept: { 'text/markdown': ['.md'] }
}
]
});
if (handle) {
setFileHandle(handle);
await set(STORAGE_KEY, handle);
}
} catch (error) {
console.error('Error creating new file:', error);
return;
}
}

try {
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
setIsSaved(true);
console.log('File saved successfully');
} catch (error) {
setIsSaved(false);
console.error('Error saving file:', error);
}
}, [fileHandle]);

const saveDebounceFile = useCallback(async (content, isFocusSaveFile = false) => {
clearTimeout(saveTimeoutRef.current);

saveTimeoutRef.current = setTimeout(async () => {
await saveFile(content, isFocusSaveFile);
console.log('debounceSaveDone', saveTimeoutRef.current);
}, 1000);
}, [saveFile]);

const getFileStatus = useCallback(async () => {
await loadStoredFileHandle();
const name = fileHandle ? fileHandle.name : null;
console.log('loadStoredFileHandle', name);
return {
name,
isSaved: isSaved,
};
}, [fileHandle, loadStoredFileHandle, isSaved]);

return { openFile, saveFile, saveDebounceFile, getFileStatus, loadStoredFileHandle };
};

export default useFile;
5 changes: 4 additions & 1 deletion src/App/Container/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Container } from 'nonaction';
import useText from './Hooks/useText.js';
export const TextContainer = Container(useText);
import useFile from './Hooks/useFile.js';

export const TextContainer = Container(useText);
export const FileContainer = Container(useFile);
4 changes: 2 additions & 2 deletions src/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React from 'react';
import styled from 'styled-components';
import { Header, Markdown } from './Components';
import { Provider } from 'nonaction';
import { TextContainer } from './Container';
import { TextContainer, FileContainer } from './Container';
const App = ({ className }) => {
return (
<div className={className} id="md2pdf-app">
<Provider inject={[TextContainer]}>
<Provider inject={[TextContainer, FileContainer]}>
<Header />
<Markdown />
</Provider>
Expand Down
Loading
Loading