Skip to content

Conversation

@trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Oct 29, 2025

https://www.loom.com/share/4e1d2489760b439f80734e7c43eccd9a

Summary by CodeRabbit

  • New Features
    • Canvas shapes can now be converted to Discourse nodes via right-click context menu
    • Text shapes convert directly to Discourse nodes with content validation
    • Image shapes can be converted and embedded into Discourse nodes with node type selection
    • Conversion actions display success and error feedback notifications to users

@linear
Copy link

linear bot commented Oct 29, 2025

@supabase
Copy link

supabase bot commented Oct 29, 2025

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@trangdoan982
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

📝 Walkthrough

Walkthrough

Introduces a context menu for Obsidian's canvas that enables converting canvas shapes to Discourse nodes. Adds a CustomContextMenu component wired into TldrawViewComponent, a convertToDiscourseNode utility orchestrating shape-to-node conversion for text and image types, and updates TypeScript configuration to include TSX files.

Changes

Cohort / File(s) Summary
CustomContextMenu Component
apps/obsidian/src/components/canvas/CustomContextMenu.tsx
New React component that renders a context menu for canvas shapes; conditionally displays a "Convert To" submenu for text or image shapes, populating items from plugin settings and invoking conversion on selection.
TldrawViewComponent Integration
apps/obsidian/src/components/canvas/TldrawViewComponent.tsx
Wires CustomContextMenu into TldrawPreviewComponent's components map under the ContextMenu path, passing current canvas file as a prop.
Shape Conversion Utility
apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts
New utility that orchestrates conversion of canvas shapes (text and image) to Discourse nodes; delegates to type-specific handlers, manages shape and node file creation, embeds images, and provides user feedback via toasts.
TypeScript Configuration
apps/obsidian/tsconfig.json
Extends include pattern to compile both **/*.ts and **/*.tsx files.

Sequence Diagram

sequenceDiagram
    participant User
    participant Canvas as Canvas Context Menu
    participant Converter as convertToDiscourseNode
    participant Editor
    participant FS as Disk/Settings

    User->>Canvas: Right-click shape
    Canvas->>Canvas: Check shape type
    alt Text or Image Shape
        Canvas->>Canvas: Show "Convert To" menu
        User->>Canvas: Select node type
        Canvas->>Converter: convertToDiscourseNode(shape, nodeType)
    else Other Shape
        Canvas->>User: No conversion option
    end

    rect rgb(200, 220, 255)
        note over Converter: Text Shape Flow
        Converter->>Converter: Render plaintext
        Converter->>FS: Create node file
        Converter->>Editor: Create discourse node shape
    end

    rect rgb(220, 200, 255)
        note over Converter: Image Shape Flow
        Converter->>Converter: Get image from shape
        Converter->>Canvas: Open node type modal
        Converter->>FS: Create node file
        Converter->>FS: Embed image in node
        Converter->>Editor: Create discourse node shape
    end

    rect rgb(255, 240, 200)
        note over Converter: Error Handling
        Converter->>Converter: Try/catch conversion
        Converter->>User: Show error toast on failure
    end

    Converter->>User: Show success toast
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • convertToDiscourseNode.ts may require attention for verifying orchestration logic across shape type handlers and ensuring error handling covers all edge cases
  • Confirm that shape position/size preservation and original shape cleanup work correctly during the conversion process

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "[ENG-871] Convert text and image into a discourse node (Obsidian)" directly and accurately reflects the main purpose of the changeset. The changes introduce a new context menu component (CustomContextMenu), integrate it into the TldrawViewComponent, and implement the core conversion logic in convertToDiscourseNode.ts—all working together to enable converting text and image canvas shapes into Discourse nodes within Obsidian. The title is clear, specific, and concise, using standard issue reference conventions without unnecessary noise or vague terminology.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/obsidian/src/components/canvas/CustomContextMenu.tsx (2)

33-35: Remove redundant optional chaining.

Since selectedShape is already checked for truthiness in the condition, the optional chaining on line 35 (selectedShape?.type) is unnecessary.

Apply this diff:

  const shouldShowConvertTo =
    selectedShape &&
-    (selectedShape?.type === "text" || selectedShape?.type === "image");
+    (selectedShape.type === "text" || selectedShape.type === "image");

43-59: Prefer template literal for string concatenation.

Line 47 uses string concatenation instead of a template literal. For consistency with modern JavaScript practices, consider using a template literal.

Apply this diff:

                <TldrawUiMenuItem
                  key={nodeType.id}
                  id={`convert-to-${nodeType.id}`}
-                  label={"Convert to " + nodeType.name}
+                  label={`Convert to ${nodeType.name}`}
                  icon="file-type"
                  onSelect={() => {
                    void convertToDiscourseNode({
                      editor,
                      shape: selectedShape,
                      nodeType,
                      plugin,
                      canvasFile,
                    });
                  }}
                />
apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts (1)

265-279: Clarify intentional spacing in template literal.

Line 277 contains a template literal with what appears to be extra whitespace: `${data}\n${imageEmbed} \n`. The space before the final \n may be intentional for formatting, but it looks unusual. Consider clarifying or removing it for consistency.

