Skip to content

Commit

Permalink
에디터 placeholder 구현 (#173)
Browse files Browse the repository at this point in the history
* feat: gfm 적용

* fix: 초기 템플릿이 적용되지 않던 문제 수정

* feat: 에디터에 placeholder 제목 적용

* style: 주석 제거 및 로그 문구 수정
  • Loading branch information
heegenie authored Dec 2, 2024
1 parent f5bc9ec commit a85aca7
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 8 deletions.
10 changes: 10 additions & 0 deletions packages/frontend/src/components/note/Editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@
outline: none;
}

.ProseMirror[data-placeholder]::before {
color: #a9a9a9;
position: absolute;
content: attr(data-placeholder);
pointer-events: none;
font-weight: 700;
font-size: 36px;
line-height: 40px;
}

milkdown-code-block {
display: block;
position: relative;
Expand Down
12 changes: 8 additions & 4 deletions packages/frontend/src/hooks/useMilkdownCollab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type useMilkdownCollabProps = {
roomName: string;
};

const template = `# `;

export default function useMilkdownCollab({
editor,
websocketUrl,
Expand All @@ -21,19 +23,21 @@ export default function useMilkdownCollab({
if (!editor) return undefined;

const doc = new Y.Doc();

const wsProvider = new WebsocketProvider(websocketUrl, roomName, doc, {
connect: true,
});

let collabService: CollabService;

wsProvider.once("synced", async (isSynced: boolean) => {
if (isSynced) {
console.log(`성공적으로 연결됨: ${wsProvider.url}`);
collabService.applyTemplate(template);
console.log(`Successfully connected: ${wsProvider.url}`);
}
});

let collabService: CollabService;

// NOTE - flushSync가 lifecycle 내에서 발생하는 것을 방지하기 위해 settimeout으로 묶어서 micro task로 취급되게 함
// NOTE - flushSync가 lifecycle 내에서 발생하는 것을 방지하기 위해 setTimeout으로 묶어서 micro task로 취급되게 함
setTimeout(() => {
editor.action((ctx: Ctx) => {
collabService = ctx.get(collabServiceCtx);
Expand Down
13 changes: 9 additions & 4 deletions packages/frontend/src/hooks/useMilkdownEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {
codeBlockComponent,
codeBlockConfig,
} from "@milkdown/kit/component/code-block";
import { Editor, defaultValueCtx, rootCtx } from "@milkdown/kit/core";
import { Editor, rootCtx } from "@milkdown/kit/core";
import { Ctx } from "@milkdown/kit/ctx";
import { block } from "@milkdown/kit/plugin/block";
import { cursor } from "@milkdown/kit/plugin/cursor";
import { commonmark } from "@milkdown/kit/preset/commonmark";
import { gfm } from "@milkdown/kit/preset/gfm";
import { collab } from "@milkdown/plugin-collab";
import { useEditor } from "@milkdown/react";
import { nord } from "@milkdown/theme-nord";
Expand All @@ -21,6 +22,8 @@ import {
} from "@prosemirror-adapter/react";
import { basicSetup } from "codemirror";

import { placeholder, placeholderCtx } from "@/lib/milkdown-plugin-placeholder";

const check = html`
<svg
xmlns="http://www.w3.org/2000/svg"
Expand All @@ -39,12 +42,12 @@ const check = html`
`;

type useMilkdownEditorProps = {
initialMarkdown?: string;
placeholderValue?: string;
BlockView: ReactPluginViewComponent;
};

export default function useMilkdownEditor({
initialMarkdown = "# Note",
placeholderValue = "제목을 입력하세요",
BlockView,
}: useMilkdownEditorProps) {
const pluginViewFactory = usePluginViewFactory();
Expand All @@ -53,7 +56,7 @@ export default function useMilkdownEditor({
return Editor.make()
.config((ctx: Ctx) => {
ctx.set(rootCtx, root);
ctx.set(defaultValueCtx, initialMarkdown);
ctx.set(placeholderCtx, placeholderValue);
ctx.set(block.key, {
view: pluginViewFactory({
component: BlockView,
Expand All @@ -71,6 +74,8 @@ export default function useMilkdownEditor({
})
.config(nord)
.use(commonmark)
.use(gfm)
.use(placeholder)
.use(codeBlockComponent)
.use(block)
.use(cursor)
Expand Down
56 changes: 56 additions & 0 deletions packages/frontend/src/lib/milkdown-plugin-placeholder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { InitReady, prosePluginsCtx } from "@milkdown/kit/core";
import type { MilkdownPlugin, TimerType } from "@milkdown/kit/ctx";
import { createSlice, createTimer } from "@milkdown/kit/ctx";
import { Plugin, PluginKey } from "@milkdown/kit/prose/state";
import type { EditorView } from "@milkdown/kit/prose/view";

export const placeholderCtx = createSlice("Type something...", "placeholder");
export const placeholderTimerCtx = createSlice(
[] as TimerType[],
"editorStateTimer",
);
export const PlaceholderReady = createTimer("PlaceholderReady");

const key = new PluginKey("MILKDOWN_PLACEHOLDER");

export const placeholder: MilkdownPlugin = (ctx) => {
ctx
.inject(placeholderCtx)
.inject(placeholderTimerCtx, [InitReady])
.record(PlaceholderReady);

return async () => {
await ctx.waitTimers(placeholderTimerCtx);

const prosePlugins = ctx.get(prosePluginsCtx);

const update = (view: EditorView) => {
const placeholder = ctx.get(placeholderCtx);
const { doc } = view.state;
if (
view.editable &&
doc.childCount === 1 &&
doc.firstChild?.isTextblock &&
doc.firstChild?.content.size === 0
) {
view.dom.setAttribute("data-placeholder", placeholder);
} else {
view.dom.removeAttribute("data-placeholder");
}
};

const plugins = [
...prosePlugins,
new Plugin({
key,
view(view) {
update(view);
return { update };
},
}),
];

ctx.set(prosePluginsCtx, plugins);
ctx.done(PlaceholderReady);
};
};

0 comments on commit a85aca7

Please sign in to comment.