mirror of
https://github.com/usememos/memos.git
synced 2025-03-19 12:10:08 +01:00
Feat: update editor tools (#14)
* fileupload * update memo editor comman tool btns * update editor tools Co-authored-by: lqwakeup <qiaobingxue1998@163.com>
This commit is contained in:
parent
8eb4273e4a
commit
63ddb2917d
1
web/public/icons/image.svg
Normal file
1
web/public/icons/image.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z"/></svg>
|
After Width: | Height: | Size: 296 B |
@ -14,7 +14,7 @@ export interface EditorRefActions {
|
|||||||
getContent: () => string;
|
getContent: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
export interface EditorProps {
|
||||||
className: string;
|
className: string;
|
||||||
initialContent: string;
|
initialContent: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
@ -23,11 +23,13 @@ interface Props {
|
|||||||
showTools: boolean;
|
showTools: boolean;
|
||||||
onConfirmBtnClick: (content: string) => void;
|
onConfirmBtnClick: (content: string) => void;
|
||||||
onCancelBtnClick: () => void;
|
onCancelBtnClick: () => void;
|
||||||
|
onTagTextBtnClick: () => void;
|
||||||
|
onUploadFileBtnClick: () => void;
|
||||||
onContentChange: (content: string) => void;
|
onContentChange: (content: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefActions>) => {
|
const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRefActions>) => {
|
||||||
const {
|
const {
|
||||||
globalState: { useTinyUndoHistoryCache },
|
globalState: { useTinyUndoHistoryCache },
|
||||||
} = useContext(appContext);
|
} = useContext(appContext);
|
||||||
@ -40,6 +42,8 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
|
|||||||
showTools,
|
showTools,
|
||||||
onConfirmBtnClick: handleConfirmBtnClickCallback,
|
onConfirmBtnClick: handleConfirmBtnClickCallback,
|
||||||
onCancelBtnClick: handleCancelBtnClickCallback,
|
onCancelBtnClick: handleCancelBtnClickCallback,
|
||||||
|
onTagTextBtnClick: handleTagTextBtnClickCallback,
|
||||||
|
onUploadFileBtnClick: handleUploadFileBtnClickCallback,
|
||||||
onContentChange: handleContentChangeCallback,
|
onContentChange: handleContentChangeCallback,
|
||||||
} = props;
|
} = props;
|
||||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@ -169,9 +173,14 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
|
|||||||
onKeyDown={handleEditorKeyDown}
|
onKeyDown={handleEditorKeyDown}
|
||||||
></textarea>
|
></textarea>
|
||||||
<div className="common-tools-wrapper">
|
<div className="common-tools-wrapper">
|
||||||
|
<div className="common-tools-container">
|
||||||
<Only when={showTools}>
|
<Only when={showTools}>
|
||||||
<div className={"common-tools-container"}>{/* nth */}</div>
|
<>
|
||||||
|
<img className="action-btn file-upload" src="/icons/tag.svg" onClick={handleTagTextBtnClickCallback} />
|
||||||
|
<img className="action-btn file-upload" src="/icons/image.svg" onClick={handleUploadFileBtnClickCallback} />
|
||||||
|
</>
|
||||||
</Only>
|
</Only>
|
||||||
|
</div>
|
||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<Only when={showCancelBtn}>
|
<Only when={showCancelBtn}>
|
||||||
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
|
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
|
||||||
|
@ -4,7 +4,7 @@ import { globalStateService, locationService, memoService, resourceService } fro
|
|||||||
import utils from "../helpers/utils";
|
import utils from "../helpers/utils";
|
||||||
import { storage } from "../helpers/storage";
|
import { storage } from "../helpers/storage";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import Editor, { EditorRefActions } from "./Editor/Editor";
|
import Editor, { EditorProps, EditorRefActions } from "./Editor/Editor";
|
||||||
import "../less/memo-editor.less";
|
import "../less/memo-editor.less";
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
@ -38,32 +38,14 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUploadFile = async (file: File) => {
|
|
||||||
const { type } = file;
|
|
||||||
|
|
||||||
if (!type.startsWith("image")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!editorRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = await resourceService.upload(file);
|
|
||||||
const url = `/r/${image.id}/${image.filename}`;
|
|
||||||
|
|
||||||
editorRef.current.insertText(url);
|
|
||||||
} catch (error: any) {
|
|
||||||
toastHelper.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePasteEvent = async (event: ClipboardEvent) => {
|
const handlePasteEvent = async (event: ClipboardEvent) => {
|
||||||
if (event.clipboardData && event.clipboardData.files.length > 0) {
|
if (event.clipboardData && event.clipboardData.files.length > 0) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const file = event.clipboardData.files[0];
|
const file = event.clipboardData.files[0];
|
||||||
handleUploadFile(file);
|
const url = await handleUploadFile(file);
|
||||||
|
if (url) {
|
||||||
|
editorRef.current?.insertText(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,7 +53,10 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
|
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const file = event.dataTransfer.files[0];
|
const file = event.dataTransfer.files[0];
|
||||||
handleUploadFile(file);
|
const url = await handleUploadFile(file);
|
||||||
|
if (url) {
|
||||||
|
editorRef.current?.insertText(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,6 +69,23 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleUploadFile = useCallback(async (file: File) => {
|
||||||
|
const { type } = file;
|
||||||
|
|
||||||
|
if (!type.startsWith("image")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const image = await resourceService.upload(file);
|
||||||
|
const url = `/r/${image.id}/${image.filename}`;
|
||||||
|
|
||||||
|
return url;
|
||||||
|
} catch (error: any) {
|
||||||
|
toastHelper.error(error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSaveBtnClick = useCallback(async (content: string) => {
|
const handleSaveBtnClick = useCallback(async (content: string) => {
|
||||||
if (content === "") {
|
if (content === "") {
|
||||||
toastHelper.error("内容不能为空呀");
|
toastHelper.error("内容不能为空呀");
|
||||||
@ -131,9 +133,53 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
setEditorContentCache(content);
|
setEditorContentCache(content);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleTagTextBtnClick = useCallback(() => {
|
||||||
|
if (!editorRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentValue = editorRef.current.getContent();
|
||||||
|
const selectionStart = editorRef.current.element.selectionStart;
|
||||||
|
const prevString = currentValue.slice(0, selectionStart);
|
||||||
|
const nextString = currentValue.slice(selectionStart);
|
||||||
|
let cursorIndex = prevString.length;
|
||||||
|
|
||||||
|
if (prevString.endsWith("# ") && nextString.startsWith(" ")) {
|
||||||
|
editorRef.current.setContent(prevString.slice(0, prevString.length - 2) + nextString.slice(1));
|
||||||
|
cursorIndex = prevString.length - 2;
|
||||||
|
} else {
|
||||||
|
editorRef.current.element.value = prevString + "# " + nextString;
|
||||||
|
cursorIndex = prevString.length + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
editorRef.current?.element.setSelectionRange(cursorIndex, cursorIndex);
|
||||||
|
editorRef.current?.focus();
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUploadFileBtnClick = useCallback(() => {
|
||||||
|
const inputEl = document.createElement("input");
|
||||||
|
inputEl.type = "file";
|
||||||
|
inputEl.multiple = false;
|
||||||
|
inputEl.accept = "image/png, image/gif, image/jpeg";
|
||||||
|
inputEl.onchange = async () => {
|
||||||
|
if (!inputEl.files || inputEl.files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = inputEl.files[0];
|
||||||
|
const url = await handleUploadFile(file);
|
||||||
|
if (url) {
|
||||||
|
editorRef.current?.insertText(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inputEl.click();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const showEditStatus = Boolean(globalState.editMemoId);
|
const showEditStatus = Boolean(globalState.editMemoId);
|
||||||
|
|
||||||
const editorConfig = useMemo(
|
const editorConfig: EditorProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
className: "memo-editor",
|
className: "memo-editor",
|
||||||
initialContent: getEditorContentCache(),
|
initialContent: getEditorContentCache(),
|
||||||
@ -143,6 +189,8 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
showTools: true,
|
showTools: true,
|
||||||
onConfirmBtnClick: handleSaveBtnClick,
|
onConfirmBtnClick: handleSaveBtnClick,
|
||||||
onCancelBtnClick: handleCancelBtnClick,
|
onCancelBtnClick: handleCancelBtnClick,
|
||||||
|
onTagTextBtnClick: handleTagTextBtnClick,
|
||||||
|
onUploadFileBtnClick: handleUploadFileBtnClick,
|
||||||
onContentChange: handleContentChange,
|
onContentChange: handleContentChange,
|
||||||
}),
|
}),
|
||||||
[showEditStatus]
|
[showEditStatus]
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
> .common-editor-inputer {
|
> .common-editor-inputer {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 24px;
|
padding-top: 4px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
min-height: 40px;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
@ -19,7 +21,6 @@
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
margin-bottom: 4px;
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
.hide-scroll-bar();
|
.hide-scroll-bar();
|
||||||
|
|
||||||
@ -40,6 +41,19 @@
|
|||||||
|
|
||||||
> .common-tools-container {
|
> .common-tools-container {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
|
|
||||||
|
> .action-btn {
|
||||||
|
width: 18px;
|
||||||
|
height: auto;
|
||||||
|
margin-right: 8px;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .btns-container {
|
> .btns-container {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user