If the space is not intentional, apply this diff:

  await plugin.app.vault.process(nodeFile, (data: string) => {
-    return `${data}\n${imageEmbed} \n`;
+    return `${data}\n${imageEmbed}\n`;
  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d803704 and 4de4f2a.

📒 Files selected for processing (4)
  • apps/obsidian/src/components/canvas/CustomContextMenu.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/TldrawViewComponent.tsx (2 hunks)
  • apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts (1 hunks)
  • apps/obsidian/tsconfig.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/main.mdc)

**/*.{ts,tsx}: Prefer type over interface
Use explicit return types for functions
Avoid any types when possible

Files:

  • apps/obsidian/src/components/canvas/TldrawViewComponent.tsx
  • apps/obsidian/src/components/canvas/CustomContextMenu.tsx
  • apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/main.mdc)

**/*.{ts,tsx,js,jsx}: Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use Prettier with the project's configuration
Maintain consistent naming conventions: PascalCase for components and types
Maintain consistent naming conventions: camelCase for variables and functions
Maintain consistent naming conventions: UPPERCASE for constants

Files:

  • apps/obsidian/src/components/canvas/TldrawViewComponent.tsx
  • apps/obsidian/src/components/canvas/CustomContextMenu.tsx
  • apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts
apps/obsidian/**

📄 CodeRabbit inference engine (.cursor/rules/obsidian.mdc)

Use the Obsidian style guide from help.obsidian.md/style-guide and docs.obsidian.md/Developer+policies for all code in the Obsidian plugin

Files:

  • apps/obsidian/src/components/canvas/TldrawViewComponent.tsx
  • apps/obsidian/src/components/canvas/CustomContextMenu.tsx
  • apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts
  • apps/obsidian/tsconfig.json
🧠 Learnings (1)
📚 Learning: 2025-07-19T22:34:08.725Z
Learnt from: CR
PR: DiscourseGraphs/discourse-graph#0
File: .cursor/rules/main.mdc:0-0
Timestamp: 2025-07-19T22:34:08.725Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use Prettier with the project's configuration

Applied to files:

  • apps/obsidian/tsconfig.json
🔇 Additional comments (10)
apps/obsidian/tsconfig.json (1)

3-3: LGTM!

The addition of TSX file support is necessary for the new React components introduced in this PR.

apps/obsidian/src/components/canvas/TldrawViewComponent.tsx (2)

53-53: LGTM!

The import is correctly structured and uses the appropriate relative path.


341-344: Verify the necessity of the ESLint disable comment.

The eslint-disable comment for @typescript-eslint/naming-convention may be needed due to Tldraw's component override API expectations, but it's worth confirming whether this is truly necessary or if there's an alternative approach that doesn't require disabling the linting rule.

apps/obsidian/src/components/canvas/CustomContextMenu.tsx (2)

1-18: LGTM!

Imports are well-organized, and the type definition follows the project's coding guidelines (using type over interface and proper naming conventions).


20-31: LGTM!

Component follows the coding guidelines: uses arrow function, PascalCase naming, and properly leverages React hooks for reactive state management.

apps/obsidian/src/components/canvas/utils/convertToDiscourseNode.ts (5)

1-28: LGTM!

Imports are well-organized, and the type definition adheres to the coding guidelines (using type over interface and PascalCase naming).


30-57: LGTM!

The main conversion function follows coding guidelines with an explicit return type, uses an arrow function, and implements proper error handling with appropriate user feedback.


111-173: Verify return value behavior for image conversion.

The function returns shapeId immediately after opening the modal (line 172), which means it will always return undefined since the modal interaction is asynchronous and onNodeCreate hasn't executed yet. Verify whether this non-blocking behavior is intentional, as callers won't receive the actual shape ID of the created discourse node.

If the return value is expected to provide the created shape ID, consider using a Promise that resolves when the modal completes:

return new Promise<TLShapeId | undefined>((resolve) => {
  const modal = new CreateNodeModal(plugin.app, {
    nodeTypes: plugin.settings.nodeTypes,
    plugin,
    initialNodeType: nodeType,
    initialTitle: "",
    onNodeCreate: async (selectedNodeType: DiscourseNode, title: string) => {
      try {
        // ... existing logic ...
        resolve(shapeId);
      } catch (error) {
        console.error("Error creating node from image:", error);
        resolve(undefined);
        throw error;
      }
    },
  });
  modal.open();
});

175-222: LGTM!

The function follows coding guidelines with explicit types, proper parameter destructuring for multiple parameters, and includes appropriate error handling. The minimum dimension enforcement and history management are well-implemented.


224-264: LGTM!

The getImageFileFromShape helper includes proper error handling and returns null for all error cases, providing a safe fallback for the caller.

Copy link
Contributor

@mdroidian mdroidian left a comment

Choose a reason for hiding this comment

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

🎉

{
"extends": "@repo/typescript-config/react-library.json",
"include": ["**/*.ts"],
"include": ["**/*.ts", "**/*.tsx"],
Copy link
Contributor

Choose a reason for hiding this comment

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

👀

@trangdoan982 trangdoan982 merged commit cbf7e08 into main Nov 3, 2025
4 checks passed
@github-project-automation github-project-automation bot moved this to Done in General Nov 3, 2025
@trangdoan982 trangdoan982 deleted the eng-871-convert-text-image-into-a-discourse-node branch November 3, 2025 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants