Skip to content
Closed
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: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions apps/desktop/src/components/editor-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,21 @@ export default function EditorArea({
setAnnotationBox(null);
};

const handleImagePaste = useCallback(async (file: File) => {
// TODO: Handle pasted image file
// - Upload the file to storage
// - Get back image URL
// - Insert into editor at cursor position
console.log('Image pasted:', file.name, file.size);
}, []);

const handleImageCopy = useCallback(async (imageUrl: string) => {
// TODO: Handle copying image to clipboard
// - Fetch image as blob
// - Copy to clipboard
console.log('Image copy requested:', imageUrl);
}, []);

Comment on lines +256 to +270
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused callbacks to meet repo guidelines.

handleImagePaste and handleImageCopy are unused; violates “no unused functions.”

Apply this diff:

-  const handleImagePaste = useCallback(async (file: File) => {
-    // TODO: Handle pasted image file
-    // - Upload the file to storage
-    // - Get back image URL
-    // - Insert into editor at cursor position
-    console.log('Image pasted:', file.name, file.size);
-  }, []);
-
-  const handleImageCopy = useCallback(async (imageUrl: string) => {
-    // TODO: Handle copying image to clipboard
-    // - Fetch image as blob
-    // - Copy to clipboard
-    console.log('Image copy requested:', imageUrl);
-  }, []);
+  // Intentionally omitted: image paste/copy handlers will be wired where used.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleImagePaste = useCallback(async (file: File) => {
// TODO: Handle pasted image file
// - Upload the file to storage
// - Get back image URL
// - Insert into editor at cursor position
console.log('Image pasted:', file.name, file.size);
}, []);
const handleImageCopy = useCallback(async (imageUrl: string) => {
// TODO: Handle copying image to clipboard
// - Fetch image as blob
// - Copy to clipboard
console.log('Image copy requested:', imageUrl);
}, []);
// Intentionally omitted: image paste/copy handlers will be wired where used.
🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/index.tsx around lines 256 to 270,
the two unused callbacks handleImagePaste and handleImageCopy should be removed
to satisfy the “no unused functions” guideline; delete their entire useCallback
declarations (including TODO comments and console.log) and ensure there are no
remaining references to these identifiers elsewhere in the file, or replace any
future usages with a single exported/implemented utility if needed.

const isEnhancedNote = !showRaw && !!enhancedContent;

return (
Expand Down Expand Up @@ -290,6 +305,7 @@ export default function EditorArea({
trigger: "@",
handleSearch: handleMentionSearch,
}}
sessionId={sessionId}
/>
)
: <Renderer ref={editorRef} initialContent={noteContent} />}
Expand Down
2 changes: 2 additions & 0 deletions packages/tiptap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@floating-ui/dom": "^1.7.4",
"@hypr/plugin-db": "workspace:^",
"@hypr/plugin-misc": "workspace:^",
"@hypr/ui": "workspace:^",
Comment on lines +19 to 20
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

New deps OK; keep TipTap versions aligned.

