Skip to content
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
2 changes: 1 addition & 1 deletion @codexteam/ui/dev/pages/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
placeholder="Write something or press / to select a tool"
first-block-placeholder="Untitled"
autofocus
:inlineToolbar="true"
:inline-toolbar="true"
/>
</template>

Expand Down
2 changes: 1 addition & 1 deletion @codexteam/ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codexteam/ui",
"version": "0.2.3",
"version": "0.2.5",
"type": "module",
"sideEffects": [
"*.css",
Expand Down
3 changes: 1 addition & 2 deletions @codexteam/ui/src/vue/components/editor/useEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ export function useEditor(editorConfig: MaybeRefOrGetter<EditorConfig>, options:
* Destroy editor instance after unmount
*/
onBeforeUnmount(() => {
editor?.destroy();
editor = undefined;
destroyEditor();
});

return {
Expand Down
44 changes: 34 additions & 10 deletions src/application/services/useNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ export const useNoteEditor = function useNoteEditor(options: UseNoteEditorOption
*/
const toolsUserConfigLoaded = ref<boolean>(false);

/**
* Incremented on each new load request to discard stale async results.
* Prevents race conditions when rapid note switching causes multiple
* concurrent loadToolsScripts invocations.
*/
let currentLoadId = 0;

/**
* Combine note and user tools
* Undefined when user or note is not loaded
Expand All @@ -102,10 +109,12 @@ export const useNoteEditor = function useNoteEditor(options: UseNoteEditorOption
});

/**
* Downloads passed tools scripts and toggles-on the isEditorReady flag
* Downloads passed tools scripts and returns the loaded config object.
* Does not mutate shared state — the caller is responsible for applying the result
* @param toolsConfigs - tools to download
* @returns loaded tools config
*/
async function loadToolsScripts(toolsConfigs: EditorTool[]): Promise<void> {
async function loadToolsScripts(toolsConfigs: EditorTool[]): Promise<Record<string, { class: EditorjsConfigTool; inlineToolbar: boolean }>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth extracting a shared type alias so loadToolsScripts and toolsUserConfig can't drift apart?

const loadedTools = await editorToolsService.getToolsLoaded(toolsConfigs);

/**
Expand All @@ -114,7 +123,7 @@ export const useNoteEditor = function useNoteEditor(options: UseNoteEditorOption
*/
const loadedToolsWithoutParagraph = loadedTools.filter(tool => tool.tool.name !== 'paragraph');

toolsUserConfig = Object.fromEntries(
return Object.fromEntries(
loadedToolsWithoutParagraph
.map(toolClassAndInfo => [
toolClassAndInfo.tool.name,
Expand All @@ -124,12 +133,6 @@ export const useNoteEditor = function useNoteEditor(options: UseNoteEditorOption
},
])
);
toolsUserConfigLoaded.value = true;

/**
* Now all tools are loaded, we're ready to use the editor
*/
isEditorReady.value = true;
}

/**
Expand All @@ -144,7 +147,28 @@ export const useNoteEditor = function useNoteEditor(options: UseNoteEditorOption
return;
}

await loadToolsScripts(tools);
const loadId = ++currentLoadId;

isEditorReady.value = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEditorReady now resets on every noteAndUserTools recompute, not just on note switch — e.g. installing a tool from the marketplace live-updates the shared userEditorTools ref while a note is open. Worth checking that case: does <Editor> visibly flash/remount instead of just picking up the new tool like before?

toolsUserConfigLoaded.value = false;

const loadedConfig = await loadToolsScripts(tools);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadToolsScripts can throw. Since isEditorReady is now reset to false before this await, a failed download leaves the note stuck with no editor rendered at all.

Might be worth wrapping this in a try/catch and surfacing an error on failure? Or maybe restoring some usable state?


/**
* If a newer load request has superseded this one — discard stale results
* to prevent overwriting state with tools from a previous note.
*/
if (loadId !== currentLoadId) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add test case so the race condition this is meant to fix doesn't regress silently?

return;
}

toolsUserConfig = loadedConfig;
toolsUserConfigLoaded.value = true;

/**
* Now all tools are loaded, we're ready to use the editor
*/
isEditorReady.value = true;
}, {
immediate: true, // load tools if they are passed to the composable immediately
});
Expand Down