Commit b2c22977 authored by steven's avatar steven

feat: update memo editor with uploading resources

parent c0edb72b
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react"; import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
import { useTranslation } from "react-i18next";
import useRefresh from "../../hooks/useRefresh"; import useRefresh from "../../hooks/useRefresh";
import "../../less/editor.less"; import "../../less/editor.less";
export interface EditorRefActions { export interface EditorRefActions {
element: HTMLTextAreaElement;
focus: FunctionType; focus: FunctionType;
insertText: (text: string) => void; insertText: (text: string) => void;
setContent: (text: string) => void; setContent: (text: string) => void;
...@@ -12,29 +10,19 @@ export interface EditorRefActions { ...@@ -12,29 +10,19 @@ export interface EditorRefActions {
getCursorPosition: () => number; getCursorPosition: () => number;
} }
interface EditorProps { interface Props {
className: string; className: string;
initialContent: string; initialContent: string;
placeholder: string; placeholder: string;
fullscreen: boolean; fullscreen: boolean;
showConfirmBtn: boolean;
tools?: ReactNode; tools?: ReactNode;
onConfirmBtnClick: (content: string) => void; onPaste: (event: React.ClipboardEvent) => 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: EditorProps, ref: React.ForwardedRef<EditorRefActions>) => { const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefActions>) => {
const { const { className, initialContent, placeholder, fullscreen, onPaste, onContentChange: handleContentChangeCallback } = props;
className,
initialContent,
placeholder,
fullscreen,
showConfirmBtn,
onConfirmBtnClick: handleConfirmBtnClickCallback,
onContentChange: handleContentChangeCallback,
} = props;
const { t } = useTranslation();
const editorRef = useRef<HTMLTextAreaElement>(null); const editorRef = useRef<HTMLTextAreaElement>(null);
const refresh = useRefresh(); const refresh = useRefresh();
...@@ -54,7 +42,6 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef ...@@ -54,7 +42,6 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
useImperativeHandle( useImperativeHandle(
ref, ref,
() => ({ () => ({
element: editorRef.current as HTMLTextAreaElement,
focus: () => { focus: () => {
editorRef.current?.focus(); editorRef.current?.focus();
}, },
...@@ -94,25 +81,6 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef ...@@ -94,25 +81,6 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
refresh(); refresh();
}, []); }, []);
const handleEditorKeyDown = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
event.stopPropagation();
if (event.code === "Enter") {
if (event.metaKey || event.ctrlKey) {
handleCommonConfirmBtnClick();
}
}
}, []);
const handleCommonConfirmBtnClick = useCallback(() => {
if (!editorRef.current) {
return;
}
handleConfirmBtnClickCallback(editorRef.current.value);
editorRef.current.value = "";
}, []);
return ( return (
<div className={"common-editor-wrapper " + className}> <div className={"common-editor-wrapper " + className}>
<textarea <textarea
...@@ -120,19 +88,9 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef ...@@ -120,19 +88,9 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
rows={1} rows={1}
placeholder={placeholder} placeholder={placeholder}
ref={editorRef} ref={editorRef}
onPaste={onPaste}
onInput={handleEditorInput} onInput={handleEditorInput}
onKeyDown={handleEditorKeyDown}
></textarea> ></textarea>
<div className="common-tools-wrapper">
<div className="common-tools-container">{props.tools !== undefined && props.tools}</div>
<div className="btns-container">
{showConfirmBtn && (
<button className="action-btn confirm-btn" disabled={editorRef.current?.value === ""} onClick={handleCommonConfirmBtnClick}>
{t("editor.save")} <span className="icon-text">✍️</span>
</button>
)}
</div>
</div>
</div> </div>
); );
}); });
......
This diff is collapsed.
...@@ -133,6 +133,20 @@ export function deleteResourceById(id: ResourceId) { ...@@ -133,6 +133,20 @@ export function deleteResourceById(id: ResourceId) {
return axios.delete(`/api/resource/${id}`); return axios.delete(`/api/resource/${id}`);
} }
export function getMemoResourceList(memoId: MemoId) {
return axios.get<ResponseObject<Resource[]>>(`/api/memo/${memoId}/resource`);
}
export function upsertMemoResource(memoId: MemoId, resourceId: ResourceId) {
return axios.post<ResponseObject<Resource>>(`/api/memo/${memoId}/resource`, {
resourceId,
});
}
export function deleteMemoResource(memoId: MemoId, resourceId: ResourceId) {
return axios.delete(`/api/memo/${memoId}/resource/${resourceId}`);
}
export function getTagList(tagFind?: TagFind) { export function getTagList(tagFind?: TagFind) {
const queryList = []; const queryList = [];
if (tagFind?.creatorId) { if (tagFind?.creatorId) {
......
...@@ -25,44 +25,4 @@ ...@@ -25,44 +25,4 @@
} }
} }
} }
> .common-tools-wrapper {
@apply w-full flex flex-row justify-between items-center;
> .common-tools-container {
@apply flex flex-row justify-start items-center;
> .action-btn {
@apply flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer opacity-60 hover:opacity-90 hover:bg-gray-300 hover:shadow;
> .icon-img {
@apply w-5 h-5 mx-auto flex flex-row justify-center items-center;
}
> .tip-text {
@apply hidden ml-1 text-xs leading-5 text-gray-700 border border-gray-300 rounded-xl px-2;
}
}
}
> .btns-container {
@apply grow-0 shrink-0 flex flex-row justify-end items-center;
> .action-btn {
@apply border-none select-none cursor-pointer py-1 px-3 rounded text-sm hover:opacity-80;
}
> .cancel-btn {
@apply text-gray-500 bg-transparent mr-2;
}
> .confirm-btn {
@apply shadow cursor-pointer px-3 py-0 leading-8 bg-green-600 text-white text-sm hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60;
> .icon-text {
@apply text-base ml-1;
}
}
}
}
} }
...@@ -63,28 +63,88 @@ ...@@ -63,28 +63,88 @@
> .memo-editor { > .memo-editor {
@apply flex flex-col justify-start items-start relative w-full h-auto bg-white; @apply flex flex-col justify-start items-start relative w-full h-auto bg-white;
}
.tag-action { > .common-tools-wrapper {
@apply relative; @apply w-full flex flex-row justify-between items-center;
&:hover { > .common-tools-container {
> .tag-list { @apply flex flex-row justify-start items-center;
@apply flex;
} > .action-btn {
} @apply flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer opacity-60 hover:opacity-90 hover:bg-gray-300 hover:shadow;
> .tag-list { &.tag-action {
@apply hidden flex-col justify-start items-start absolute top-6 left-0 mt-1 p-1 z-1 rounded w-32 max-h-52 overflow-auto font-mono bg-black; @apply relative;
> .item-container { &:hover {
@apply w-full text-white cursor-pointer rounded text-sm leading-6 px-2 hover:bg-gray-700; > .tag-list {
@apply flex;
}
}
> .tag-list {
@apply hidden flex-col justify-start items-start absolute top-6 left-0 mt-1 p-1 z-1 rounded w-32 max-h-52 overflow-auto font-mono bg-black;
> .item-container {
@apply w-full text-white cursor-pointer rounded text-sm leading-6 px-2 hover:bg-gray-700;
}
> .tip-text {
@apply w-full text-sm text-gray-200 leading-6 px-2 cursor-default;
}
}
}
> .icon-img {
@apply w-5 h-5 mx-auto flex flex-row justify-center items-center;
} }
> .tip-text { > .tip-text {
@apply w-full text-sm text-gray-200 leading-6 px-2 cursor-default; @apply hidden ml-1 text-xs leading-5 text-gray-700 border border-gray-300 rounded-xl px-2;
} }
} }
} }
> .btns-container {
@apply grow-0 shrink-0 flex flex-row justify-end items-center;
> .action-btn {
@apply border-none select-none cursor-pointer py-1 px-3 rounded text-sm hover:opacity-80;
}
> .cancel-btn {
@apply text-gray-500 bg-transparent mr-2;
}
> .confirm-btn {
@apply shadow cursor-pointer px-3 py-0 leading-8 bg-green-600 text-white text-sm hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60;
> .icon-text {
@apply text-base ml-1;
}
}
}
}
> .resource-list-wrapper {
@apply w-full flex flex-row justify-start flex-wrap;
> .resource-container {
@apply mt-1 mr-1 flex flex-row justify-start items-center flex-nowrap bg-gray-50 px-2 py-1 rounded cursor-pointer hover:bg-gray-100;
> .icon-img {
@apply w-4 h-auto mr-1 text-gray-500;
}
> .name-text {
@apply text-gray-500 text-sm max-w-xs truncate font-mono;
}
> .close-icon {
@apply w-4 h-auto ml-1 text-gray-500 hover:text-gray-800;
}
}
} }
.emoji-picker-react { .emoji-picker-react {
......
...@@ -93,6 +93,7 @@ const memoService = { ...@@ -93,6 +93,7 @@ const memoService = {
const { data } = (await api.createMemo(memoCreate)).data; const { data } = (await api.createMemo(memoCreate)).data;
const memo = convertResponseModelMemo(data); const memo = convertResponseModelMemo(data);
store.dispatch(createMemo(memo)); store.dispatch(createMemo(memo));
return memo;
}, },
patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => { patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment