feat: import for google-keep#14454
Conversation
…in footer visible. Tags also not working
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds Google Keep Takeout import: a new transformer to unzip and parse Keep notes/attachments into AFFiNE docs, plus import dialog UI, types, styles, i18n keys, and progress/tag/favorite handling. (50 words) Changes
sequenceDiagram
participant User as User
participant ImportDialog as Import Dialog
participant Options as Options Panel
participant Transformer as GoogleKeep\nTransformer
participant Services as Services\n(Favorite/Tag/Explorer)
participant Doc as AFFiNE Doc
User->>ImportDialog: Open import dialog & choose "Google Keep"
ImportDialog->>Options: Show Google Keep options panel
User->>Options: Configure options & continue
User->>ImportDialog: Provide Keep zip file
ImportDialog->>Transformer: importGoogleKeepZip(zipBlob, options, onImportProgress)
Transformer->>Transformer: Unzip archive, index files, decode JSON notes
Transformer->>Transformer: Build safe HTML, probe/resolve attachments, layout images
Transformer->>Doc: Import HTML content via HtmlTransformer
Transformer->>Doc: Insert attachments, set tags, sync root title/metadata
Transformer->>Services: Create/reuse tags, mark favorites, link to folder
Transformer->>ImportDialog: Emit progress updates / errors
ImportDialog->>User: Display progress and final status
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment Tip CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.Add a configuration file to your project to customize how CodeRabbit runs |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 506-538: The Image object URL created in readImageSize can leak if
image decoding fails because URL.revokeObjecturl(?url=https%3A%2F%2Fgithub.com%2Ftoeverything%2FAFFiNE%2Fpull%2F14454%2FobjectUrl) is only called on
the success path; wrap the image loading block in a try/finally (or ensure a
finally-like cleanup) so that URL.revokeObjecturl(?url=https%3A%2F%2Fgithub.com%2Ftoeverything%2FAFFiNE%2Fpull%2F14454%2FobjectUrl) always runs
regardless of success or error, and keep the existing reject/resolve behavior
inside the try block while revoking in the finally for the objectUrl created in
readImageSize.
In `@packages/frontend/i18n/src/resources/de.json`:
- Around line 623-626: Replace the fragmented German value for the Google Keep
import settings title with the idiomatic compound noun: update the JSON value
for "com.affine.import.google-keep.options.title" from "Google Notizen Import
Einstellungen" to "Google Notizen Importeinstellungen" (or
"Google-Notizen-Importeinstellungen" if you prefer hyphenation) so the
translation reads naturally as a single compound word.
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts
Outdated
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
🧹 Nitpick comments (3)
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts (3)
802-803:toHtmlis declaredasyncbut never usesawait.The function body is entirely synchronous. Dropping
asyncand returningstringdirectly avoids an unnecessary microtask tick on every call.Suggested fix
-async function toHtml( +function toHtml( note: GoogleKeepNote, fallbackTitle: string -): Promise<string> { +): string {Then at the call site (line 928):
- const html = await toHtml(note, fallbackTitle); + const html = toHtml(note, fallbackTitle);
880-895: Zip entries are iterated twice — can be consolidated into a single pass.The first loop (lines 880-884) collects JSON candidates, and the second loop (lines 893-895) builds the full file map. Both iterate the same
unzipentries and can be merged.Suggested consolidation
- const candidates: Array<{ path: string; content: Blob }> = []; - - for (const entry of unzip) { - if (entry.path.toLowerCase().endsWith('.json')) { - candidates.push({ path: entry.path, content: entry.content }); - } - } - - const notesToImport: Array<{ - note: GoogleKeepNote; - fallbackTitle: string; - notePath: string; - }> = []; - const allFiles = new Map<string, Blob>(); - - for (const entry of unzip) { - allFiles.set(normalizePath(entry.path), entry.content); - } + const candidates: Array<{ path: string; content: Blob }> = []; + const allFiles = new Map<string, Blob>(); + + for (const entry of unzip) { + allFiles.set(normalizePath(entry.path), entry.content); + if (entry.path.toLowerCase().endsWith('.json')) { + candidates.push({ path: entry.path, content: entry.content }); + } + } + + const notesToImport: Array<{ + note: GoogleKeepNote; + fallbackTitle: string; + notePath: string; + }> = [];
927-968: Notes are processed sequentially — acceptable for import but consider error resilience.If a single note fails mid-import (e.g.,
HtmlTransformer.importHTMLToDocthrows), the entire remaining batch is skipped. Consider wrapping the per-note logic in a try/catch to continue importing the remaining notes and report failures.Suggested resilience improvement
for (const { note, fallbackTitle, notePath } of notesToImport) { + try { const html = await toHtml(note, fallbackTitle); // ... existing body ... docIds.push(docId); importedDocs += 1; onProgress?.({ totalDocs: notesToImport.length, importedDocs }); } + } catch (error) { + console.error(`Failed to import note "${fallbackTitle}":`, error); + importedDocs += 1; + onProgress?.({ totalDocs: notesToImport.length, importedDocs }); + } }
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts (1)
343-346: Deduplicate path candidates whenbaseDiris empty.When the note JSON resides at the archive root,
baseDiris''and both candidates are identical, causing a redundant secondMap.getfor the same key.♻️ Proposed fix
- const candidates = [ - normalizedFilePath, - baseDir ? `${baseDir}/${normalizedFilePath}` : normalizedFilePath, - ]; + const candidates = baseDir + ? [normalizedFilePath, `${baseDir}/${normalizedFilePath}`] + : [normalizedFilePath];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts` around lines 343 - 346, The candidates array currently contains duplicate entries when baseDir is an empty string, causing redundant lookups; update the logic that builds candidates (the const candidates = [...] block using normalizedFilePath and baseDir) to only include the second entry when baseDir is non-empty/truthy (or otherwise deduplicate entries e.g., filter unique values) so that Map.get is not called twice for the same key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 936-962: The created doc can be orphaned if
appendAttachmentBlocksToDoc rejects, so move the metadata and title sync steps
(collection.meta.setDocMeta and syncRootTitle) and favorite handling
(onFavoriteImported) to occur immediately after HtmlTransformer.importHTMLToDoc
creates the docId, before calling appendAttachmentBlocksToDoc; then call
appendAttachmentBlocksToDoc as best-effort inside its own try/catch so any error
is caught and logged/handled without throwing (ensure docIds.push(docId),
importedDocs increment, and onProgress are executed regardless of attachment
failures).
- Around line 299-303: The current looksLikeHtml heuristic in renderTextContent
allows plain-text strings containing angle-bracket patterns to bypass escaping
(via looksLikeHtml and renderTextContent), which leads to downstream HTML
parsing removing content; fix by removing the passthrough: always treat input as
plain text and return escaped HTML using escapeHtml (i.e., stop returning raw
text when looksLikeHtml matches), or if you prefer a stricter heuristic tighten
looksLikeHtml to detect unambiguous HTML signals such as <!DOCTYPE, <html, or
multiple well-formed tag pairs before skipping escape; update renderTextContent
and/or looksLikeHtml accordingly so escapeHtml is always applied to Google Keep
JSON textContent.
---
Nitpick comments:
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 343-346: The candidates array currently contains duplicate entries
when baseDir is an empty string, causing redundant lookups; update the logic
that builds candidates (the const candidates = [...] block using
normalizedFilePath and baseDir) to only include the second entry when baseDir is
non-empty/truthy (or otherwise deduplicate entries e.g., filter unique values)
so that Map.get is not called twice for the same key.
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts
Outdated
Show resolved
Hide resolved
…s and escape text content
…-fox/AFFiNE into canary-import-google-keep
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts (2)
542-571:resolveKeepAttachmentsisasyncwith noawaitexpressions.Every operation in the body is synchronous —
resolveAttachmentBlob,toFileName, and the MIME-type derivation are all pure. Removingasyncmakes the caller's dependency onPromiseresolution explicit and avoids misleading readers about its asynchrony.♻️ Proposed refactor
-async function resolveKeepAttachments( +function resolveKeepAttachments( note: GoogleKeepNote, notePath: string, files: Map<string, Blob> -): Promise<ResolvedKeepAttachment[]> { +): ResolvedKeepAttachment[] {The call site at line 911 (
await resolveKeepAttachments(...)) still compiles correctly once the return type changes; theawaitbecomes a no-op on a non-Promise value, so no other change is required.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts` around lines 542 - 571, The function resolveKeepAttachments is marked async but contains no awaits; remove the async keyword and change its return type from Promise<ResolvedKeepAttachment[]> to ResolvedKeepAttachment[] so it returns a plain array; keep the internal logic as-is (calls to resolveAttachmentBlob, toFileName, and mimeType derivation remain unchanged); you can leave callers that currently do "await resolveKeepAttachments(...)" unchanged since awaiting a non-Promise is a no-op.
491-532: Consider upstreaming thecreateImageBitmappath to the shared utility.A
readImageSizefunction already exists inblocksuite/affine/shared/src/utils/image.ts. The local implementation in google-keep.ts is genuinely superior (addscreateImageBitmapprimary path for better performance, avoids unnecessaryDOMPurify.sanitizeon blob URLs, uses proper try/finally for URL revocation, and returns a non-zero fallback size). This duplication means future improvements must be applied in both places.To consolidate, consider upstreaming the
createImageBitmappath and improved error handling into the shared utility, then import from there instead. The non-zero fallback value ({260, 180}) is specific to Keep attachment layouts and would need design consideration for the shared API.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts` around lines 491 - 532, The local readImageSize implementation duplicates and improves the shared utility; refactor by merging the createImageBitmap-first logic, try/finally URL.revokeObjectURL usage, and non-zero fallback behavior into the shared readImageSize utility so callers (including the google-keep transformer) import the single implementation; update the shared function to accept an optional fallback size or remove the KEEP_ATTACHMENTS_COLUMN_WIDTH-specific constant from the shared module and have the google-keep caller apply its { width: KEEP_ATTACHMENTS_COLUMN_WIDTH, height: 180 } fallback after importing the unified readImageSize, ensuring references to createImageBitmap, readImageSize, and KEEP_ATTACHMENTS_COLUMN_WIDTH are preserved and tested.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 14-50: The toMeta conversion currently maps Google Keep's isPinned
to meta.favorite but never maps isTrashed to DocMeta.trash, so trashed notes are
treated as active; update the toMeta function to check GoogleKeepNote.isTrashed
(or is_trashed variant if normalized) and set meta.trash = true when present
(and ensure meta.trash remains undefined/false otherwise), and adjust any
callers that build DocMeta from toMeta to preserve this field so trashed notes
are surfaced as trashed in AFFiNE.
---
Nitpick comments:
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 542-571: The function resolveKeepAttachments is marked async but
contains no awaits; remove the async keyword and change its return type from
Promise<ResolvedKeepAttachment[]> to ResolvedKeepAttachment[] so it returns a
plain array; keep the internal logic as-is (calls to resolveAttachmentBlob,
toFileName, and mimeType derivation remain unchanged); you can leave callers
that currently do "await resolveKeepAttachments(...)" unchanged since awaiting a
non-Promise is a no-op.
- Around line 491-532: The local readImageSize implementation duplicates and
improves the shared utility; refactor by merging the createImageBitmap-first
logic, try/finally URL.revokeObjectURL usage, and non-zero fallback behavior
into the shared readImageSize utility so callers (including the google-keep
transformer) import the single implementation; update the shared function to
accept an optional fallback size or remove the
KEEP_ATTACHMENTS_COLUMN_WIDTH-specific constant from the shared module and have
the google-keep caller apply its { width: KEEP_ATTACHMENTS_COLUMN_WIDTH, height:
180 } fallback after importing the unified readImageSize, ensuring references to
createImageBitmap, readImageSize, and KEEP_ATTACHMENTS_COLUMN_WIDTH are
preserved and tested.
…d size detection; fit imported images into max width/height while preserving aspect ratio; use shared readImageSize util instead of local implementation
man-of-fox
left a comment
There was a problem hiding this comment.
everythin commented by coderabbitai[bot] is integrated and tested with more than 350 Notes from Google Takedown Export
There was a problem hiding this comment.
🧹 Nitpick comments (1)
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts (1)
595-623: Isolate image-attachment failures so one bad file doesn’t drop the whole batch.
Promise.allrejects the entire image prep if any attachment fails. Consider per-attachment error handling (orPromise.allSettled) so successful images still import.♻️ Suggested refactor (best-effort per attachment)
- const preparedImages = await Promise.all( - imageAttachments.map(async attachment => { - const blobWithType = new File( - [attachment.blob], - attachment.fileName, - { - type: attachment.mimeType, - } - ); - const [sourceId, naturalSize] = await Promise.all([ - store.blobSync.set(blobWithType), - // Use the typed File for size detection; raw zip blobs can miss MIME type. - readImageSize(blobWithType, { - width: KEEP_ATTACHMENTS_COLUMN_WIDTH, - height: 180, - }), - ]); - - return { - sourceId, - naturalWidth: Math.max(1, naturalSize.width), - naturalHeight: Math.max(1, naturalSize.height), - }; - }) - ); + const preparedImages = ( + await Promise.all( + imageAttachments.map(async attachment => { + try { + const blobWithType = new File( + [attachment.blob], + attachment.fileName, + { + type: attachment.mimeType, + } + ); + const [sourceId, naturalSize] = await Promise.all([ + store.blobSync.set(blobWithType), + // Use the typed File for size detection; raw zip blobs can miss MIME type. + readImageSize(blobWithType, { + width: KEEP_ATTACHMENTS_COLUMN_WIDTH, + height: 180, + }), + ]); + + return { + sourceId, + naturalWidth: Math.max(1, naturalSize.width), + naturalHeight: Math.max(1, naturalSize.height), + }; + } catch (error) { + console.warn( + '[GoogleKeepTransformer] Failed to prepare image attachment:', + attachment.fileName, + error + ); + return null; + } + }) + ) + ).filter( + (image): image is { sourceId: string; naturalWidth: number; naturalHeight: number } => + Boolean(image) + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts` around lines 595 - 623, The image preparation uses Promise.all over imageAttachments so a single bad file rejects the whole batch; change to per-attachment error isolation by mapping attachments to individual promises that catch their own errors (or use Promise.allSettled on the mapped promises) and then filter out failures before using preparedImages. Locate the block around imageAttachments.map in google-keep.ts (functions/identifiers: ensureSurfaceModel, getAttachmentsAnchorFromNote, store.blobSync.set, readImageSize, KEEP_ATTACHMENTS_COLUMN_WIDTH, preparedImages) and update the logic to either wrap the inner async work for each attachment in try/catch and return null/error marker, or use Promise.allSettled and keep only settled results with status "fulfilled" before proceeding so one bad attachment does not drop the whole batch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 595-623: The image preparation uses Promise.all over
imageAttachments so a single bad file rejects the whole batch; change to
per-attachment error isolation by mapping attachments to individual promises
that catch their own errors (or use Promise.allSettled on the mapped promises)
and then filter out failures before using preparedImages. Locate the block
around imageAttachments.map in google-keep.ts (functions/identifiers:
ensureSurfaceModel, getAttachmentsAnchorFromNote, store.blobSync.set,
readImageSize, KEEP_ATTACHMENTS_COLUMN_WIDTH, preparedImages) and update the
logic to either wrap the inner async work for each attachment in try/catch and
return null/error marker, or use Promise.allSettled and keep only settled
results with status "fulfilled" before proceeding so one bad attachment does not
drop the whole batch.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## canary #14454 +/- ##
==========================================
- Coverage 59.11% 58.33% -0.78%
==========================================
Files 2909 2910 +1
Lines 160044 160342 +298
Branches 23361 23307 -54
==========================================
- Hits 94607 93539 -1068
- Misses 63278 64636 +1358
- Partials 2159 2167 +8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
need to fix |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts (1)
525-759: Make attachment import best‑effort per file.
A single failed image (Promise.all) or file attachment currently aborts all remaining attachments for that note. Consider isolating failures so the rest still import.♻️ Possible direction (images)
- const preparedImages = await Promise.all( - imageAttachments.map(async attachment => { + const preparedImages = ( + await Promise.allSettled( + imageAttachments.map(async attachment => { const blobWithType = new File( [attachment.blob], attachment.fileName, { type: attachment.mimeType, } ); const [sourceId, naturalSize] = await Promise.all([ store.blobSync.set(blobWithType), readImageSize(blobWithType, { width: KEEP_ATTACHMENTS_COLUMN_WIDTH, height: 180, }), ]); return { sourceId, naturalWidth: Math.max(1, naturalSize.width), naturalHeight: Math.max(1, naturalSize.height), }; - }) - ); + }) + ) + ) + .filter( + (result): result is PromiseFulfilledResult<{ + sourceId: string; + naturalWidth: number; + naturalHeight: number; + }> => result.status === 'fulfilled' + ) + .map(result => result.value);Also consider per‑file try/catch in the non‑image loop so one bad file doesn’t drop the rest.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts` around lines 525 - 759, The image and file attachment import currently uses Promise.all for preparedImages and has no error handling in the file loop, so one failure aborts all attachments; update appendAttachmentBlocksToDoc to handle failures per-file: replace the Promise.all(imageAttachments.map(...)) with either Promise.allSettled or individual try/catch per image (referencing imageAttachments, preparedImages, readImageSize, store.blobSync.set) and only keep fulfilled results, and wrap the non-image loop body (creation of File, await store.blobSync.set, and store.addBlock for 'affine:attachment') in a try/catch that logs/skips failed files so one bad file doesn't stop the rest.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@blocksuite/affine/shared/src/utils/image.ts`:
- Around line 9-15: The current early return in readImageSize() incorrectly
rejects Blobs with empty types; update the guard so it only short‑circuits when
file.type is non‑empty and does not start with 'image/' (i.e., allow empty
file.type to proceed to probing). Locate the readImageSize function and replace
the condition that checks file.type.startsWith('image/') with a check that first
verifies file.type is truthy and then that it does not start with 'image/', so
legitimate image data in untyped Blobs still gets size-detected.
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 867-875: The filter incorrectly excludes attachment-only Google
Keep notes by only checking title, textContent, and listContent; update the
check that currently inspects note (the GoogleKeepNote object) to also treat
note.attachments (or attachments.length > 0) as valid content so attachment-only
notes are not skipped—modify the conditional around note.title /
note.textContent / note.listContent to include a check for note.attachments (and
ensure it handles undefined vs empty arrays) in the same logical OR/AND
structure.
---
Nitpick comments:
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts`:
- Around line 525-759: The image and file attachment import currently uses
Promise.all for preparedImages and has no error handling in the file loop, so
one failure aborts all attachments; update appendAttachmentBlocksToDoc to handle
failures per-file: replace the Promise.all(imageAttachments.map(...)) with
either Promise.allSettled or individual try/catch per image (referencing
imageAttachments, preparedImages, readImageSize, store.blobSync.set) and only
keep fulfilled results, and wrap the non-image loop body (creation of File,
await store.blobSync.set, and store.addBlock for 'affine:attachment') in a
try/catch that logs/skips failed files so one bad file doesn't stop the rest.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
blocksuite/affine/shared/src/utils/image.tsblocksuite/affine/widgets/linked-doc/src/transformers/google-keep.tspackages/frontend/i18n/src/resources/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/frontend/i18n/src/resources/en.json
| export async function readImageSize( | ||
| file: File | Blob, | ||
| fallback: ImageSize = { width: 0, height: 0 } | ||
| ) { | ||
| if (!file.type.startsWith('image/')) { | ||
| return fallback; | ||
| } |
There was a problem hiding this comment.
Allow untyped image Blobs to be probed.
Blob.type can be empty (e.g., zip/fetch-created blobs). The current guard will return the fallback even if the data is an image, which can regress size detection. Consider only short‑circuiting when a non-empty type is explicitly non‑image.
🔧 Suggested tweak
- if (!file.type.startsWith('image/')) {
+ if (file.type && !file.type.startsWith('image/')) {
return fallback;
}📝 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.
| export async function readImageSize( | |
| file: File | Blob, | |
| fallback: ImageSize = { width: 0, height: 0 } | |
| ) { | |
| if (!file.type.startsWith('image/')) { | |
| return fallback; | |
| } | |
| export async function readImageSize( | |
| file: File | Blob, | |
| fallback: ImageSize = { width: 0, height: 0 } | |
| ) { | |
| if (file.type && !file.type.startsWith('image/')) { | |
| return fallback; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@blocksuite/affine/shared/src/utils/image.ts` around lines 9 - 15, The current
early return in readImageSize() incorrectly rejects Blobs with empty types;
update the guard so it only short‑circuits when file.type is non‑empty and does
not start with 'image/' (i.e., allow empty file.type to proceed to probing).
Locate the readImageSize function and replace the condition that checks
file.type.startsWith('image/') with a check that first verifies file.type is
truthy and then that it does not start with 'image/', so legitimate image data
in untyped Blobs still gets size-detected.
| // Keep exports include additional metadata JSON files. Ignore files that | ||
| // do not look like an individual note payload. | ||
| if ( | ||
| note.title === undefined && | ||
| note.textContent === undefined && | ||
| note.listContent === undefined | ||
| ) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd blocksuite && find . -name "google-keep.ts" -type fRepository: toeverything/AFFiNE
Length of output: 122
🏁 Script executed:
#!/bin/bash
# Scan for Keep-like JSON fixtures that have attachments but no title/text/list fields.
rg -l '"attachments"' -g '*.json' -g '!**/node_modules/**' -g '!**/dist/**' |
while read -r f; do
python - <<'PY' "$f"
import json,sys
path=sys.argv[1]
try:
with open(path,'r',encoding='utf-8') as fh:
data=json.load(fh)
except Exception:
sys.exit(0)
if isinstance(data, dict) and data.get("attachments") and \
"title" not in data and "textContent" not in data and "listContent" not in data:
print(path)
PY
doneRepository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
cat -n blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts | sed -n '860,885p'Repository: toeverything/AFFiNE
Length of output: 905
🏁 Script executed:
# Search for test fixtures or sample data related to google-keep
find blocksuite -type f \( -name "*.json" -o -name "*keep*" \) | grep -E "(test|fixture|sample)" | head -20Repository: toeverything/AFFiNE
Length of output: 243
🏁 Script executed:
rg -A 20 "interface GoogleKeepNote|type GoogleKeepNote" blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.tsRepository: toeverything/AFFiNE
Length of output: 571
🏁 Script executed:
rg "attachments" blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.tsRepository: toeverything/AFFiNE
Length of output: 713
🏁 Script executed:
# Search for any Google Keep documentation or schema references
rg -i "google.*keep|keep.*note" -A 5 blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts | head -40Repository: toeverything/AFFiNE
Length of output: 1107
Don't skip attachment-only notes.
The current filter skips notes without title, textContent, or listContent, but notes with only attachments will be dropped because the condition doesn't account for them. The attachments field is a valid part of the GoogleKeepNote type and should qualify as content.
🔧 Suggested fix
if (
note.title === undefined &&
note.textContent === undefined &&
note.listContent === undefined &&
+ !note.attachments?.length
) {
continue;
}📝 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.
| // Keep exports include additional metadata JSON files. Ignore files that | |
| // do not look like an individual note payload. | |
| if ( | |
| note.title === undefined && | |
| note.textContent === undefined && | |
| note.listContent === undefined | |
| ) { | |
| continue; | |
| } | |
| // Keep exports include additional metadata JSON files. Ignore files that | |
| // do not look like an individual note payload. | |
| if ( | |
| note.title === undefined && | |
| note.textContent === undefined && | |
| note.listContent === undefined && | |
| !note.attachments?.length | |
| ) { | |
| continue; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@blocksuite/affine/widgets/linked-doc/src/transformers/google-keep.ts` around
lines 867 - 875, The filter incorrectly excludes attachment-only Google Keep
notes by only checking title, textContent, and listContent; update the check
that currently inspects note (the GoogleKeepNote object) to also treat
note.attachments (or attachments.length > 0) as valid content so attachment-only
notes are not skipped—modify the conditional around note.title /
note.textContent / note.listContent to include a check for note.attachments (and
ensure it handles undefined vs empty arrays) in the same logical OR/AND
structure.




because I want to migrate my notes from Google Keep to affine, I wrote this missing importer.
Summary by CodeRabbit
New Features
Localization
Style
Improvements
Here some screenshots of the changes:








English version:
German version:
Importoptions:
Import status: