From fcc9ea809ec9768674537ed63acd2848e3db9170 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:50:26 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`canary`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @supervoidcoder. * https://github.com/OmniBlocks/UltimateNotebook/pull/2#issuecomment-3670347222 The following files were modified: * `blocksuite/affine/blocks/note/src/configs/slash-menu.ts` * `blocksuite/affine/data-view/src/core/group-by/compare-date-keys.ts` * `blocksuite/affine/data-view/src/core/group-by/define.ts` * `blocksuite/affine/data-view/src/core/group-by/trait.ts` * `blocksuite/affine/shared/src/adapters/pdf/css-utils.ts` * `blocksuite/affine/shared/src/adapters/pdf/delta-converter.ts` * `blocksuite/affine/shared/src/adapters/pdf/image-utils.ts` * `blocksuite/affine/shared/src/adapters/pdf/svg-utils.ts` * `blocksuite/affine/shared/src/adapters/pdf/utils.ts` * `blocksuite/affine/shared/src/utils/number-prefix.ts` * `blocksuite/affine/widgets/linked-doc/src/transformers/pdf.ts` * `packages/backend/native/src/doc.rs` * `packages/backend/server/src/core/utils/blocksuite.ts` * `packages/common/native/src/doc_parser.rs` * `packages/frontend/core/src/bootstrap/cleanup.ts` * `packages/frontend/core/src/components/hooks/affine/use-export-page.ts` * `packages/frontend/media-capture-playground/web/components/saved-recording-item.tsx` * `tests/kit/src/utils/keyboard.ts` --- .../blocks/note/src/configs/slash-menu.ts | 9 +- .../src/core/group-by/compare-date-keys.ts | 28 ++- .../data-view/src/core/group-by/define.ts | 10 +- .../data-view/src/core/group-by/trait.ts | 8 +- .../shared/src/adapters/pdf/css-utils.ts | 9 +- .../src/adapters/pdf/delta-converter.ts | 10 +- .../shared/src/adapters/pdf/image-utils.ts | 22 ++- .../shared/src/adapters/pdf/svg-utils.ts | 17 +- .../affine/shared/src/adapters/pdf/utils.ts | 17 +- .../affine/shared/src/utils/number-prefix.ts | 15 +- .../linked-doc/src/transformers/pdf.ts | 7 +- packages/backend/native/src/doc.rs | 112 ++++++++++- .../server/src/core/utils/blocksuite.ts | 37 +++- packages/common/native/src/doc_parser.rs | 174 +++++++++++++++++- .../frontend/core/src/bootstrap/cleanup.ts | 14 +- .../hooks/affine/use-export-page.ts | 9 +- .../web/components/saved-recording-item.tsx | 13 +- tests/kit/src/utils/keyboard.ts | 11 +- 18 files changed, 487 insertions(+), 35 deletions(-) diff --git a/blocksuite/affine/blocks/note/src/configs/slash-menu.ts b/blocksuite/affine/blocks/note/src/configs/slash-menu.ts index 8efc0b2998cd0..86bff3b77494a 100644 --- a/blocksuite/affine/blocks/note/src/configs/slash-menu.ts +++ b/blocksuite/affine/blocks/note/src/configs/slash-menu.ts @@ -78,6 +78,13 @@ const noteSlashMenuConfig: SlashMenuConfig = { ], }; +/** + * Create a slash-menu action item that converts the current block to a specified text flavour and type. + * + * @param config - Configuration describing the conversion target (includes name, flavour, type, icon, description, and optional `searchAlias`). + * @param group - Optional menu group identifier to place the action under. + * @returns A SlashMenuActionItem that is shown only if the target flavour exists in the schema and, when invoked, updates the current block to the configured flavour and type. + */ function createConversionItem( config: TextConversionConfig, group?: SlashMenuItem['group'] @@ -153,4 +160,4 @@ function createTextFormatItem( export const NoteSlashMenuConfigExtension = SlashMenuConfigExtension( 'affine:note', noteSlashMenuConfig -); +); \ No newline at end of file diff --git a/blocksuite/affine/data-view/src/core/group-by/compare-date-keys.ts b/blocksuite/affine/data-view/src/core/group-by/compare-date-keys.ts index 073815d3a7910..8a167269ef5ab 100644 --- a/blocksuite/affine/data-view/src/core/group-by/compare-date-keys.ts +++ b/blocksuite/affine/data-view/src/core/group-by/compare-date-keys.ts @@ -10,7 +10,12 @@ export const RELATIVE_ASC = [ export const RELATIVE_DESC = [...RELATIVE_ASC].reverse(); /** - * Sorts relative date keys in chronological order + * Orders two relative date keys according to a predefined chronological sequence. + * + * @param a - First key to compare. + * @param b - Second key to compare. + * @param asc - When true, use ascending chronological order; when false, use descending order. + * @returns A negative number if `a` comes before `b`, a positive number if `a` comes after `b`, or `0` if their order is undefined. */ export function sortRelativeKeys(a: string, b: string, asc: boolean): number { const order: readonly string[] = asc ? RELATIVE_ASC : RELATIVE_DESC; @@ -25,7 +30,15 @@ export function sortRelativeKeys(a: string, b: string, asc: boolean): number { } /** - * Sorts numeric date keys (timestamps) + * Compare two strings that represent numeric date keys by their numeric value. + * + * If both inputs convert to finite numbers the result orders them ascending when `asc` is true, + * otherwise descending. If either input is not numeric, no ordering is applied. + * + * @param a - First key string, expected to contain a numeric timestamp + * @param b - Second key string, expected to contain a numeric timestamp + * @param asc - When true, sort in ascending numeric order; when false, sort in descending numeric order + * @returns A negative number if `a` should come before `b`, a positive number if `a` should come after `b`, `0` if they are equal or either value is not numeric */ export function sortNumericKeys(a: string, b: string, asc: boolean): number { const na = Number(a); @@ -38,6 +51,15 @@ export function sortNumericKeys(a: string, b: string, asc: boolean): number { return 0; // Not both numeric } +/** + * Create a comparator for date-like string keys that orders relative keys, numeric keys, and plain strings according to the chosen mode and direction. + * + * When `mode` is `'date-relative'`, relative keys are ordered by a predefined chronological list first, then numeric-like keys by their numeric value, and finally by lexicographic order for remaining ties. For other modes, numeric-like keys are compared first, then lexicographically. + * + * @param mode - If `'date-relative'` use relative-key-first ordering; any other value uses numeric-first ordering. + * @param asc - If `true` sort ascending; if `false` sort descending. + * @returns A comparator function that returns a negative number if `a` should come before `b`, `0` if they are equivalent, or a positive number if `a` should come after `b`. + */ export function compareDateKeys(mode: string | undefined, asc: boolean) { return (a: string, b: string) => { if (mode === 'date-relative') { @@ -59,4 +81,4 @@ export function compareDateKeys(mode: string | undefined, asc: boolean) { (asc ? a.localeCompare(b) : b.localeCompare(a)) ); }; -} +} \ No newline at end of file diff --git a/blocksuite/affine/data-view/src/core/group-by/define.ts b/blocksuite/affine/data-view/src/core/group-by/define.ts index 3baf463637282..b4ad47c9a0941 100644 --- a/blocksuite/affine/data-view/src/core/group-by/define.ts +++ b/blocksuite/affine/data-view/src/core/group-by/define.ts @@ -41,6 +41,14 @@ const WEEK_OPTS_SUN = { weekStartsOn: 0 } as const; const rangeLabel = (a: Date, b: Date) => `${fmt(a, 'MMM d yyyy')} – ${fmt(b, 'MMM d yyyy')}`; +/** + * Creates a date-focused GroupByConfig with common wiring for grouping and labeling dates. + * + * @param name - Identifier for the resulting group configuration + * @param grouper - Maps a date (milliseconds or null) to one or more group entries with `key` and numeric `value` (or `null` for ungrouped) + * @param groupName - Produces the display label for a group's numeric value (or `null` for ungrouped) + * @returns A GroupByConfig configured for date grouping and rendered with the date group view + */ function buildDateCfg( name: string, grouper: (ms: number | null) => { key: string; value: number | null }[], @@ -263,4 +271,4 @@ export const groupByMatchers: GroupByConfig[] = [ dateWeekMonCfg, dateMonthCfg, dateYearCfg, -]; +]; \ No newline at end of file diff --git a/blocksuite/affine/data-view/src/core/group-by/trait.ts b/blocksuite/affine/data-view/src/core/group-by/trait.ts index 14b8055bc1e66..9feb37b28341e 100644 --- a/blocksuite/affine/data-view/src/core/group-by/trait.ts +++ b/blocksuite/affine/data-view/src/core/group-by/trait.ts @@ -75,6 +75,12 @@ export class Group< } } +/** + * Type guard that detects objects having an optional `groupProperties` array. + * + * @param data - Value to test for a `groupProperties` property + * @returns `true` if `data` is an object with a `groupProperties` property that is either `undefined` or an array of `GroupProperty`; `false` otherwise. + */ function hasGroupProperties( data: unknown ): data is { groupProperties?: GroupProperty[] } { @@ -496,4 +502,4 @@ export const sortByManually = ( } result.push(...map.values()); return result; -}; +}; \ No newline at end of file diff --git a/blocksuite/affine/shared/src/adapters/pdf/css-utils.ts b/blocksuite/affine/shared/src/adapters/pdf/css-utils.ts index 324073b34a223..e33b00e99b323 100644 --- a/blocksuite/affine/shared/src/adapters/pdf/css-utils.ts +++ b/blocksuite/affine/shared/src/adapters/pdf/css-utils.ts @@ -1,5 +1,10 @@ /** - * Resolve CSS variable color (var(--affine-xxx)) using computed styles + * Resolve a CSS custom property reference of the form `var(--name)` to its computed value from the document root. + * + * Accepts a CSS color string or a `var(...)` expression. If the input is a plain value (not a `var(...)` expression), it is returned unchanged. + * + * @param color - A CSS color string or a `var(...)` reference to a CSS custom property. + * @returns `null` if the input is falsy, not a string, the environment lacks a `document`, the input is not a valid `var(...)` reference, the referenced name does not start with `--`, or the computed value is empty; otherwise the trimmed computed value of the CSS custom property. */ export function resolveCssVariable(color: string): string | null { if (!color || typeof color !== 'string') { @@ -22,4 +27,4 @@ export function resolveCssVariable(color: string): string | null { } const value = rootComputedStyle.getPropertyValue(variable).trim(); return value || null; -} +} \ No newline at end of file diff --git a/blocksuite/affine/shared/src/adapters/pdf/delta-converter.ts b/blocksuite/affine/shared/src/adapters/pdf/delta-converter.ts index 1a993e0baea26..87b00b5b86356 100644 --- a/blocksuite/affine/shared/src/adapters/pdf/delta-converter.ts +++ b/blocksuite/affine/shared/src/adapters/pdf/delta-converter.ts @@ -5,9 +5,11 @@ import { resolveCssVariable } from './css-utils.js'; /** - * Extract text from delta operations, preserving inline properties - * Returns normalized format: string if simple, array if complex (with inline styles) - */ + * Convert a Quill-style delta (props.text.delta) into a normalized text representation with inline styling. + * + * @param props - Input object expected to contain a Quill-like delta at `props.text.delta`. + * @param configs - Map of configuration values; supports `docLinkBaseUrl` for constructing reference links and `title:` entries to supply display titles for referenced pages. + * @returns A single space when no renderable content is present; a plain string when the result is a single unstyled segment; otherwise an array of strings and objects. Objects include a `text` property and optional inline styling fields such as `bold`, `italics`, `decoration`, `font`, `fontSize`, `color`, `background`, and `link`. export function extractTextWithInline( props: Record, configs: Map @@ -119,4 +121,4 @@ export function extractTextWithInline( return result[0] || ' '; } return result; -} +} \ No newline at end of file diff --git a/blocksuite/affine/shared/src/adapters/pdf/image-utils.ts b/blocksuite/affine/shared/src/adapters/pdf/image-utils.ts index 6e693a988f124..96fe5af418365 100644 --- a/blocksuite/affine/shared/src/adapters/pdf/image-utils.ts +++ b/blocksuite/affine/shared/src/adapters/pdf/image-utils.ts @@ -5,7 +5,13 @@ import { MAX_PAPER_HEIGHT, MAX_PAPER_WIDTH } from './utils.js'; /** - * Calculate image dimensions respecting props, original size, and paper constraints + * Compute target image width and height using provided block dimensions or fallbacks to original dimensions, then clamp to paper size limits preserving aspect ratio when necessary. + * + * @param blockWidth - Preferred width from layout; used if > 0 + * @param blockHeight - Preferred height from layout; used if > 0 + * @param originalWidth - Source image intrinsic width; used when `blockWidth` is not provided + * @param originalHeight - Source image intrinsic height; used when `blockHeight` is not provided + * @returns An object containing `width` and/or `height` (numbers) representing the computed dimensions; empty object if neither dimension can be determined */ export function calculateImageDimensions( blockWidth: number | undefined, @@ -54,7 +60,12 @@ export function calculateImageDimensions( } /** - * Extract dimensions from SVG + * Parse an SVG source string and extract numeric width and height. + * + * If `width` or `height` attributes are missing, attempt to derive them from the SVG `viewBox`'s width and height when available. + * + * @param svgText - The SVG document text to parse + * @returns An object containing numeric `width` and/or `height` when found; properties are `undefined` if not present or not derivable */ export function extractSvgDimensions(svgText: string): { width?: number; @@ -87,7 +98,10 @@ export function extractSvgDimensions(svgText: string): { } /** - * Extract dimensions from JPEG/PNG using Image API + * Extract the intrinsic width and height in pixels from an image Blob. + * + * @param blob - Image file blob (e.g., JPEG or PNG) + * @returns An object with `width` and `height` in pixels when available, or an empty object if dimensions could not be determined */ export async function extractImageDimensions( blob: Blob @@ -111,4 +125,4 @@ export async function extractImageDimensions( }; img.src = url; }); -} +} \ No newline at end of file diff --git a/blocksuite/affine/shared/src/adapters/pdf/svg-utils.ts b/blocksuite/affine/shared/src/adapters/pdf/svg-utils.ts index 7000ac6cb94d9..c881251cbb5e1 100644 --- a/blocksuite/affine/shared/src/adapters/pdf/svg-utils.ts +++ b/blocksuite/affine/shared/src/adapters/pdf/svg-utils.ts @@ -5,7 +5,10 @@ import { resolveCssVariable } from './css-utils.js'; /** - * Get SVG string for bulleted list icon based on depth + * Selects a bulleted list SVG icon variant based on list depth. + * + * @param depth - List nesting depth used to choose one of four bullet variants + * @returns The SVG markup for the selected bullet icon as a string */ export function getBulletIconSvg(depth: number): string { const bulletIndex = depth % 4; @@ -20,7 +23,10 @@ export function getBulletIconSvg(depth: number): string { } /** - * Get SVG string for checkbox icon (checked or unchecked) + * Return the SVG markup for a checkbox icon that reflects the checked state. + * + * @param checked - If `true`, returns the filled (checked) checkbox SVG; if `false`, returns the outlined (unchecked) checkbox SVG + * @returns The SVG markup string for the checkbox icon */ export function getCheckboxIconSvg(checked: boolean): string { if (checked) { @@ -31,7 +37,10 @@ export function getCheckboxIconSvg(checked: boolean): string { } /** - * Get SVG string for toggle icon (down or right) + * Provide an SVG chevron icon that reflects a toggle's expansion state. + * + * @param expanded - Whether the toggle is expanded; when true the icon is a downward-pointing chevron, otherwise a right-pointing chevron. + * @returns An SVG string representing a downward-pointing chevron when `expanded` is `true`, or a right-pointing chevron when `expanded` is `false`. */ export function getToggleIconSvg(expanded: boolean): string { if (expanded) { @@ -39,4 +48,4 @@ export function getToggleIconSvg(expanded: boolean): string { } else { return ''; } -} +} \ No newline at end of file diff --git a/blocksuite/affine/shared/src/adapters/pdf/utils.ts b/blocksuite/affine/shared/src/adapters/pdf/utils.ts index 22d3f831e037d..4360a125bf5e9 100644 --- a/blocksuite/affine/shared/src/adapters/pdf/utils.ts +++ b/blocksuite/affine/shared/src/adapters/pdf/utils.ts @@ -38,14 +38,20 @@ export const TABLE_LAYOUT_NO_BORDERS = { } as const; /** - * Generate placeholder text for images that cannot be rendered + * Create a fallback placeholder label for an image. + * + * @param caption - Optional caption to include in the placeholder + * @returns `"[Image: {caption}]"` if `caption` is provided, `"[Image]"` otherwise */ export function getImagePlaceholder(caption?: string): string { return caption ? `[Image: ${caption}]` : '[Image]'; } /** - * Check if text content has meaningful content + * Determine whether provided text content contains any non-empty text. + * + * @param textContent - A string or an array of strings or objects with a `text` property. When a string is provided, whitespace-only strings are treated as empty. When an array is provided, presence is determined by the array having at least one element. + * @returns `true` if the content contains text (a string with non-whitespace characters or an array with at least one item), `false` otherwise. */ export function hasTextContent( textContent: string | Array @@ -57,7 +63,10 @@ export function hasTextContent( } /** - * Convert text content array to plain string + * Convert mixed text content into a single plain string. + * + * @param textContent - A string or an array containing strings or objects with a `text` property; when an array is provided, each element's string or `text` value is used in order. + * @returns The concatenated plain string produced from the input. */ export function textContentToString( textContent: string | Array @@ -68,4 +77,4 @@ export function textContentToString( return textContent .map(item => (typeof item === 'string' ? item : item.text)) .join(''); -} +} \ No newline at end of file diff --git a/blocksuite/affine/shared/src/utils/number-prefix.ts b/blocksuite/affine/shared/src/utils/number-prefix.ts index bc716de09179d..67a13f51da0a8 100644 --- a/blocksuite/affine/shared/src/utils/number-prefix.ts +++ b/blocksuite/affine/shared/src/utils/number-prefix.ts @@ -1,3 +1,9 @@ +/** + * Converts a non-negative integer into a lowercase alphabetic sequence using `a`..`z`. + * + * @param n - The non-negative integer to convert (0 maps to `a`) + * @returns The lowercase alphabetic representation where `0` → `a`, `1` → `b`, and `26` → `aa` + */ function number2letter(n: number) { const ordA = 'a'.charCodeAt(0); const ordZ = 'z'.charCodeAt(0); @@ -10,7 +16,12 @@ function number2letter(n: number) { return s; } -// Derive from https://gist.github.com/imilu/00f32c61e50b7ca296f91e9d96d8e976 +/** + * Convert a positive integer to its Roman numeral representation. + * + * @param num - The integer to convert; expected to be positive (values ≤ 0 produce an empty string) + * @returns The Roman numeral representation of `num` using standard symbols (e.g., `M`, `CM`, `D`, `CD`, `C`, `XC`, `L`, `XL`, `X`, `IX`, `V`, `IV`, `I`) + */ function number2roman(num: number) { const lookup: Record = { M: 1000, @@ -50,4 +61,4 @@ function getPrefix(depth: number, index: number) { export function getNumberPrefix(index: number, depth: number) { const prefix = getPrefix(depth, index); return `${prefix}.`; -} +} \ No newline at end of file diff --git a/blocksuite/affine/widgets/linked-doc/src/transformers/pdf.ts b/blocksuite/affine/widgets/linked-doc/src/transformers/pdf.ts index 3ef41402fdac1..7c67acd49e130 100644 --- a/blocksuite/affine/widgets/linked-doc/src/transformers/pdf.ts +++ b/blocksuite/affine/widgets/linked-doc/src/transformers/pdf.ts @@ -8,6 +8,11 @@ import type { Store } from '@blocksuite/store'; import { download } from './utils.js'; +/** + * Export the given document to a PDF file and trigger a download of the generated file. + * + * @param doc - The document store to export; its workspace data and provider are used to build the transformer, produce a snapshot, and generate the PDF + */ async function exportDoc(doc: Store) { const provider = doc.provider; const job = doc.getTransformer([ @@ -29,4 +34,4 @@ async function exportDoc(doc: Store) { export const PdfTransformer = { exportDoc, -}; +}; \ No newline at end of file diff --git a/packages/backend/native/src/doc.rs b/packages/backend/native/src/doc.rs index c5da3c83b120b..1023e55455fd8 100644 --- a/packages/backend/native/src/doc.rs +++ b/packages/backend/native/src/doc.rs @@ -9,6 +9,16 @@ pub struct NativeMarkdownResult { } impl From for NativeMarkdownResult { + /// Creates a NativeMarkdownResult from a MarkdownResult. + /// + /// # Examples + /// + /// ``` + /// let mr = MarkdownResult { title: "t".into(), markdown: "md".into() }; + /// let n: NativeMarkdownResult = NativeMarkdownResult::from(mr); + /// assert_eq!(n.title, "t"); + /// assert_eq!(n.markdown, "md"); + /// ``` fn from(result: MarkdownResult) -> Self { Self { title: result.title, @@ -31,6 +41,28 @@ pub struct NativeBlockInfo { } impl From for NativeBlockInfo { + /// Constructs a `NativeBlockInfo` from a `BlockInfo`. + /// + /// # Examples + /// + /// ``` + /// use affine_common::doc_parser::BlockInfo; + /// use crate::NativeBlockInfo; + /// + /// let info = BlockInfo { + /// block_id: String::new(), + /// flavour: String::new(), + /// content: None, + /// blob: None, + /// ref_doc_id: None, + /// ref_info: None, + /// parent_flavour: None, + /// parent_block_id: None, + /// additional: None, + /// }; + /// + /// let native: NativeBlockInfo = NativeBlockInfo::from(info); + /// ``` fn from(info: BlockInfo) -> Self { Self { block_id: info.block_id, @@ -54,6 +86,19 @@ pub struct NativeCrawlResult { } impl From for NativeCrawlResult { + /// Converts a `CrawlResult` into a `NativeCrawlResult`. + /// + /// The conversion maps `blocks` into `NativeBlockInfo` items and copies `title` and `summary` + /// into the native representation exposed to JavaScript. + /// + /// # Examples + /// + /// ``` + /// // given a `crawl_result: CrawlResult` + /// let native: NativeCrawlResult = crawl_result.into(); + /// assert_eq!(native.title, crawl_result.title); + /// assert_eq!(native.summary, crawl_result.summary); + /// ``` fn from(result: CrawlResult) -> Self { Self { blocks: result.blocks.into_iter().map(Into::into).collect(), @@ -63,6 +108,26 @@ impl From for NativeCrawlResult { } } +/// Parse a binary document into a NativeCrawlResult suitable for JavaScript consumption. +/// +/// On failure, returns a `napi::Error` with `Status::GenericFailure` describing the parse error. +/// +/// # Returns +/// +/// `NativeCrawlResult` with parsed `blocks`, `title`, and `summary`. +/// +/// # Examples +/// +/// ``` +/// use napi::bindgen_prelude::Buffer; +/// +/// // `doc_bin` should contain the binary document bytes and `doc_id` its identifier. +/// let doc_bin = Buffer::from(vec![/* ...document bytes... */]); +/// let doc_id = "example-doc-id".to_string(); +/// +/// let result = parse_doc_from_binary(doc_bin, doc_id).expect("parsing should succeed"); +/// assert!(!result.title.is_empty()); +/// ``` #[napi] pub fn parse_doc_from_binary(doc_bin: Buffer, doc_id: String) -> Result { let result = doc_parser::parse_doc_from_binary(doc_bin.into(), doc_id) @@ -70,6 +135,31 @@ pub fn parse_doc_from_binary(doc_bin: Buffer, doc_id: String) -> Result` with the document IDs discovered in the root document. +/// +/// # Examples +/// +/// ``` +/// use napi::bindgen_prelude::Buffer; +/// +/// // `data` should contain the binary root document. +/// let data: Vec = vec![]; +/// let buf = Buffer::from(data); +/// let ids = read_all_doc_ids_from_root_doc(buf, None).unwrap(); +/// // All returned IDs are non-empty strings. +/// assert!(ids.iter().all(|s| !s.is_empty())); +/// ``` #[napi] pub fn read_all_doc_ids_from_root_doc( doc_bin: Buffer, @@ -90,4 +200,4 @@ pub fn read_all_doc_ids_from_root_doc( let result = doc_parser::get_doc_ids_from_binary(doc_bin.into(), include_trash.unwrap_or(false)) .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; Ok(result) -} +} \ No newline at end of file diff --git a/packages/backend/server/src/core/utils/blocksuite.ts b/packages/backend/server/src/core/utils/blocksuite.ts index fd12fedf336e5..2691fd2387841 100644 --- a/packages/backend/server/src/core/utils/blocksuite.ts +++ b/packages/backend/server/src/core/utils/blocksuite.ts @@ -47,6 +47,14 @@ export interface ParsePageOptions { maxSummaryLength: number; } +/** + * Extracts page title and textual summary from a Yjs document representing a page. + * + * @param doc - The Yjs document to parse for page blocks. + * @param opts - Options controlling summary extraction. + * @param opts.maxSummaryLength - Maximum number of characters to include in the summary; use `-1` to include full textual content. + * @returns The page's title and assembled summary, or `null` if the document is not a page or contains no root page block. + */ export function parsePageDoc( doc: YDoc, opts: ParsePageOptions = { maxSummaryLength: 150 } @@ -156,10 +164,22 @@ export function parsePageDoc( return content; } +/** + * Extracts all document IDs referenced by a workspace root snapshot. + * + * @param snapshot - Binary snapshot of the workspace root document + * @returns An array of document IDs found in the snapshot + */ export function readAllDocIdsFromWorkspaceSnapshot(snapshot: Uint8Array) { return readAllDocIdsFromRootDoc(Buffer.from(snapshot), false); } +/** + * Parses a JSON string and returns the resulting value if parsing succeeds. + * + * @param str - The JSON string to parse + * @returns The parsed value cast to `T`, or `undefined` if parsing fails + */ function safeParseJson(str: string): T | undefined { try { return JSON.parse(str) as T; @@ -168,6 +188,13 @@ function safeParseJson(str: string): T | undefined { } } +/** + * Parse a document snapshot and enrich its blocks with `docId`, `ref`, and parsed additional metadata. + * + * @param docId - Identifier of the document represented by the snapshot + * @param docSnapshot - Binary snapshot of the document + * @returns An object containing the parsed document data with a `blocks` array; each block preserves its original fields and additionally includes `docId`, `ref` (from `refInfo`), and `additional` parsed as JSON when present + */ export async function readAllBlocksFromDocSnapshot( docId: string, docSnapshot: Uint8Array @@ -187,6 +214,14 @@ export async function readAllBlocksFromDocSnapshot( }; } +/** + * Convert a serialized document snapshot into a plain title and Markdown content. + * + * @param docId - The document identifier used when parsing the snapshot. + * @param docSnapshot - Binary snapshot of the document (Yjs/Blocksuite format) as a Uint8Array. + * @param aiEditable - When true, include AI-editable transformations in the generated Markdown. + * @returns An object with `title` (document title) and `markdown` (document content as a Markdown string). + */ export function parseDocToMarkdownFromDocSnapshot( docId: string, docSnapshot: Uint8Array, @@ -202,4 +237,4 @@ export function parseDocToMarkdownFromDocSnapshot( title: parsed.title, markdown: parsed.markdown, }; -} +} \ No newline at end of file diff --git a/packages/common/native/src/doc_parser.rs b/packages/common/native/src/doc_parser.rs index 699fa76f982fd..37142e261a047 100644 --- a/packages/common/native/src/doc_parser.rs +++ b/packages/common/native/src/doc_parser.rs @@ -74,6 +74,17 @@ pub enum ParseError { } impl From for ParseError { + /// Convert a `JwstCodecError` into a `ParseError::ParserError`. + /// + /// The conversion preserves the original error message by storing its string representation. + /// + /// # Examples + /// + /// ``` + /// fn convert(e: JwstCodecError) -> ParseError { + /// e.into() + /// } + /// ``` fn from(value: JwstCodecError) -> Self { Self::ParserError(value.to_string()) } @@ -85,6 +96,27 @@ pub struct MarkdownResult { pub markdown: String, } +/// Converts a serialized document binary into a Markdown representation and a document title. +/// +/// Parses the provided document binary, traverses its block structure, and renders supported +/// block types (pages, paragraphs, lists, code blocks, tables, databases, and embeds) into +/// a single Markdown string. When `ai_editable` is true, top-level child blocks include an +/// HTML comment with their `block_id` and `flavour` to aid downstream editing. +/// +/// # Returns +/// +/// `Ok(MarkdownResult)` containing the document `title` and rendered `markdown` on success, +/// `Err(ParseError)` when the binary is invalid or the document cannot be parsed. +/// +/// # Examples +/// +/// ``` +/// use crate::{parse_doc_to_markdown, ParseError}; +/// +/// // invalid/empty binary yields an error +/// let res = parse_doc_to_markdown(vec![], "doc-id".into(), false); +/// assert!(matches!(res, Err(ParseError::InvalidBinary))); +/// ``` pub fn parse_doc_to_markdown( doc_bin: Vec, doc_id: String, @@ -297,6 +329,24 @@ pub fn parse_doc_to_markdown( }) } +/// Computes the nesting depth of a list item by walking its ancestor chain and counting +/// how many ancestor blocks have flavour `"affine:list"`. +/// +/// # Returns +/// +/// `usize` number of ancestor list blocks (the nesting depth). +/// +/// # Examples +/// +/// ``` +/// use std::collections::HashMap; +/// // `Map` is the block map value type expected by `get_list_depth`. +/// // Here we use empty maps to illustrate the call; an empty parent map yields depth 0. +/// let parent_lookup: HashMap = HashMap::new(); +/// let blocks: HashMap = HashMap::new(); +/// let depth = crate::get_list_depth("some-block", &parent_lookup, &blocks); +/// assert_eq!(depth, 0); +/// ``` fn get_list_depth( block_id: &str, parent_lookup: &HashMap, @@ -318,6 +368,32 @@ fn get_list_depth( depth } +/// Parses an Affine document binary into a structured crawl representation containing blocks, title, and a text summary. +/// +/// This function validates and decodes the provided document binary, traverses its block graph starting from the page root, and produces a CrawlResult that lists BlockInfo entries (with content, blobs, references and additional metadata), the document title, and a truncated textual summary. +/// +/// # Parameters +/// +/// - `doc_bin`: Binary payload of the Affine document (v1 format). +/// - `doc_id`: Document identifier used when constructing the internal Doc context. +/// +/// # Returns +/// +/// A `CrawlResult` containing the collected blocks, the document title (defaults to `"Untitled"` when absent), and a summary string. +/// +/// # Examples +/// +/// ```no_run +/// let bin: Vec = vec![/* Affine document binary */]; +/// let doc_id = "example-doc".to_string(); +/// match parse_doc_from_binary(bin, doc_id) { +/// Ok(result) => { +/// println!("Title: {}", result.title); +/// println!("Blocks: {}", result.blocks.len()); +/// } +/// Err(e) => eprintln!("Failed to parse document: {:?}", e), +/// } +/// ``` pub fn parse_doc_from_binary(doc_bin: Vec, doc_id: String) -> Result { if doc_bin.is_empty() || doc_bin == [0, 0] { return Err(ParseError::InvalidBinary); @@ -523,6 +599,27 @@ pub fn parse_doc_from_binary(doc_bin: Vec, doc_id: String) -> Result` containing the page IDs found in `meta.pages`. +/// +/// # Examples +/// +/// ```rust +/// let doc_bin = std::fs::read("fixtures/demo.ydoc").unwrap(); +/// let ids = get_doc_ids_from_binary(doc_bin, false).unwrap(); +/// assert!(ids.iter().all(|id| !id.is_empty())); +/// ``` pub fn get_doc_ids_from_binary( doc_bin: Vec, include_trash: bool, @@ -566,6 +663,24 @@ pub fn get_doc_ids_from_binary( Ok(doc_ids) } +/// Extracts the list of child block IDs from a block's `"sys:children"` entry. +/// +/// Returns a vector of child IDs found in the block's `"sys:children"` array, or an empty vector if the key is absent or not an array. +/// +/// # Examples +/// +/// ```no_run +/// use serde_json::json; +/// use y_octo::Map; +/// +/// // Construct a Map-like block with a "sys:children" array: +/// let mut block: Map = serde_json::from_value(json!({ +/// "sys:children": ["id1", "id2", "id3"] +/// })).unwrap(); +/// +/// let ids = collect_child_ids(&block); +/// assert_eq!(ids, vec!["id1".to_string(), "id2".to_string(), "id3".to_string()]); +/// ``` fn collect_child_ids(block: &Map) -> Vec { block .get("sys:children") @@ -722,6 +837,24 @@ fn gather_database_texts(block: &Map) -> (Vec, Option) { (texts, database_title) } +/// Collects non-empty cell text values from a block's keys named like `prop:cells..text`. +/// +/// Scans the provided map for keys that start with `prop:cells.` and end with `.text`, converts +/// their values to strings, and returns the non-empty results in the same order as the map's iteration. +/// +/// # Examples +/// +/// ``` +/// use serde_json::{Map, Value, json}; +/// let mut block = Map::new(); +/// block.insert("prop:cells.a.text".to_string(), json!("First")); +/// block.insert("prop:cells.b.text".to_string(), json!("")); +/// block.insert("prop:cells.c.text".to_string(), json!("Second")); +/// block.insert("prop:title".to_string(), json!("Ignored")); +/// +/// let contents = crate::gather_table_contents(&block); +/// assert_eq!(contents, vec!["First".to_string(), "Second".to_string()]); +/// ``` fn gather_table_contents(block: &Map) -> Vec { let mut contents = Vec::new(); for key in block.keys() { @@ -736,6 +869,31 @@ fn gather_table_contents(block: &Map) -> Vec { contents } +/// Formats a table cell value according to its column type and metadata. +/// +/// For `"select"` and `"multi-select"` column types, this resolves option IDs +/// to their display values using `col_data["options"]`. For other column +/// types it converts the value to a string when possible. +/// +/// # Returns +/// +/// A `String` containing the formatted cell value. For unmatched select IDs or +/// when expected structures are missing, returns an empty string; otherwise the +/// resolved display text or a fallback string representation. +/// +/// # Examples +/// +/// ``` +/// use y_octo::Any; +/// +/// // Default/value-to-string fallback +/// let v = Any::String("plain".into()); +/// assert_eq!(format_cell_value(&v, "text", None), "plain"); +/// +/// // Unknown type without metadata returns stringified value +/// let v2 = Any::Number(42.into()); +/// assert_eq!(format_cell_value(&v2, "unknown", None), "42"); +/// ``` fn format_cell_value(value: &Any, col_type: &str, col_data: Option<&Map>) -> String { match col_type { "select" => { @@ -786,6 +944,20 @@ fn format_cell_value(value: &Any, col_type: &str, col_data: Option<&Map>) -> Str } } +/// Convert a `Value` into a `String` when a textual representation is available. +/// +/// Attempts to extract text directly from the `Value` (`to_text`) and, if that +/// is not available, converts the underlying `Any` representation (`to_any`) +/// to a `String`. Returns `None` when the value cannot be represented as text. +/// +/// # Examples +/// +/// ``` +/// // Construct a text value (implementation may vary by `Value` constructors) +/// let v = Value::from("hello"); +/// let s = value_to_string(&v); +/// assert_eq!(s.as_deref(), Some("hello")); +/// ``` fn value_to_string(value: &Value) -> Option { if let Some(text) = value.to_text() { return Some(text.to_string()); @@ -838,4 +1010,4 @@ mod tests { config ); } -} +} \ No newline at end of file diff --git a/packages/frontend/core/src/bootstrap/cleanup.ts b/packages/frontend/core/src/bootstrap/cleanup.ts index c3c97a87f61ef..f2d2eae9ce0da 100644 --- a/packages/frontend/core/src/bootstrap/cleanup.ts +++ b/packages/frontend/core/src/bootstrap/cleanup.ts @@ -1,3 +1,15 @@ +/** + * Remove unused IndexedDB databases whose names match known legacy patterns. + * + * Deletes any database whose name: + * - ends with `:server-clock` + * - ends with `:sync-metadata` + * - starts with `idx:` and ends with `:block` or `:doc` + * - starts with `jp:` + * + * If IndexedDB is not available in the environment the function does nothing. + * If retrieving the list of databases fails, an error is logged to the console. + */ function cleanupUnusedIndexedDB() { const indexedDB = window.indexedDB; if (!indexedDB) { @@ -30,4 +42,4 @@ function cleanupUnusedIndexedDB() { }); } -cleanupUnusedIndexedDB(); +cleanupUnusedIndexedDB(); \ No newline at end of file diff --git a/packages/frontend/core/src/components/hooks/affine/use-export-page.ts b/packages/frontend/core/src/components/hooks/affine/use-export-page.ts index 4baaf3ad3d9f4..073f38967f0d3 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-export-page.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-export-page.ts @@ -141,6 +141,13 @@ async function exportToMarkdown(doc: Store, std?: BlockStdScope) { } } +/** + * Dispatches the requested export action for a document page based on the specified export type. + * + * @param page - The document page store to export. + * @param type - The export format to perform (`'html' | 'markdown' | 'snapshot' | 'pdf' | 'png' | 'pdf-export'`). + * @param editorContainer - The editor container providing editor-specific context required by some export handlers (for example, PDF printing). + */ async function exportHandler({ page, type, @@ -228,4 +235,4 @@ export const useExportPage = () => { ); return onClickHandler; -}; +}; \ No newline at end of file diff --git a/packages/frontend/media-capture-playground/web/components/saved-recording-item.tsx b/packages/frontend/media-capture-playground/web/components/saved-recording-item.tsx index 988f7308a1aec..cd619aab7dd75 100644 --- a/packages/frontend/media-capture-playground/web/components/saved-recording-item.tsx +++ b/packages/frontend/media-capture-playground/web/components/saved-recording-item.tsx @@ -570,7 +570,16 @@ function TranscribeButton({ ); } -// Main SavedRecordingItem component (simplified) +/** + * Render a saved recording card with playback, waveform, transcription, and delete controls. + * + * This component manages audio loading and processing (waveform generation), playback controls + * and state (current time, playback rate), transcription actions and status, deletion flow, + * and related UI sections (header, waveform visualizer, transcription panels). + * + * @param recording - The saved recording object (expected fields include `wav` filename, `metadata` with recording timestamps/duration, and optional `transcription` data). + * @returns A React element containing the full saved recording UI and its interactive behavior. + */ export function SavedRecordingItem({ recording, }: SavedRecordingItemProps): ReactElement { @@ -892,4 +901,4 @@ export function SavedRecordingItem({ /> ); -} +} \ No newline at end of file diff --git a/tests/kit/src/utils/keyboard.ts b/tests/kit/src/utils/keyboard.ts index 7ed9ff71ba26b..19e98c355e9bb 100644 --- a/tests/kit/src/utils/keyboard.ts +++ b/tests/kit/src/utils/keyboard.ts @@ -105,6 +105,15 @@ export async function undoByKeyboard(page: Page) { const clipboardMutex = new AsyncLock(); +/** + * Write the given text into the page's clipboard and optionally simulate pasting it into the focused element. + * + * The write is serialized with an internal mutex to avoid concurrent clipboard modifications. The function injects a synthetic `paste` event into the page with the provided text; if `paste` is `true` (default), it then triggers the platform-appropriate modifier plus `V` to perform a keyboard paste into the active element. + * + * @param page - Playwright page whose clipboard and input focus will be used + * @param text - Text to place into the clipboard and the synthetic paste event + * @param paste - Whether to simulate a keyboard paste after writing the clipboard (default: `true`) + */ export async function writeTextToClipboard( page: Page, text: string, @@ -133,4 +142,4 @@ export async function writeTextToClipboard( await page.keyboard.press('v', { delay: 50 }); await keyUpCtrlOrMeta(page); } -} +} \ No newline at end of file