diff --git a/apps/client/src/ee/ai-chat/components/chat-input.tsx b/apps/client/src/ee/ai-chat/components/chat-input.tsx index 88c270542..d01381874 100644 --- a/apps/client/src/ee/ai-chat/components/chat-input.tsx +++ b/apps/client/src/ee/ai-chat/components/chat-input.tsx @@ -177,7 +177,7 @@ export default function ChatInput({ }, []); const handleSubmit = useCallback(() => { - if (!editor || isStreaming) return; + if (!editor || editor.isDestroyed || isStreaming) return; const json = editor.getJSON(); const text = editorJsonToText(json).trim(); const readyAttachments = pendingAttachments.filter((a) => !a.uploading); @@ -264,7 +264,7 @@ export default function ChatInput({ }); useEffect(() => { - if (editor && autofocus) { + if (editor && !editor.isDestroyed && autofocus) { editor.commands.focus(); } }, [editor]); diff --git a/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx b/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx index d009c2c4a..f9fa2df3e 100644 --- a/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx +++ b/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx @@ -21,7 +21,7 @@ import { ResultPreview } from "./result-preview.tsx"; import classes from "./ai-menu.module.css"; import { marked } from "marked"; import { DOMSerializer } from "@tiptap/pm/model"; -import { copyToClipboard, htmlToMarkdown } from "@docmost/editor-ext"; +import { copyToClipboard, htmlToMarkdown, isEditorReady } from "@docmost/editor-ext"; import { useLocation } from "react-router-dom"; interface EditorAiMenuProps { @@ -56,7 +56,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { }); }, [prompt, output, activeCommandSet]); const updateMenuPlacement = useCallback(() => { - if (!editor || !showAiMenu) return; + if (!isEditorReady(editor) || !showAiMenu) return; const { view } = editor; const { from, to } = editor.state.selection; @@ -102,7 +102,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { ); const handleGenerate = useCallback( (item?: CommandItem) => { - if (!editor || isLoading) return; + if (!isEditorReady(editor) || isLoading) return; let command: CommandItem | null = item || null; @@ -165,6 +165,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { return setActiveCommandSet("main"); } if (item.id === "result-replace") { + if (!isEditorReady(editor)) return setShowAiMenu(false); const chain = editor.chain().focus(); if (lastAction.action === AiAction.CONTINUE_WRITING) { @@ -190,6 +191,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { return setShowAiMenu(false); } if (item.id === "result-insert-below") { + if (!isEditorReady(editor)) return setShowAiMenu(false); editor .chain() .focus() @@ -253,7 +255,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { ); useEffect(() => { - if (!editor) return; + if (!isEditorReady(editor)) return; const handleClose = () => setShowAiMenu(false); const observer = new ResizeObserver(() => { diff --git a/apps/client/src/ee/comment/components/resolve-comment.tsx b/apps/client/src/ee/comment/components/resolve-comment.tsx index 4e22fb71c..2b59b1601 100644 --- a/apps/client/src/ee/comment/components/resolve-comment.tsx +++ b/apps/client/src/ee/comment/components/resolve-comment.tsx @@ -3,6 +3,7 @@ import { IconCircleCheck, IconCircleCheckFilled } from "@tabler/icons-react"; import { useResolveCommentMutation } from "@/ee/comment/queries/comment-query"; import { useTranslation } from "react-i18next"; import { Editor } from "@tiptap/react"; +import { isEditorReady } from "@docmost/editor-ext"; interface ResolveCommentProps { editor: Editor; @@ -31,7 +32,7 @@ function ResolveComment({ resolved: !isResolved, }); - if (editor) { + if (isEditorReady(editor)) { editor.commands.setCommentResolved(commentId, !isResolved); } diff --git a/apps/client/src/ee/template/pages/template-editor.tsx b/apps/client/src/ee/template/pages/template-editor.tsx index cef891060..68d692782 100644 --- a/apps/client/src/ee/template/pages/template-editor.tsx +++ b/apps/client/src/ee/template/pages/template-editor.tsx @@ -105,7 +105,7 @@ export default function TemplateEditor() { // Load template data into editor useEffect(() => { - if (existingTemplate && editor) { + if (existingTemplate && editor && !editor.isDestroyed) { loadedRef.current = false; setTitle(existingTemplate.title || ""); setIcon(existingTemplate.icon || null); @@ -383,7 +383,8 @@ export default function TemplateEditor() { onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); - editor?.commands.focus("start"); + if (editor && !editor.isDestroyed) + editor.commands.focus("start"); } }} /> diff --git a/apps/client/src/features/comment/components/comment-dialog.tsx b/apps/client/src/features/comment/components/comment-dialog.tsx index ac7107f9b..6eadc86a6 100644 --- a/apps/client/src/features/comment/components/comment-dialog.tsx +++ b/apps/client/src/features/comment/components/comment-dialog.tsx @@ -15,6 +15,7 @@ import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; import { useCreateCommentMutation } from "@/features/comment/queries/comment-query"; import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom"; import { useEditor } from "@tiptap/react"; +import { isEditorReady } from "@docmost/editor-ext"; import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; import { useTranslation } from "react-i18next"; @@ -48,11 +49,14 @@ function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) { setReadOnlyCommentData(null); } else { setShowCommentPopup(false); - editor.chain().focus().unsetCommentDecoration().run(); + if (isEditorReady(editor)) { + editor.chain().focus().unsetCommentDecoration().run(); + } } }; const getSelectedText = () => { + if (!isEditorReady(editor)) return ""; const { from, to } = editor.state.selection; return editor.state.doc.textBetween(from, to); }; @@ -74,24 +78,28 @@ function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) { const createdComment = await createCommentMutation.mutateAsync(commentData); - editor - .chain() - .setComment(createdComment.id) - .unsetCommentDecoration() - .run(); + if (isEditorReady(editor)) { + editor + .chain() + .setComment(createdComment.id) + .unsetCommentDecoration() + .run(); + editor.commands.setTextSelection({ + from: editor.view.state.selection.from, + to: editor.view.state.selection.from, + }); + } setActiveCommentId(createdComment.id); - editor.commands.setTextSelection({ from: editor.view.state.selection.from, to: editor.view.state.selection.from }); - setAsideState({ tab: "comments", isAsideOpen: true }); setTimeout(() => { const selector = `div[data-comment-id="${createdComment.id}"]`; const commentElement = document.querySelector(selector); commentElement?.scrollIntoView({ behavior: "smooth", block: "center" }); - editor.view.dispatch( - editor.state.tr.scrollIntoView() - ); + if (isEditorReady(editor)) { + editor.view.dispatch(editor.state.tr.scrollIntoView()); + } }, 400); } finally { diff --git a/apps/client/src/features/comment/components/comment-editor.tsx b/apps/client/src/features/comment/components/comment-editor.tsx index 525cc2a1c..eeb0983a0 100644 --- a/apps/client/src/features/comment/components/comment-editor.tsx +++ b/apps/client/src/features/comment/components/comment-editor.tsx @@ -112,22 +112,24 @@ const CommentEditor = forwardRef( // websocket on another browser). Skip for editable editors to avoid // resetting the cursor position on every keystroke. useEffect(() => { - if (!editable && commentEditor && defaultContent) { + if (!editable && commentEditor && !commentEditor.isDestroyed && defaultContent) { commentEditor.commands.setContent(defaultContent); } }, [defaultContent, editable, commentEditor]); useEffect(() => { setTimeout(() => { - if (autofocus) { - commentEditor?.commands.focus("end"); + if (autofocus && commentEditor && !commentEditor.isDestroyed) { + commentEditor.commands.focus("end"); } }, 10); }, [commentEditor, autofocus]); useImperativeHandle(ref, () => ({ clearContent: () => { - commentEditor.commands.clearContent(); + if (commentEditor && !commentEditor.isDestroyed) { + commentEditor.commands.clearContent(); + } }, })); diff --git a/apps/client/src/features/comment/components/comment-list-item.tsx b/apps/client/src/features/comment/components/comment-list-item.tsx index 8af21d28e..7cedcd848 100644 --- a/apps/client/src/features/comment/components/comment-list-item.tsx +++ b/apps/client/src/features/comment/components/comment-list-item.tsx @@ -5,6 +5,7 @@ import { useAtom, useAtomValue } from "jotai"; import { useTimeAgo } from "@/hooks/use-time-ago"; import CommentEditor from "@/features/comment/components/comment-editor"; import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms"; +import { isEditorReady } from "@docmost/editor-ext"; import CommentActions from "@/features/comment/components/comment-actions"; import CommentMenu from "@/features/comment/components/comment-menu"; import { useHasFeature } from "@/ee/hooks/use-feature"; @@ -75,7 +76,9 @@ function CommentListItem({ async function handleDeleteComment() { try { await deleteCommentMutation.mutateAsync(comment.id); - editor?.commands.unsetComment(comment.id); + if (isEditorReady(editor)) { + editor.commands.unsetComment(comment.id); + } } catch (error) { console.error("Failed to delete comment:", error); } @@ -93,7 +96,7 @@ function CommentListItem({ resolved: !isResolved, }); - if (editor) { + if (isEditorReady(editor)) { editor.commands.setCommentResolved(comment.id, !isResolved); } } catch (error) { diff --git a/apps/client/src/features/editor/components/bubble-menu/readonly-bubble-menu.tsx b/apps/client/src/features/editor/components/bubble-menu/readonly-bubble-menu.tsx index 7998becf4..b4dac675e 100644 --- a/apps/client/src/features/editor/components/bubble-menu/readonly-bubble-menu.tsx +++ b/apps/client/src/features/editor/components/bubble-menu/readonly-bubble-menu.tsx @@ -11,6 +11,7 @@ import { } from "@/features/comment/atoms/comment-atom"; import { useTranslation } from "react-i18next"; import { getRelativeSelection, ySyncPluginKey } from "@tiptap/y-tiptap"; +import { isEditorReady } from "@docmost/editor-ext"; type ReadonlyBubbleMenuProps = { editor: Editor; @@ -29,6 +30,10 @@ export const ReadonlyBubbleMenu: FC = ({ editor }) => { const updateMenuPosition = useCallback(() => { if (isInteractingRef.current) return; + if (!isEditorReady(editor)) { + setVisible(false); + return; + } const pmSelection = editor.state.selection; if (!(pmSelection instanceof TextSelection) || pmSelection.empty) { @@ -97,7 +102,7 @@ export const ReadonlyBubbleMenu: FC = ({ editor }) => { }, [showReadOnlyCommentPopup]); const handleCommentClick = () => { - if (!editor) return; + if (!isEditorReady(editor)) return; const view = editor.view; const ystate = ySyncPluginKey.getState(view.state); diff --git a/apps/client/src/features/editor/components/link/link-view.tsx b/apps/client/src/features/editor/components/link/link-view.tsx index 46227ef90..daa5bb5de 100644 --- a/apps/client/src/features/editor/components/link/link-view.tsx +++ b/apps/client/src/features/editor/components/link/link-view.tsx @@ -28,7 +28,7 @@ import { usePageQuery } from "@/features/page/queries/page-query.ts"; import { useSharePageQuery } from "@/features/share/queries/share-query.ts"; import { buildSharedPageUrl } from "@/features/page/page.utils.ts"; import { extractPageSlugId } from "@/lib"; -import { sanitizeUrl, copyToClipboard } from "@docmost/editor-ext"; +import { sanitizeUrl, copyToClipboard, isEditorReady } from "@docmost/editor-ext"; import { normalizeUrl } from "@/lib/utils"; const parseInternalLink = ( @@ -313,7 +313,9 @@ export default function LinkView(props: MarkViewProps) { ); const handleRemoveLink = useCallback(() => { - editor.chain().focus().extendMarkRange("link").unsetLink().run(); + if (isEditorReady(editor)) { + editor.chain().focus().extendMarkRange("link").unsetLink().run(); + } setPopoverState("closed"); }, [editor]); @@ -345,7 +347,7 @@ export default function LinkView(props: MarkViewProps) { NodeFilter.SHOW_TEXT, ); const textNode = walker.nextNode(); - if (textNode) { + if (textNode && isEditorReady(editor)) { const view = editor.view as any; view.domObserver.stop(); textNode.nodeValue = val; diff --git a/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx b/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx index a6054d222..d64d2614c 100644 --- a/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx +++ b/apps/client/src/features/editor/components/search-and-replace/search-and-replace-dialog.tsx @@ -17,6 +17,7 @@ import { IconX, } from "@tabler/icons-react"; import { useEditor } from "@tiptap/react"; +import { isEditorReady } from "@docmost/editor-ext"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { searchAndReplaceStateAtom } from "@/features/editor/components/search-and-replace/atoms/search-and-replace-state-atom.ts"; import { useAtom } from "jotai"; @@ -64,13 +65,13 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo replaceButtonToggle(); } // Clear search term in editor - if (editor) { + if (isEditorReady(editor)) { editor.commands.setSearchTerm(""); } }; const goToSelection = () => { - if (!editor) return; + if (!isEditorReady(editor)) return; const { results, resultIndex } = editor.storage.searchAndReplace; //TODO: check type error @@ -90,27 +91,32 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo }; const next = () => { + if (!isEditorReady(editor)) return; editor.commands.nextSearchResult(); goToSelection(); }; const previous = () => { + if (!isEditorReady(editor)) return; editor.commands.previousSearchResult(); goToSelection(); }; const replace = () => { + if (!isEditorReady(editor)) return; editor.commands.setReplaceTerm(replaceText); editor.commands.replace(); goToSelection(); }; const replaceAll = () => { + if (!isEditorReady(editor)) return; editor.commands.setReplaceTerm(replaceText); editor.commands.replaceAll(); }; useEffect(() => { + if (!isEditorReady(editor)) return; editor.commands.setSearchTerm(searchText); editor.commands.resetIndex(); editor.commands.selectCurrentItem(); @@ -118,6 +124,7 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo const handleOpenEvent = (e) => { setPageFindState({ isOpen: true }); + if (!isEditorReady(editor)) return; const selectedText = editor.state.doc.textBetween( editor.state.selection.from, editor.state.selection.to, @@ -149,6 +156,7 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo }, [pageFindState.isOpen]); useEffect(() => { + if (!isEditorReady(editor)) return; editor.commands.setCaseSensitive(caseSensitive.isCaseSensitive); editor.commands.resetIndex(); goToSelection(); diff --git a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx index 4b76e08dd..2f2922e26 100644 --- a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx +++ b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx @@ -50,6 +50,7 @@ export const TableOfContents: FC = (props) => { const headerPaddingRef = useRef(null); const handleScrollToHeading = (position: number) => { + if (!props.editor || props.editor.isDestroyed) return; const { view } = props.editor; const headerOffset = parseInt( @@ -73,16 +74,21 @@ export const TableOfContents: FC = (props) => { }; const handleUpdate = () => { - const result = recalculateLinks(props.editor?.$nodes("heading")); + if (!props.editor || props.editor.isDestroyed) return; + + const result = recalculateLinks(props.editor.$nodes("heading")); setLinks(result.links); setHeadingDOMNodes(result.nodes); }; useEffect(() => { + // "create" repopulates once the editor view mounts after this component + props.editor?.on("create", handleUpdate); props.editor?.on("update", handleUpdate); return () => { + props.editor?.off("create", handleUpdate); props.editor?.off("update", handleUpdate); }; }, [props.editor]); diff --git a/apps/client/src/features/editor/components/table/handle/cell-chevron.tsx b/apps/client/src/features/editor/components/table/handle/cell-chevron.tsx index db79844e8..4fde678cb 100644 --- a/apps/client/src/features/editor/components/table/handle/cell-chevron.tsx +++ b/apps/client/src/features/editor/components/table/handle/cell-chevron.tsx @@ -9,7 +9,7 @@ import { Menu, UnstyledButton } from "@mantine/core"; import { IconChevronDown } from "@tabler/icons-react"; import clsx from "clsx"; import { useTranslation } from "react-i18next"; -import { isCellSelection } from "@docmost/editor-ext"; +import { isCellSelection, isEditorReady } from "@docmost/editor-ext"; import { CellChevronMenu } from "./menus/cell-chevron-menu"; import classes from "./handle.module.css"; @@ -27,7 +27,9 @@ export const CellChevron = React.memo(function CellChevron({ tablePos, }: CellChevronProps) { const { t } = useTranslation(); - const cellDom = editor.view.nodeDOM(cellPos) as HTMLElement | null; + const cellDom = isEditorReady(editor) + ? (editor.view.nodeDOM(cellPos) as HTMLElement | null) + : null; const { refs, floatingStyles, middlewareData } = useFloating({ placement: "top-end", @@ -61,6 +63,7 @@ export const CellChevron = React.memo(function CellChevron({ }); const onOpen = useCallback(() => { + if (!isEditorReady(editor)) return; const current = editor.state.selection; // Preserve an existing multi-cell CellSelection that already covers @@ -86,6 +89,7 @@ export const CellChevron = React.memo(function CellChevron({ }, [editor, cellPos]); const onClose = useCallback(() => { + if (!isEditorReady(editor)) return; editor.commands.unfreezeHandles(); }, [editor]); diff --git a/apps/client/src/features/editor/components/table/handle/hooks/use-column-row-menu-lifecycle.ts b/apps/client/src/features/editor/components/table/handle/hooks/use-column-row-menu-lifecycle.ts index a30595597..7a9bfc8f7 100644 --- a/apps/client/src/features/editor/components/table/handle/hooks/use-column-row-menu-lifecycle.ts +++ b/apps/client/src/features/editor/components/table/handle/hooks/use-column-row-menu-lifecycle.ts @@ -1,6 +1,7 @@ import { useCallback } from "react"; import type { Editor } from "@tiptap/react"; import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; +import { isEditorReady } from "@docmost/editor-ext"; import { buildRowOrColumnSelection, Orientation } from "../lib/select-row-column"; interface Args { @@ -19,6 +20,7 @@ export function useColumnRowMenuLifecycle({ tablePos, }: Args) { const onOpen = useCallback(() => { + if (!isEditorReady(editor)) return; const selection = buildRowOrColumnSelection( editor.state, tableNode, @@ -33,6 +35,7 @@ export function useColumnRowMenuLifecycle({ }, [editor, orientation, index, tableNode, tablePos]); const onClose = useCallback(() => { + if (!isEditorReady(editor)) return; editor.commands.unfreezeHandles(); }, [editor]); diff --git a/apps/client/src/features/editor/components/table/handle/hooks/use-table-clear.ts b/apps/client/src/features/editor/components/table/handle/hooks/use-table-clear.ts index 1bd4cb209..308c7d98b 100644 --- a/apps/client/src/features/editor/components/table/handle/hooks/use-table-clear.ts +++ b/apps/client/src/features/editor/components/table/handle/hooks/use-table-clear.ts @@ -2,6 +2,7 @@ import { useCallback } from "react"; import type { Editor } from "@tiptap/react"; import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; import { TableMap } from "@tiptap/pm/tables"; +import { isEditorReady } from "@docmost/editor-ext"; type Scope = | { kind: "col"; index: number } @@ -15,6 +16,7 @@ export function useTableClear( scope: Scope, ) { return useCallback(() => { + if (!isEditorReady(editor)) return; const tr = editor.state.tr; const tableStart = tablePos + 1; const map = TableMap.get(tableNode); diff --git a/apps/client/src/features/editor/components/table/handle/hooks/use-table-move-row-column.ts b/apps/client/src/features/editor/components/table/handle/hooks/use-table-move-row-column.ts index 476c68f8d..be337cc85 100644 --- a/apps/client/src/features/editor/components/table/handle/hooks/use-table-move-row-column.ts +++ b/apps/client/src/features/editor/components/table/handle/hooks/use-table-move-row-column.ts @@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react"; import type { Editor } from "@tiptap/react"; import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; import { TableMap } from "@tiptap/pm/tables"; -import { moveColumn, moveRow } from "@docmost/editor-ext"; +import { isEditorReady, moveColumn, moveRow } from "@docmost/editor-ext"; export type MoveDirection = "left" | "right" | "up" | "down"; @@ -25,7 +25,7 @@ export function useTableMoveRowColumn( const canMove = target >= 0 && target <= maxIndex; const handleMove = useCallback(() => { - if (!canMove) return; + if (!canMove || !isEditorReady(editor)) return; const tr = editor.state.tr; const moved = orientation === "col" diff --git a/apps/client/src/features/editor/components/table/handle/hooks/use-table-sort.ts b/apps/client/src/features/editor/components/table/handle/hooks/use-table-sort.ts index afc6a2774..cc395a3ed 100644 --- a/apps/client/src/features/editor/components/table/handle/hooks/use-table-sort.ts +++ b/apps/client/src/features/editor/components/table/handle/hooks/use-table-sort.ts @@ -4,6 +4,7 @@ import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; import { convertArrayOfRowsToTableNode, convertTableNodeToArrayOfRows, + isEditorReady, transpose, } from "@docmost/editor-ext"; import { @@ -63,7 +64,7 @@ export function useTableSort({ }, [tableNode, orientation, index]); const handleSort = useCallback(() => { - if (!canSort) return; + if (!canSort || !isEditorReady(editor)) return; const rows = convertTableNodeToArrayOfRows(tableNode); const axes = orientation === "col" ? rows : transpose(rows); diff --git a/apps/client/src/features/editor/title-editor.tsx b/apps/client/src/features/editor/title-editor.tsx index fefe9f330..d828e1473 100644 --- a/apps/client/src/features/editor/title-editor.tsx +++ b/apps/client/src/features/editor/title-editor.tsx @@ -152,7 +152,11 @@ export function TitleEditor({ const debounceUpdate = useDebouncedCallback(saveTitle, 500); useEffect(() => { - if (titleEditor && title !== titleEditor.getText()) { + if ( + titleEditor && + !titleEditor.isDestroyed && + title !== titleEditor.getText() + ) { titleEditor.commands.setContent(title); } }, [pageId, title, titleEditor]); diff --git a/apps/client/src/features/page-history/components/history-editor.tsx b/apps/client/src/features/page-history/components/history-editor.tsx index d071abc3e..c7fa07036 100644 --- a/apps/client/src/features/page-history/components/history-editor.tsx +++ b/apps/client/src/features/page-history/components/history-editor.tsx @@ -34,7 +34,7 @@ export function HistoryEditor({ }); useEffect(() => { - if (!editor || !content) return; + if (!editor || editor.isDestroyed || !content) return; let decorationSet = DecorationSet.empty; let addedCount = 0;