Both additions make sense for image handling. Ensure the lockfile resolves all @tiptap/* to the same 3.4.x to avoid duplicate installs.

Run to inspect @tiptap versions across the workspace:

Also applies to: 28-29


🏁 Script executed:

#!/bin/bash
fd -a package.json -E node_modules -E dist -E build -E out \
| xargs -I{} sh -c 'jq -r "\"\(.name)//deps\" as \$p | (.dependencies//{} + .devDependencies//{} + .peerDependencies//{}) | to_entries[] | select(.key|startswith(\"@tiptap/\")) | "\$p:\(.key)@\(.value)\"" "{}"' \
| sort -u

Length of output: 1187


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find package.json files excluding common build dirs
mapfile -t files < <(find . -type f -name package.json -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*" -not -path "*/out/*")

if [ ${#files[@]} -eq 0 ]; then
  echo "NO_PACKAGE_JSON_FOUND"
  exit 0
fi

for f in "${files[@]}"; do
  name=$(jq -r '.name // empty' "$f" 2>/dev/null || true)
  if [ -z "$name" ]; then name="$f"; fi
  jq -r '(.dependencies // {} + .devDependencies // {} + .peerDependencies // {} + .optionalDependencies // {}) | to_entries[] | select(.key | startswith("@tiptap/")) | "\(.key)@\(.value)"' "$f" 2>/dev/null \
    | awk -v p="$f" -v n="$name" '{print p " // " n ":" $0}'
done | sort -u || true

Length of output: 56


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if ! command -v jq >/dev/null 2>&1; then
  echo "MISSING_JQ"
  exit 0
fi

echo "---- PACKAGE.JSON SCAN ----"
find . -type f -name package.json -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*" -not -path "*/out/*" -print0 \
  | while IFS= read -r -d '' f; do
      name=$(jq -r '.name // empty' "$f" 2>/dev/null || true)
      if [ -z "$name" ]; then name="$f"; fi
      jq -r '(.dependencies // {} + .devDependencies // {} + .peerDependencies // {} + .optionalDependencies // {}) 
             | to_entries[] 
             | select(.key | test("^@tiptap/")) 
             | "\(.key)@\(.value)"' "$f" 2>/dev/null \
        | awk -v f="$f" -v n="$name" '{print f " // " n ":" $0}'
    done | sort -u || true

echo "---- LOCKFILE SCAN ----"
find . -type f \( -name "pnpm-lock.yaml" -o -name "yarn.lock" -o -name "package-lock.json" -o -name "npm-shrinkwrap.json" \) -not -path "*/node_modules/*" -print0 \
  | while IFS= read -r -d '' lf; do
      echo "LOCKFILE: $lf"
      if grep -n --color=never -E "@tiptap/" "$lf" >/dev/null 2>&1; then
        grep -n -E "@tiptap/" "$lf" | sed -n '1,200p'
      else
        echo "NO_TIPTAP_FOUND_IN_LOCKFILE"
      fi
    done || true

Length of output: 13537


New deps OK; align @tiptap versions in lockfile.

@hypr/plugin-misc and @hypr/ui additions are fine for image handling. packages/tiptap/package.json pulls @tiptap/extension-file-handler@^3.4.2, but pnpm-lock.yaml shows most @tiptap/* resolved to 3.4.1 while extension-file-handler (and extension-text-style transitively) resolved to 3.4.2. Align all @tiptap/* to a single 3.4.x (either upgrade everything to 3.4.2 or pin file-handler/text-style to 3.4.1) and regenerate the lockfile to avoid duplicate installs.

"@hypr/utils": "workspace:^",
"@remixicon/react": "^4.6.0",
Expand All @@ -24,6 +25,7 @@
"@tiptap/core": "^3.4.1",
"@tiptap/extension-bubble-menu": "^3.4.1",
"@tiptap/extension-document": "^3.4.1",
"@tiptap/extension-file-handler": "^3.4.2",
"@tiptap/extension-highlight": "^3.4.1",
"@tiptap/extension-history": "^3.4.1",
"@tiptap/extension-image": "^3.4.1",
Expand Down
5 changes: 3 additions & 2 deletions packages/tiptap/src/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ interface EditorProps {
editable?: boolean;
setContentFromOutside?: boolean;
mentionConfig: MentionConfig;
sessionId?: string;
}

const Editor = forwardRef<{ editor: TiptapEditor | null }, EditorProps>(
({ handleChange, initialContent, editable = true, setContentFromOutside = false, mentionConfig }, ref) => {
({ handleChange, initialContent, editable = true, setContentFromOutside = false, mentionConfig, sessionId }, ref) => {
const previousContentRef = useRef<HTMLContent>(initialContent);

const onUpdate = ({ editor }: { editor: TiptapEditor }) => {
Expand All @@ -31,7 +32,7 @@ const Editor = forwardRef<{ editor: TiptapEditor | null }, EditorProps>(

const editor = useEditor({
extensions: [
...shared.extensions,
...(sessionId ? shared.createExtensions(sessionId) : shared.extensions),
mention(mentionConfig),
],
editable,
Expand Down
81 changes: 80 additions & 1 deletion packages/tiptap/src/shared/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ import TaskItem from "@tiptap/extension-task-item";
import TaskList from "@tiptap/extension-task-list";
import Underline from "@tiptap/extension-underline";
import StarterKit from "@tiptap/starter-kit";
import FileHandler from "@tiptap/extension-file-handler";

import { commands as miscCommands } from "@hypr/plugin-misc";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Gate FileHandler behind sessionId and avoid bundling Tauri in web/SSR; drop PII-y debug logs.

  • Prevents calling imageUpload with empty sessionId.
  • Prevents web/SSR breakage from statically importing @hypr/plugin-misc.
  • Removes logs that can leak full note contents.

Apply this diff:

-import { commands as miscCommands } from "@hypr/plugin-misc";
@@
   ClipboardTextSerializer,
-  FileHandler.configure({
+  ...(sessionId ? [
+  FileHandler.configure({
     allowedMimeTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
     onDrop: async (currentEditor, files, pos) => {
-      console.log("onDrop", files, pos)
       for (const file of files) {
         try {
           // Convert file to bytes
           const arrayBuffer = await file.arrayBuffer()
           const bytes = new Uint8Array(arrayBuffer)
           const extension = getFileExtension(file.name)
           
-          // Upload via Tauri command
-          const imageUrl = await miscCommands.imageUpload(sessionId, Array.from(bytes), extension)
-          console.log("full note html: ", currentEditor.getHTML())
+          const { commands: miscCommands } = await import("@hypr/plugin-misc")
+          const imageUrl = await miscCommands.imageUpload(sessionId, Array.from(bytes), extension)
           
           // Insert URL (not base64!)
           currentEditor
             .chain()
             .insertContentAt(pos, {
               type: 'image',
               attrs: {
                 src: imageUrl,
               },
             })
             .focus()
             .run()
         } catch (error) {
           console.error('Failed to upload image:', error)
         }
       }
     },
     onPaste: async (currentEditor, files, htmlContent) => {
       for (const file of files) {
-        console.log("onPaste", files, htmlContent)
         if (htmlContent) {
-          // if there is htmlContent, stop manual insertion & let other extensions handle insertion via inputRule
-          console.log(htmlContent) // eslint-disable-line no-console
           return false
         }
 
         try {
           // Convert file to bytes
           const arrayBuffer = await file.arrayBuffer()
           const bytes = new Uint8Array(arrayBuffer)
           const extension = getFileExtension(file.name)
           
-          // Upload via Tauri command
-          const imageUrl = await miscCommands.imageUpload(sessionId, Array.from(bytes), extension)
-          console.log("full note html: ", currentEditor.getHTML())
+          const { commands: miscCommands } = await import("@hypr/plugin-misc")
+          const imageUrl = await miscCommands.imageUpload(sessionId, Array.from(bytes), extension)
           
           // Insert URL (not base64!)
           currentEditor
             .chain()
             .insertContentAt(currentEditor.state.selection.anchor, {
               type: 'image',
               attrs: {
                 src: imageUrl,
               },
             })
             .focus()
             .run()
         } catch (error) {
           console.error('Failed to upload image:', error)
         }
       }
     },
-  }),
+  }),
+  ] : []),

Also applies to: 101-167

🤖 Prompt for AI Agents
In packages/tiptap/src/shared/extensions.ts around lines 11 and 101-167, avoid
statically importing @hypr/plugin-misc and remove PII debug logging: change the
top-level import to a dynamic/conditional import used only when a valid
sessionId/environment indicates native (Tauri) context (i.e., do not import on
web/SSR), guard any FileHandler/imageUpload calls so they only run when
sessionId is non-empty/defined, and strip or replace any logs that print full
note contents or other PII with minimal, non-sensitive messages; ensure error
handling still reports errors (without leaking content) and adjust types/returns
accordingly.


import { AIHighlight } from "./ai-highlight";
import { StreamingAnimation } from "./animation";
import { ClipboardTextSerializer } from "./clipboard";
import CustomListKeymap from "./custom-list-keymap";
import { Hashtag } from "./hashtag";

export const extensions = [
// Helper function to extract file extension
const getFileExtension = (filename: string): string => {
const ext = filename.split('.').pop()?.toLowerCase();
return ext || 'png';
};

export const createExtensions = (sessionId: string) => [
StarterKit.configure({
heading: {
levels: [1],
Expand Down Expand Up @@ -89,4 +98,74 @@ export const extensions = [
CustomListKeymap,
StreamingAnimation,
ClipboardTextSerializer,
FileHandler.configure({
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
onDrop: async (currentEditor, files, pos) => {
console.log("onDrop", files, pos)
for (const file of files) {
try {
// Convert file to bytes
const arrayBuffer = await file.arrayBuffer()
const bytes = new Uint8Array(arrayBuffer)
const extension = getFileExtension(file.name)

// Upload via Tauri command
const imageUrl = await miscCommands.imageUpload(sessionId, Array.from(bytes), extension)
console.log("full note html: ", currentEditor.getHTML())

// Insert URL (not base64!)
currentEditor
.chain()
.insertContentAt(pos, {
type: 'image',
attrs: {
src: imageUrl,
},
})
.focus()
.run()
} catch (error) {
console.error('Failed to upload image:', error)
}
}
},
onPaste: async (currentEditor, files, htmlContent) => {
for (const file of files) {
console.log("onPaste", files, htmlContent)
if (htmlContent) {
// if there is htmlContent, stop manual insertion & let other extensions handle insertion via inputRule
console.log(htmlContent) // eslint-disable-line no-console
return false
}

try {
// Convert file to bytes
const arrayBuffer = await file.arrayBuffer()
const bytes = new Uint8Array(arrayBuffer)
const extension = getFileExtension(file.name)

// Upload via Tauri command
const imageUrl = await miscCommands.imageUpload(sessionId, Array.from(bytes), extension)
console.log("full note html: ", currentEditor.getHTML())

// Insert URL (not base64!)
currentEditor
.chain()
.insertContentAt(currentEditor.state.selection.anchor, {
type: 'image',
attrs: {
src: imageUrl,
},
})
.focus()
.run()
} catch (error) {
console.error('Failed to upload image:', error)
}
}
},
}),
];

// For backward compatibility - default extensions without session ID
export const extensions = createExtensions('');
1 change: 1 addition & 0 deletions plugins/listener/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ specta-typescript = { workspace = true }
uuid = { workspace = true }

[dependencies]
hypr-aec = { workspace = true }
hypr-agc = { workspace = true }
hypr-audio = { workspace = true }
hypr-audio-utils = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions plugins/misc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ lazy_static = { workspace = true }
regex = { workspace = true }
specta = { workspace = true }
sysinfo = { workspace = true }
uuid = { version = "1.0", features = ["v4"] }
2 changes: 2 additions & 0 deletions plugins/misc/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const COMMANDS: &[&str] = &[
"audio_open",
"audio_exist",
"audio_delete",
"image_upload",
"image_delete",
];

fn main() {
Expand Down
6 changes: 6 additions & 0 deletions plugins/misc/js/bindings.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ async audioOpen(sessionId: string) : Promise<null> {
},
async audioDelete(sessionId: string) : Promise<null> {
return await TAURI_INVOKE("plugin:misc|audio_delete", { sessionId });
},
async imageUpload(sessionId: string, imageData: number[], extension: string) : Promise<string> {
return await TAURI_INVOKE("plugin:misc|image_upload", { sessionId, imageData, extension });
},
async imageDelete(sessionId: string, imageFilename: string) : Promise<null> {
return await TAURI_INVOKE("plugin:misc|image_delete", { sessionId, imageFilename });
}
}

Expand Down
13 changes: 13 additions & 0 deletions plugins/misc/permissions/autogenerated/commands/image_delete.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

[[permission]]
identifier = "allow-image-delete"
description = "Enables the image_delete command without any pre-configured scope."
commands.allow = ["image_delete"]

[[permission]]
identifier = "deny-image-delete"
description = "Denies the image_delete command without any pre-configured scope."
commands.deny = ["image_delete"]
13 changes: 13 additions & 0 deletions plugins/misc/permissions/autogenerated/commands/image_upload.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

[[permission]]
identifier = "allow-image-upload"
description = "Enables the image_upload command without any pre-configured scope."
commands.allow = ["image_upload"]

[[permission]]
identifier = "deny-image-upload"
description = "Denies the image_upload command without any pre-configured scope."
commands.deny = ["image_upload"]
54 changes: 54 additions & 0 deletions plugins/misc/permissions/autogenerated/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Default permissions for the plugin
- `allow-audio-open`
- `allow-audio-exist`
- `allow-audio-delete`
- `allow-image-upload`
- `allow-image-delete`

## Permission Table

Expand Down Expand Up @@ -181,6 +183,58 @@ Denies the get_git_hash command without any pre-configured scope.
<tr>
<td>

`misc:allow-image-delete`

</td>
<td>

Enables the image_delete command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`misc:deny-image-delete`

</td>
<td>

Denies the image_delete command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`misc:allow-image-upload`

</td>
<td>

Enables the image_upload command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`misc:deny-image-upload`

</td>
<td>

Denies the image_upload command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`misc:allow-opinionated-md-to-html`

</td>
Expand Down
2 changes: 2 additions & 0 deletions plugins/misc/permissions/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ permissions = [
"allow-audio-open",
"allow-audio-exist",
"allow-audio-delete",
"allow-image-upload",
"allow-image-delete",
]
28 changes: 26 additions & 2 deletions plugins/misc/permissions/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,30 @@
"const": "deny-get-git-hash",
"markdownDescription": "Denies the get_git_hash command without any pre-configured scope."
},
{
"description": "Enables the image_delete command without any pre-configured scope.",
"type": "string",
"const": "allow-image-delete",
"markdownDescription": "Enables the image_delete command without any pre-configured scope."
},
{
"description": "Denies the image_delete command without any pre-configured scope.",
"type": "string",
"const": "deny-image-delete",
"markdownDescription": "Denies the image_delete command without any pre-configured scope."
},
{
"description": "Enables the image_upload command without any pre-configured scope.",
"type": "string",
"const": "allow-image-upload",
"markdownDescription": "Enables the image_upload command without any pre-configured scope."
},
{
"description": "Denies the image_upload command without any pre-configured scope.",
"type": "string",
"const": "deny-image-upload",
"markdownDescription": "Denies the image_upload command without any pre-configured scope."
},
{
"description": "Enables the opinionated_md_to_html command without any pre-configured scope.",
"type": "string",
Expand All @@ -391,10 +415,10 @@
"markdownDescription": "Denies the parse_meeting_link command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-git-hash`\n- `allow-get-fingerprint`\n- `allow-opinionated-md-to-html`\n- `allow-delete-session-folder`\n- `allow-parse-meeting-link`\n- `allow-audio-open`\n- `allow-audio-exist`\n- `allow-audio-delete`",
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-git-hash`\n- `allow-get-fingerprint`\n- `allow-opinionated-md-to-html`\n- `allow-delete-session-folder`\n- `allow-parse-meeting-link`\n- `allow-audio-open`\n- `allow-audio-exist`\n- `allow-audio-delete`\n- `allow-image-upload`\n- `allow-image-delete`",
"type": "string",
"const": "default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-git-hash`\n- `allow-get-fingerprint`\n- `allow-opinionated-md-to-html`\n- `allow-delete-session-folder`\n- `allow-parse-meeting-link`\n- `allow-audio-open`\n- `allow-audio-exist`\n- `allow-audio-delete`"
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-git-hash`\n- `allow-get-fingerprint`\n- `allow-opinionated-md-to-html`\n- `allow-delete-session-folder`\n- `allow-parse-meeting-link`\n- `allow-audio-open`\n- `allow-audio-exist`\n- `allow-audio-delete`\n- `allow-image-upload`\n- `allow-image-delete`"
}
]
}
Expand Down
Loading
Loading