mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
refactor(mobile): organize markdown editor and preview owners (#42428)
This commit is contained in:
@@ -133,14 +133,14 @@
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/components/mobile-markdown-blocks.tsx",
|
||||
"filePath": "mobile/src/components/markdown/blocks/mobile-markdown-blocks.tsx",
|
||||
"kind": "jsx-expression",
|
||||
"text": "[x]",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/components/MobileRichMarkdownEditor.tsx",
|
||||
"filePath": "mobile/src/components/rich-markdown/MobileRichMarkdownEditor.tsx",
|
||||
"kind": "jsx-attribute:placeholder",
|
||||
"text": "https://",
|
||||
"dynamic": false,
|
||||
|
||||
@@ -300,14 +300,12 @@ for (const engine of ['chromium', 'webkit']) {
|
||||
if (engine === 'chromium') {
|
||||
expect(shown.links?.cursor).toBe('pointer')
|
||||
expect(hidden.links?.cursor).not.toBe('pointer')
|
||||
} else {
|
||||
expect(shown.links?.cursor).toBe(hidden.links?.cursor)
|
||||
}
|
||||
expect(shown.mountedSandbox).toBe('allow-top-navigation-by-user-activation')
|
||||
}, 240_000)
|
||||
|
||||
// Parser round-trip must preserve artifact rendering beyond links; real browser catches doctype/newline losses happy-dom misses. Granted arm is unchanged control.
|
||||
it('changes nothing an engine renders except that links are not links', async (ctx) => {
|
||||
// Check painted fixtures, granted document content and inert fragment targets.
|
||||
it('paints both artifact variants and keeps hidden fragment links inert', async (ctx) => {
|
||||
// A doctype with a public identifier and no system identifier, which is quirks. The bare
|
||||
// name is not, so a pass that rewrote one as the other moves the whole artifact between
|
||||
// layout modes -- which is what this arm exists to catch.
|
||||
@@ -318,21 +316,18 @@ for (const engine of ['chromium', 'webkit']) {
|
||||
doctype: quirks,
|
||||
grants: ['navigate', 'storage']
|
||||
})
|
||||
// Both painted, so every comparison below is between two rendered documents.
|
||||
// Both fixture variants painted.
|
||||
expect(shown.pixel).toBe(ARTIFACT_RGB)
|
||||
expect(hidden.pixel).toBe(ARTIFACT_RGB)
|
||||
|
||||
// Doctype public/system IDs discriminate preservation where inherited compatMode cannot.
|
||||
// Known granted doctype remains distinct from the bare-doctype control.
|
||||
expect(shown.inside?.doctypePublicId).toBe('-//W3C//DTD HTML 4.01 Transitional//EN')
|
||||
expect(hidden.inside?.doctypePublicId).toBe(shown.inside?.doctypePublicId)
|
||||
expect(hidden.inside?.doctypeSystemId).toBe(shown.inside?.doctypeSystemId)
|
||||
// Bare-doctype control proves ID reader can distinguish lost identifier.
|
||||
const bare = await open(browser(), { signal: ctx.signal, grants: ['navigate', 'storage'] })
|
||||
expect(bare.inside?.doctypePublicId).toBe('')
|
||||
|
||||
// Browser parser drops leading pre newline; inert-link serializer must restore it.
|
||||
// Granted fixture retains the leading pre newline.
|
||||
expect(shown.inside?.preText).toBe('\nkept')
|
||||
expect(hidden.inside?.preText).toBe(shown.inside?.preText)
|
||||
|
||||
// Granted fragment remains known broken navigation; hidden arm must strip it rather than preserve false scroll affordance.
|
||||
expect(shown.inside?.fragmentHref).toBe('#fragtarget')
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
MobileHtmlPreview,
|
||||
MOBILE_HTML_PREVIEW_SANDBOX,
|
||||
MOBILE_HTML_PREVIEW_SEALED_SANDBOX
|
||||
} from './MobileHtmlPreview'
|
||||
} from '../files/preview/documents/MobileHtmlPreview'
|
||||
|
||||
window.__sandbox = MOBILE_HTML_PREVIEW_SANDBOX
|
||||
window.__sealedSandbox = MOBILE_HTML_PREVIEW_SEALED_SANDBOX
|
||||
|
||||
@@ -52,7 +52,7 @@ const PAGE_ENTRY = `
|
||||
import { createElement, useEffect, useRef, useState } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import { MobileRichMarkdownEditor } from './MobileRichMarkdownEditor'
|
||||
import { MobileRichMarkdownEditor } from './rich-markdown/MobileRichMarkdownEditor'
|
||||
import { applyMobileColorScheme } from '../theme/mobile-theme'
|
||||
|
||||
/**
|
||||
|
||||
+21
-5
@@ -5,7 +5,8 @@ import { dirname, resolve } from 'node:path'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
const sourcePath = 'mobile/src/components/mobile-markdown-preview-html.ts'
|
||||
const currentSourcePath = 'mobile/src/components/markdown/parsing/mobile-markdown-preview-html.ts'
|
||||
const legacyBaselineSourcePath = 'mobile/src/components/mobile-markdown-preview-html.ts'
|
||||
const baselineRef = process.argv[2]
|
||||
if (!baselineRef) {
|
||||
throw new Error(
|
||||
@@ -14,7 +15,7 @@ if (!baselineRef) {
|
||||
}
|
||||
async function load(source) {
|
||||
const result = await build({
|
||||
stdin: { contents: source, resolveDir: dirname(resolve(sourcePath)), loader: 'ts' },
|
||||
stdin: { contents: source, resolveDir: dirname(resolve(currentSourcePath)), loader: 'ts' },
|
||||
bundle: true,
|
||||
write: false,
|
||||
platform: 'node',
|
||||
@@ -26,10 +27,25 @@ async function load(source) {
|
||||
)
|
||||
).normalizeMobileMarkdownPreviewHtml
|
||||
}
|
||||
const before = await load(
|
||||
execFileSync('git', ['show', `${baselineRef}:${sourcePath}`], { encoding: 'utf8' })
|
||||
const baselinePaths = execFileSync(
|
||||
'git',
|
||||
['ls-tree', '-r', '--name-only', baselineRef, '--', currentSourcePath, legacyBaselineSourcePath],
|
||||
{ encoding: 'utf8' }
|
||||
)
|
||||
const after = await load(readFileSync(sourcePath, 'utf8'))
|
||||
.trim()
|
||||
.split('\n')
|
||||
const baselineSourcePath = baselinePaths.includes(currentSourcePath)
|
||||
? currentSourcePath
|
||||
: baselinePaths.includes(legacyBaselineSourcePath)
|
||||
? legacyBaselineSourcePath
|
||||
: null
|
||||
if (baselineSourcePath === null) {
|
||||
throw new Error(`Baseline contains neither Markdown preview source path: ${baselineRef}`)
|
||||
}
|
||||
const before = await load(
|
||||
execFileSync('git', ['show', `${baselineRef}:${baselineSourcePath}`], { encoding: 'utf8' })
|
||||
)
|
||||
const after = await load(readFileSync(currentSourcePath, 'utf8'))
|
||||
function measure(fn, input, repeats) {
|
||||
const samples = []
|
||||
for (let run = 0; run < repeats; run++) {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ src/terminal/document/terminal-webview-engine.generated.ts
|
||||
src/terminal/document/terminal-webview-engine-css.generated.ts
|
||||
src/terminal/document/terminal-webview-document-script.generated.ts
|
||||
src/components/mermaid/mermaid-page-engine.generated.ts
|
||||
src/components/katex-page-style.generated.ts
|
||||
src/components/markdown/math/katex-page-style.generated.ts
|
||||
.expo/
|
||||
dist/
|
||||
/android/
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"extends": ["../.oxlintrc.json"],
|
||||
"ignorePatterns": [
|
||||
"src/components/mobile-markdown-mdast-nodes.d.ts",
|
||||
"src/components/markdown/mobile-markdown-mdast-nodes.d.ts",
|
||||
"src/terminal/document/terminal-webview-engine.generated.ts",
|
||||
"src/components/mermaid/mermaid-page-engine.generated.ts",
|
||||
"src/components/katex-page-style.generated.ts"
|
||||
"src/components/markdown/math/katex-page-style.generated.ts"
|
||||
],
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
|
||||
@@ -4,7 +4,14 @@ import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const mobileRoot = path.resolve(import.meta.dirname, '..')
|
||||
const outputPath = path.join(mobileRoot, 'src', 'components', 'katex-page-style.generated.ts')
|
||||
const outputPath = path.join(
|
||||
mobileRoot,
|
||||
'src',
|
||||
'components',
|
||||
'markdown',
|
||||
'math',
|
||||
'katex-page-style.generated.ts'
|
||||
)
|
||||
|
||||
// KaTeX's stylesheet for the page, woff2 only: every shell WebView reads it, and the bundle
|
||||
// emits each imported font as a same-origin asset, which the shell's font-src allows.
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
import { useLayoutEffect, useRef, useState } from 'react'
|
||||
import { Alert, Platform, Pressable, Text } from 'react-native'
|
||||
import { Check, Copy } from 'lucide-react-native'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { useClipboardWriter } from '../platform/clipboard/clipboard'
|
||||
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { translate } from '../../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
|
||||
import { useClipboardWriter } from '../../platform/clipboard/clipboard'
|
||||
import { colors, radii, spacing, typography } from '../../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
|
||||
|
||||
type CopyFeedback = { status: 'copied'; text: string } | { status: 'failed' }
|
||||
|
||||
+4
-4
@@ -2,10 +2,10 @@ import { Text, type TextStyle } from 'react-native'
|
||||
import type {
|
||||
MobileSyntaxSegment,
|
||||
MobileSyntaxTokenKind
|
||||
} from '../files/session-documents/mobile-file-syntax'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
} from '../../files/session-documents/mobile-file-syntax'
|
||||
import { colors } from '../../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
|
||||
|
||||
export function MobileSyntaxSegments({ segments }: { segments: MobileSyntaxSegment[] }) {
|
||||
useMobileColorScheme()
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { normalizeMobileMarkdownPreviewHtml } from './mobile-markdown-preview-html'
|
||||
import { normalizeMobileMarkdownPreviewHtml } from './parsing/mobile-markdown-preview-html'
|
||||
|
||||
describe('normalizeMobileMarkdownPreviewHtml', () => {
|
||||
it('normalizes common README HTML into readable Markdown preview text', () => {
|
||||
+8
-8
@@ -1,19 +1,19 @@
|
||||
import { INLINE_TEXT_SELECTION } from './inline-text-selection'
|
||||
import { INLINE_TEXT_SELECTION } from '../../platform/text-selection/inline-text-selection'
|
||||
import { memo, useContext, useMemo, type ReactNode } from 'react'
|
||||
import { Text as NativeText, View } from 'react-native'
|
||||
import { MARKDOWN_TONES, scaledMarkdownText, styles } from './mobile-markdown-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { renderBlocks, type MarkdownBlockContext } from './mobile-markdown-blocks'
|
||||
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
|
||||
import { renderBlocks, type MarkdownBlockContext } from './blocks/mobile-markdown-blocks'
|
||||
import { MarkdownText } from './mobile-markdown-text'
|
||||
import { MobileMarkdownRemoteImage } from './MobileMarkdownRemoteImage'
|
||||
import { MobileMarkdownRemoteImage } from './blocks/MobileMarkdownRemoteImage'
|
||||
import { MarkdownTextContext, type MarkdownTextSetup } from './mobile-markdown-text-setup'
|
||||
import { MarkdownLinkHandlerContext } from './markdown-link-handler'
|
||||
import { useMobileMarkdownTree } from './use-mobile-markdown-tree'
|
||||
import { MarkdownLinkHandlerContext } from './links/markdown-link-handler'
|
||||
import { useMobileMarkdownTree } from './parsing/use-mobile-markdown-tree'
|
||||
import {
|
||||
MobileMarkdownLinksContext,
|
||||
useNativeChatFileLinkExistsFrom
|
||||
} from './mobile-markdown-links-context'
|
||||
import type { NativeChatVisualDirective } from '../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
} from './links/mobile-markdown-links-context'
|
||||
import type { NativeChatVisualDirective } from '../../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
|
||||
type Props = {
|
||||
content?: string
|
||||
+5
-5
@@ -7,11 +7,11 @@ import {
|
||||
OctagonAlert,
|
||||
TriangleAlert
|
||||
} from 'lucide-react-native'
|
||||
import type { GitHubCalloutKind } from '../../../src/shared/github/markdown/github-callouts'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import { styles } from './mobile-markdown-styles'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import type { GitHubCalloutKind } from '../../../../../src/shared/github/markdown/github-callouts'
|
||||
import { translate } from '../../../i18n/mobile-i18n'
|
||||
import { styles } from '../mobile-markdown-styles'
|
||||
import { colors } from '../../../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme'
|
||||
|
||||
// Desktop's markdown-github-callout.tsx in the phone's tokens.
|
||||
const CALLOUTS: Record<GitHubCalloutKind, { color: () => string; label: () => string }> = {
|
||||
+9
-9
@@ -1,19 +1,19 @@
|
||||
import { TextWrap } from 'lucide-react-native'
|
||||
import { memo, useContext, useMemo } from 'react'
|
||||
import { Pressable, ScrollView, Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { highlightMobileCode } from '../files/session-documents/mobile-file-syntax'
|
||||
import { useMobileTranslation } from '../../../i18n/use-mobile-translation'
|
||||
import { highlightMobileCode } from '../../../files/session-documents/mobile-file-syntax'
|
||||
import {
|
||||
MobileNativeChatDisclosureContext,
|
||||
MobileNativeChatMarkdownRowKeyContext,
|
||||
useMobileNativeChatDisclosure
|
||||
} from '../session/native-chat/transcript/viewport/mobile-native-chat-disclosure-store'
|
||||
import { colors, pressedStyle } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { MobileSyntaxSegments } from './MobileSyntaxSegments'
|
||||
import { MobileSourceCopyButton } from './MobileSourceCopyButton'
|
||||
import { scaledMarkdownText, styles } from './mobile-markdown-styles'
|
||||
import { MarkdownText } from './mobile-markdown-text'
|
||||
} from '../../../session/native-chat/transcript/viewport/mobile-native-chat-disclosure-store'
|
||||
import { colors, pressedStyle } from '../../../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme'
|
||||
import { MobileSyntaxSegments } from '../../code-presentation/MobileSyntaxSegments'
|
||||
import { MobileSourceCopyButton } from '../../code-presentation/MobileSourceCopyButton'
|
||||
import { scaledMarkdownText, styles } from '../mobile-markdown-styles'
|
||||
import { MarkdownText } from '../mobile-markdown-text'
|
||||
|
||||
export const MobileMarkdownCodeBlock = memo(function MobileMarkdownCodeBlock({
|
||||
code,
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { Pressable, Text, View, type StyleProp, type TextStyle } from 'react-native'
|
||||
import { ChevronRight } from 'lucide-react-native'
|
||||
import { DisclosureChevron } from './DisclosureChevron'
|
||||
import { styles } from './mobile-markdown-styles'
|
||||
import { colors, pressedStyle } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { DisclosureChevron } from '../../DisclosureChevron'
|
||||
import { styles } from '../mobile-markdown-styles'
|
||||
import { colors, pressedStyle } from '../../../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme'
|
||||
|
||||
/** A `<details>` section: closed unless authored `open`, like desktop and GitHub. */
|
||||
export function MobileMarkdownDetails({
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
import { useContext, useEffect, useState } from 'react'
|
||||
import { Image, Pressable, StyleSheet, Text } from 'react-native'
|
||||
import { MarkdownLinkHandlerContext } from './markdown-link-handler'
|
||||
import { styles as markdownStyles } from './mobile-markdown-styles'
|
||||
import { pressedStyle } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { MarkdownLinkHandlerContext } from '../links/markdown-link-handler'
|
||||
import { styles as markdownStyles } from '../mobile-markdown-styles'
|
||||
import { pressedStyle } from '../../../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme'
|
||||
|
||||
type Size = { width: number; height: number }
|
||||
|
||||
+10
-10
@@ -15,28 +15,28 @@ import {
|
||||
type StyleProp,
|
||||
type TextStyle
|
||||
} from 'react-native'
|
||||
import type { NativeChatVisualDirective } from '../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
import type { NativeChatVisualDirective } from '../../../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
import { isMobileMermaidLanguage } from './mobile-mermaid-language'
|
||||
import { mobileMathMeasureText } from './mobile-markdown-math-nodes'
|
||||
import { mobileMathMeasureText } from '../math/mobile-markdown-math-nodes'
|
||||
import { mobileMarkdownTableColumnWidths } from './mobile-markdown-table-columns'
|
||||
import { mobileMarkdownVisualPlaceholderIndex } from './mobile-markdown-visual-lines'
|
||||
import {
|
||||
openMarkdownHref,
|
||||
renderInline,
|
||||
type MarkdownInlineContext
|
||||
} from './mobile-markdown-inline'
|
||||
import { MARKDOWN_TONES, scaledMarkdownText, styles } from './mobile-markdown-styles'
|
||||
import { MarkdownText } from './mobile-markdown-text'
|
||||
} from '../parsing/mobile-markdown-inline'
|
||||
import { MARKDOWN_TONES, scaledMarkdownText, styles } from '../mobile-markdown-styles'
|
||||
import { MarkdownText } from '../mobile-markdown-text'
|
||||
import { codeFenceClosed } from './mobile-markdown-code-nodes'
|
||||
import { plainText } from './mobile-markdown-plain-text'
|
||||
import { MermaidDiagram } from './mermaid/MermaidDiagram'
|
||||
import { plainText } from '../parsing/mobile-markdown-plain-text'
|
||||
import { MermaidDiagram } from '../../mermaid/MermaidDiagram'
|
||||
import { MobileMarkdownCallout } from './MobileMarkdownCallout'
|
||||
import { MobileMarkdownCodeBlock } from './MobileMarkdownCodeBlock'
|
||||
import { MobileMarkdownDetails } from './MobileMarkdownDetails'
|
||||
import { MobileMarkdownMath } from './MobileMarkdownMath'
|
||||
import { MobileMarkdownMath } from '../math/MobileMarkdownMath'
|
||||
import { drawnBlock } from './mobile-markdown-drawn-blocks'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import { colors, pressedStyle } from '../theme/mobile-theme'
|
||||
import { translate } from '../../../i18n/mobile-i18n'
|
||||
import { colors, pressedStyle } from '../../../theme/mobile-theme'
|
||||
|
||||
/** Prose base size — passed to MermaidDiagram fallback mono text. */
|
||||
const MERMAID_BASE = 13
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import type { RootContent } from 'mdast'
|
||||
import type { ReactNode } from 'react'
|
||||
import { getMobileUiRevision } from '../i18n/mobile-i18n'
|
||||
import { getMobileColorScheme } from '../theme/mobile-theme'
|
||||
import { getMobileUiRevision } from '../../../i18n/mobile-i18n'
|
||||
import { getMobileColorScheme } from '../../../theme/mobile-theme'
|
||||
import type { MarkdownBlockContext, ParagraphText } from './mobile-markdown-blocks'
|
||||
|
||||
type DrawnBlock = {
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import type { Image, PhrasingContent } from 'mdast'
|
||||
import { Fragment, type ReactNode } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { MarkdownText } from './mobile-markdown-text'
|
||||
import { renderInline } from './mobile-markdown-inline'
|
||||
import { styles } from './mobile-markdown-styles'
|
||||
import { MarkdownText } from '../mobile-markdown-text'
|
||||
import { renderInline } from '../parsing/mobile-markdown-inline'
|
||||
import { styles } from '../mobile-markdown-styles'
|
||||
import type { MarkdownBlockContext, ParagraphText } from './mobile-markdown-blocks'
|
||||
|
||||
function isBlankInline(node: PhrasingContent): boolean {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { spacing } from '../theme/mobile-theme'
|
||||
import { spacing } from '../../../theme/mobile-theme'
|
||||
|
||||
// Measured 12px advances: 6.4 regular, 6.9 bold, up to 8.7 per bold glyph. Estimation affects wrapping, never column alignment.
|
||||
const AVERAGE_GLYPH_WIDTH = 7
|
||||
+2
-2
@@ -3,8 +3,8 @@ import {
|
||||
NATIVE_CHAT_VISUAL_MAX_PER_MESSAGE,
|
||||
parseNativeChatVisualDirectiveLine,
|
||||
type NativeChatVisualDirective
|
||||
} from '../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
import { mobileMarkdownCodeLines } from './mobile-markdown-tree'
|
||||
} from '../../../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
import { mobileMarkdownCodeLines } from '../parsing/mobile-markdown-tree'
|
||||
// Private-use delimiters, as the preview normalizer's own code placeholders use.
|
||||
const PLACEHOLDER_PREFIX = '\uE000ORCA_VISUAL_'
|
||||
const PLACEHOLDER = /^\uE000ORCA_VISUAL_(\d+)\uE000$/
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Prefer precise chat file paths over recall: missed link is tolerable, false prose/version tap is broken navigation.
|
||||
|
||||
import { parseFileLinkLocation } from '../../../src/shared/file-links/file-link-location'
|
||||
import { parseFileLinkLocation } from '../../../../../src/shared/file-links/file-link-location'
|
||||
|
||||
export type FilePathSegment =
|
||||
| { type: 'text'; value: string }
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { routeNativeChatHref } from '../../../src/shared/native-chat/native-chat-href-routing'
|
||||
import { routeNativeChatHref } from '../../../../../src/shared/native-chat/native-chat-href-routing'
|
||||
|
||||
export type MarkdownHrefRoute =
|
||||
| { kind: 'web'; url: string }
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { createContext } from 'react'
|
||||
import { openExternalLink } from '../platform/external-links/external-link'
|
||||
import { openExternalLink } from '../../../platform/external-links/external-link'
|
||||
|
||||
// Screens that offer choices for a tapped web link provide this; elsewhere the system browser opens it.
|
||||
export const MarkdownLinkHandlerContext = createContext<(url: string) => void>(openExternalLink)
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { createContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
|
||||
import { createNativeChatFileLinkHooks } from '../../../src/shared/native-chat/native-chat-file-link-hooks'
|
||||
import type { NativeChatFileLinkExistence } from '../../../src/shared/native-chat/native-chat-file-link-existence'
|
||||
import type { GitHubRepoReference } from '../../../src/shared/github/markdown/github-reference-links'
|
||||
import { createNativeChatFileLinkHooks } from '../../../../../src/shared/native-chat/native-chat-file-link-hooks'
|
||||
import type { NativeChatFileLinkExistence } from '../../../../../src/shared/native-chat/native-chat-file-link-existence'
|
||||
import type { GitHubRepoReference } from '../../../../../src/shared/github/markdown/github-reference-links'
|
||||
|
||||
/** What a chat knows about its workspace for links in its text; absent outside chat. */
|
||||
export type MobileMarkdownLinks = {
|
||||
+5
-2
@@ -1,6 +1,9 @@
|
||||
import type { Code, Parent, PhrasingContent, RootContent } from 'mdast'
|
||||
import { isDisplayMathOpener, markdownMathCloser } from '../../../src/shared/markdown-math-rules'
|
||||
import type { Token } from './mobile-markdown-code-nodes'
|
||||
import {
|
||||
isDisplayMathOpener,
|
||||
markdownMathCloser
|
||||
} from '../../../../../src/shared/markdown-math-rules'
|
||||
import type { Token } from '../blocks/mobile-markdown-code-nodes'
|
||||
|
||||
// The mdast nodes mobile-markdown-math's tokens become, shaped like desktop chat's math nodes.
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { BoundedMap } from '../../../src/shared/bounded-map'
|
||||
import { MARKDOWN_MATH_KATEX_SETTINGS } from '../../../src/shared/markdown-math-rules'
|
||||
import { copyMarkdownMathSelection } from '../../../src/shared/markdown-math-selection'
|
||||
import { BoundedMap } from '../../../../../src/shared/bounded-map'
|
||||
import { MARKDOWN_MATH_KATEX_SETTINGS } from '../../../../../src/shared/markdown-math-rules'
|
||||
import { copyMarkdownMathSelection } from '../../../../../src/shared/markdown-math-selection'
|
||||
import { KATEX_PAGE_STYLE } from './katex-page-style.generated'
|
||||
import { GITHUB_MATH_SIZE_MACROS } from './mobile-markdown-math-nodes'
|
||||
import type { MobileMathTypesetter } from './mobile-markdown-math-typesetter'
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { Nodes } from 'mdast'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseMobileMarkdownTree } from './mobile-markdown-tree'
|
||||
import { parseMobileMarkdownTree } from '../parsing/mobile-markdown-tree'
|
||||
|
||||
/** Formulas as `D:` (display) or `I:` (inline) plus the text drawn around them. */
|
||||
function drawn(source: string, chatMath = true): { math: string[]; text: string } {
|
||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
||||
endsDollarAmount,
|
||||
rejectsSingleDollarClose,
|
||||
rejectsSingleDollarOpen
|
||||
} from '../../../src/shared/markdown-math-rules'
|
||||
} from '../../../../../src/shared/markdown-math-rules'
|
||||
|
||||
// Desktop chat's math grammar (src/renderer/src/components/sidebar/chat-markdown-math.ts) for markdown-it.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { BlockContent, DefinitionContent, Literal, Parent, PhrasingContent } from 'mdast'
|
||||
import type { GitHubCalloutKind } from '../../../src/shared/github/markdown/github-callouts'
|
||||
import type { GitHubCalloutKind } from '../../../../src/shared/github/markdown/github-callouts'
|
||||
|
||||
// The node types MobileMarkdown adds to mdast, registered by declaration merging. Merging needs
|
||||
// interfaces, which React Doctor's root rules flag; mobile's lint config ignores this file because
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { StyleSheet, type TextStyle } from 'react-native'
|
||||
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet, themedStyles } from '../theme/themed-styles'
|
||||
import { colors, radii, spacing, typography } from '../../theme/mobile-theme'
|
||||
import { themedStyleSheet, themedStyles } from '../../theme/themed-styles'
|
||||
|
||||
/** Lifts the 13pt base to 14pt body text for documents (file preview, PR bodies); headings scale with it. */
|
||||
export const MOBILE_MARKDOWN_DOCUMENT_SCALE = 14 / 13
|
||||
+4
-1
@@ -1,5 +1,8 @@
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import { findProseUrlEnd, splitAtProseUrlTerminators } from '../../../src/shared/prose-url-boundary'
|
||||
import {
|
||||
findProseUrlEnd,
|
||||
splitAtProseUrlTerminators
|
||||
} from '../../../../../src/shared/prose-url-boundary'
|
||||
|
||||
type Linkify = MarkdownIt['linkify']
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import type { Root, RootContent } from 'mdast'
|
||||
import type { Token } from './mobile-markdown-code-nodes'
|
||||
import { hasDetailsSectionTag } from './mobile-markdown-details'
|
||||
import type { MathEnv } from './mobile-markdown-math'
|
||||
import type { Token } from '../blocks/mobile-markdown-code-nodes'
|
||||
import { hasDetailsSectionTag } from '../blocks/mobile-markdown-details'
|
||||
import type { MathEnv } from '../math/mobile-markdown-math'
|
||||
import { markdownTokens, parseMobileMarkdownTree } from './mobile-markdown-tree'
|
||||
|
||||
/** One parse's finished blocks, kept for the next parse of the same text grown longer. */
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { PhrasingContent } from 'mdast'
|
||||
import type { Token } from './mobile-markdown-code-nodes'
|
||||
import type { Token } from '../blocks/mobile-markdown-code-nodes'
|
||||
import { plainText } from './mobile-markdown-plain-text'
|
||||
import { attrValue } from './mobile-markdown-preview-html'
|
||||
|
||||
+6
-6
@@ -1,17 +1,17 @@
|
||||
import type { PhrasingContent } from 'mdast'
|
||||
import { Fragment, type ReactNode } from 'react'
|
||||
import type { TextProps } from 'react-native'
|
||||
import { openExternalLink } from '../platform/external-links/external-link'
|
||||
import { openExternalLink } from '../../../platform/external-links/external-link'
|
||||
import {
|
||||
detectFilePathSegments,
|
||||
isFilePathCodeSpan,
|
||||
normalizeFilePath
|
||||
} from './markdown-file-path-detection'
|
||||
import { routeMarkdownHref } from './markdown-href-routing'
|
||||
import { styles, type MarkdownTone } from './mobile-markdown-styles'
|
||||
import { MarkdownText } from './mobile-markdown-text'
|
||||
} from '../links/markdown-file-path-detection'
|
||||
import { routeMarkdownHref } from '../links/markdown-href-routing'
|
||||
import { styles, type MarkdownTone } from '../mobile-markdown-styles'
|
||||
import { MarkdownText } from '../mobile-markdown-text'
|
||||
import { plainText } from './mobile-markdown-plain-text'
|
||||
import { MobileMarkdownMath } from './MobileMarkdownMath'
|
||||
import { MobileMarkdownMath } from '../math/MobileMarkdownMath'
|
||||
|
||||
export type MarkdownInlineContext = {
|
||||
// File targets call this when viewer exists; omit to keep paths plain.
|
||||
+5
-5
@@ -3,13 +3,13 @@ import cjkFriendly from 'markdown-it-cjk-friendly'
|
||||
import footnote from 'markdown-it-footnote'
|
||||
import type { AlignType, Parent, PhrasingContent, Root, RootContent, Table } from 'mdast'
|
||||
import remend from 'remend'
|
||||
import { forEachGitHubCallout } from '../../../src/shared/github/markdown/github-callouts'
|
||||
import { codeNode, type Token } from './mobile-markdown-code-nodes'
|
||||
import { forEachGitHubCallout } from '../../../../../src/shared/github/markdown/github-callouts'
|
||||
import { codeNode, type Token } from '../blocks/mobile-markdown-code-nodes'
|
||||
import { inlineTagNode, inlineTagPairs } from './mobile-markdown-inline-html'
|
||||
import { readTaskMarker } from './mobile-markdown-task-items'
|
||||
import { plainText } from './mobile-markdown-plain-text'
|
||||
import { appendHtmlBlock, noteInlineHtml, popContainer } from './mobile-markdown-details'
|
||||
import { mobileMarkdownMath, type MathEnv } from './mobile-markdown-math'
|
||||
import { appendHtmlBlock, noteInlineHtml, popContainer } from '../blocks/mobile-markdown-details'
|
||||
import { mobileMarkdownMath, type MathEnv } from '../math/mobile-markdown-math'
|
||||
import {
|
||||
displayFormulas,
|
||||
inlineMathNode,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
mathClosed,
|
||||
mathFenceNode,
|
||||
withoutMath
|
||||
} from './mobile-markdown-math-nodes'
|
||||
} from '../math/mobile-markdown-math-nodes'
|
||||
import { withImagesSplitOut } from './mobile-markdown-html-images'
|
||||
import { normalizeMobileMarkdownPreviewHtml } from './mobile-markdown-preview-html'
|
||||
import { endLinkifyUrlsAtProsePunctuation } from './markdown-linkify-prose-boundary'
|
||||
+4
-4
@@ -3,17 +3,17 @@ import { useMemo, useRef } from 'react'
|
||||
import {
|
||||
linkGitHubReferences,
|
||||
type GitHubRepoReference
|
||||
} from '../../../src/shared/github/markdown/github-reference-links'
|
||||
} from '../../../../../src/shared/github/markdown/github-reference-links'
|
||||
import {
|
||||
linkNativeChatFilePaths,
|
||||
type FileLinkExists
|
||||
} from '../../../src/shared/native-chat/native-chat-file-link-tree'
|
||||
import type { NativeChatVisualDirective } from '../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
} from '../../../../../src/shared/native-chat/native-chat-file-link-tree'
|
||||
import type { NativeChatVisualDirective } from '../../../../../src/shared/native-chat/native-chat-visual-directive'
|
||||
import {
|
||||
parseGrowingMobileMarkdownTree,
|
||||
type MobileMarkdownTreeCache
|
||||
} from './mobile-markdown-growing-tree'
|
||||
import { protectMobileMarkdownVisualLines } from './mobile-markdown-visual-lines'
|
||||
import { protectMobileMarkdownVisualLines } from '../blocks/mobile-markdown-visual-lines'
|
||||
|
||||
const NO_DIRECTIVES: NativeChatVisualDirective[] = []
|
||||
|
||||
+8
-8
@@ -11,24 +11,24 @@ import {
|
||||
type ForwardedRef
|
||||
} from 'react'
|
||||
import { View, type HostInstance } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { openExternalLink } from '../platform/external-links/external-link'
|
||||
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
|
||||
import { colors } from '../../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
|
||||
import { openExternalLink } from '../../platform/external-links/external-link'
|
||||
import { MobileRichMarkdownToolbar } from './MobileRichMarkdownToolbar'
|
||||
import { TextInputModal } from './TextInputModal'
|
||||
import { TextInputModal } from '../TextInputModal'
|
||||
import {
|
||||
ensureDocumentStyle,
|
||||
mountRichMarkdownWebDocument,
|
||||
type RichMarkdownWebDocument
|
||||
} from './rich-markdown/rich-markdown-web-document-mount'
|
||||
} from './document/rich-markdown-web-document-mount'
|
||||
import { useMobileRichMarkdownEditorController } from './use-mobile-rich-markdown-editor-controller'
|
||||
import {
|
||||
noWorkspaceImage,
|
||||
type RichMarkdownEditorApi,
|
||||
type RichMarkdownUrlPromptKind
|
||||
} from './rich-markdown/document-host-seams'
|
||||
} from './document/document-host-seams'
|
||||
import type {
|
||||
MobileRichMarkdownEditorMessage,
|
||||
MobileRichMarkdownEditorProps
|
||||
+5
-5
@@ -17,11 +17,11 @@ import {
|
||||
Quote,
|
||||
Strikethrough
|
||||
} from 'lucide-react-native'
|
||||
import { colors, radii, spacing } from '../theme/mobile-theme'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { colors, radii, spacing } from '../../theme/mobile-theme'
|
||||
import type { Translate } from '../../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
|
||||
import { themedStyleSheet } from '../../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
|
||||
import type { MobileRichMarkdownCommand } from './mobile-rich-markdown-editor-contract'
|
||||
|
||||
type ToolbarItem = {
|
||||
@@ -1,13 +1,21 @@
|
||||
import { createRichMarkdownEditorScope } from './document-scope'
|
||||
import { currentMarkdown, setEditable, setMarkdown, stopEditorContent } from './editor-content'
|
||||
import { startEditorListeners, stopEditorListeners } from './editor-listeners'
|
||||
import { startEditorSurface } from './editor-surface'
|
||||
import { startHostBridge } from './host-bridge'
|
||||
import { dismissKeyboard } from './keyboard-dismiss'
|
||||
import { runCommand } from './editor-commands'
|
||||
import { startKeyboardInset, stopKeyboardInset } from './keyboard-inset'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownEditorDocument, RichMarkdownEditorHost } from './document-host-seams'
|
||||
import { createRichMarkdownEditorScope } from './document/document-scope'
|
||||
import {
|
||||
currentMarkdown,
|
||||
setEditable,
|
||||
setMarkdown,
|
||||
stopEditorContent
|
||||
} from './editing/editor-content'
|
||||
import { startEditorListeners, stopEditorListeners } from './editing/editor-listeners'
|
||||
import { startEditorSurface } from './editing/editor-surface'
|
||||
import { startHostBridge } from './document/host-bridge'
|
||||
import { dismissKeyboard } from './editing/keyboard-dismiss'
|
||||
import { runCommand } from './editing/editor-commands'
|
||||
import { startKeyboardInset, stopKeyboardInset } from './editing/keyboard-inset'
|
||||
import type { RichMarkdownEditorScope } from './document/document-scope'
|
||||
import type {
|
||||
RichMarkdownEditorDocument,
|
||||
RichMarkdownEditorHost
|
||||
} from './document/document-host-seams'
|
||||
|
||||
// Each mount owns local editor scope so overlapping editors/listeners never read another mount.
|
||||
// Start in order: read surface, install listeners, start keyboard measurement, then announce ready.
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
import type { RichMarkdownEditorDocument } from './document-host-seams'
|
||||
+1
-10
@@ -1,7 +1,7 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
import type { RichMarkdownEditorDocument } from './document-host-seams'
|
||||
@@ -64,15 +64,6 @@ describe('an editor document that is stopped', () => {
|
||||
expect(posted).toEqual([])
|
||||
})
|
||||
|
||||
it('stops observing the viewport, so no inset of its own reaches the next mount', () => {
|
||||
const posted: MobileRichMarkdownEditorMessage[] = []
|
||||
plantMarkup()
|
||||
const mounted = mount(posted)
|
||||
expect(mounted.keyboard.observers).toHaveLength(1)
|
||||
mounted.stop()
|
||||
expect(mounted.keyboard.observers).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('leaves a second mount a document of its own, not the first one continued', () => {
|
||||
const first: MobileRichMarkdownEditorMessage[] = []
|
||||
plantMarkup()
|
||||
-6
@@ -6,12 +6,6 @@ import {
|
||||
type RichMarkdownEditorHost,
|
||||
type RichMarkdownEditorHostSeams
|
||||
} from './document-host-seams'
|
||||
export type {
|
||||
RichMarkdownEditorApi,
|
||||
RichMarkdownEditorDocument,
|
||||
RichMarkdownEditorHost,
|
||||
RichMarkdownEditorHostSeams
|
||||
} from './document-host-seams'
|
||||
|
||||
// Own all mutable editor state per factory call; module let would leak generation, caret and inset between overlapping mounts (ruling 21).
|
||||
export type RichMarkdownEditorState = {
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { colors, getMobileColorScheme } from '../../theme/mobile-theme'
|
||||
import { TEXT_INPUT_FONT_SIZE } from '../../platform/text-input/text-input-font-size'
|
||||
import { colors, getMobileColorScheme } from '../../../theme/mobile-theme'
|
||||
import { TEXT_INPUT_FONT_SIZE } from '../../../platform/text-input/text-input-font-size'
|
||||
|
||||
// Read theme at build time; native embeds sheet, page scopes it to editor host.
|
||||
// #editor scrolls itself: iOS WebKit stops drawing selection ~2 screens into an editable scrolled by a separate box (#34472).
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { scopeDocumentStyleToHost } from '../../style-scoping/document-style-scoping'
|
||||
import { scopeDocumentStyleToHost } from '../../../style-scoping/document-style-scoping'
|
||||
|
||||
const PREFIX = '.orca-rich-markdown-document-host'
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { richMarkdownEditorStyle } from './document-style'
|
||||
import { getMobileColorScheme } from '../../theme/mobile-theme'
|
||||
import { scopeDocumentStyleToHost } from '../../style-scoping/document-style-scoping'
|
||||
import { getMobileColorScheme } from '../../../theme/mobile-theme'
|
||||
import { scopeDocumentStyleToHost } from '../../../style-scoping/document-style-scoping'
|
||||
import type { RichMarkdownEditorApi, RichMarkdownUrlPromptKind } from './document-host-seams'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
+3
-3
@@ -6,10 +6,10 @@ import {
|
||||
wrapSelection
|
||||
} from './editor-selection'
|
||||
import { editorElement } from './editor-surface'
|
||||
import { isSafeUrl } from './markdown-escaping'
|
||||
import { isSafeUrl } from '../serialization/markdown-escaping'
|
||||
import type { MobileRichMarkdownCommand } from '../mobile-rich-markdown-editor-contract'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownUrlPromptKind } from './document-host-seams'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
import type { RichMarkdownUrlPromptKind } from '../document/document-host-seams'
|
||||
|
||||
const TASK_LIST_HTML =
|
||||
'<ul data-type="taskList"><li data-checked="false"><label contenteditable="false">' +
|
||||
+4
-10
@@ -1,10 +1,10 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
import type { RichMarkdownEditorDocument } from './document-host-seams'
|
||||
import type { RichMarkdownEditorDocument } from '../document/document-host-seams'
|
||||
|
||||
// Round-trip host generation unchanged so pre-replacement changes can be discarded; suppress document’s own rewrites to avoid echo.
|
||||
const started: RichMarkdownEditorDocument[] = []
|
||||
@@ -76,15 +76,9 @@ describe('the editor document content', () => {
|
||||
})
|
||||
|
||||
it('reports one change for a checkbox tap, not one per event the tap raises', () => {
|
||||
// Assert click/input/change all bubble for one tick, then prove only one report; missing events cannot satisfy count accidentally.
|
||||
const editor = editorDocument()
|
||||
editor.handle.setMarkdown('- [ ] Open', 1)
|
||||
const reached: string[] = []
|
||||
for (const name of ['click', 'input', 'change']) {
|
||||
editor.editor.addEventListener(name, (event) => reached.push(event.type))
|
||||
}
|
||||
editor.editor.querySelector('input')!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
expect(reached).toEqual(['click', 'input', 'change'])
|
||||
expect(editor.editor.querySelector('li')!.getAttribute('data-checked')).toBe('true')
|
||||
expect(editor.posted).toEqual([{ type: 'change', markdown: '- [x] Open', generation: 1 }])
|
||||
})
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { blockMarkdown } from './html-block-markdown'
|
||||
import { markdownToHtml } from './markdown-to-html'
|
||||
import { WORKSPACE_IMAGE_PATH_ATTRIBUTE } from './markdown-inline-render'
|
||||
import { post } from './host-bridge'
|
||||
import { blockMarkdown } from '../serialization/html-block-markdown'
|
||||
import { markdownToHtml } from '../serialization/markdown-to-html'
|
||||
import { WORKSPACE_IMAGE_PATH_ATTRIBUTE } from '../serialization/markdown-inline-render'
|
||||
import { post } from '../document/host-bridge'
|
||||
import { editorElement } from './editor-surface'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
/** What the surface holds right now, as markdown: every block, blank ones dropped. */
|
||||
export function currentMarkdown(scope: RichMarkdownEditorScope): string {
|
||||
+3
-5
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
@@ -98,11 +98,9 @@ it('keeps an image nested inside inline formatting', async () => {
|
||||
|
||||
it('leaves existing code blocks intact when selecting surrounding paragraphs', async () => {
|
||||
const editor = mount('Before\n\n```ts\nconst n = 1\n```\n\nAfter')
|
||||
const original = editor.surface.querySelector('pre')!.outerHTML
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(editor.surface)
|
||||
select(range)
|
||||
await editor.send.runCommand('inlineCode')
|
||||
expect(editor.surface.querySelector('pre')!.outerHTML).toBe(original)
|
||||
expect(editor.send.currentMarkdown()).toBe('`Before`\n\n```ts\nconst n = 1\n```\n\n`After`')
|
||||
})
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import { emitChange } from './editor-content'
|
||||
import { applySelectionRange, caretRangeAtPoint, focusEditor } from './editor-selection'
|
||||
import { editorElement } from './editor-surface'
|
||||
import { post } from './host-bridge'
|
||||
import { post } from '../document/host-bridge'
|
||||
import { runCommand } from './editor-commands'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
/** What the event landed on, for a target that may be a text node or nothing at all. */
|
||||
function closestFrom(target: EventTarget | null, selector: string): Element | null {
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import type { RichMarkdownEditorDocument } from './document-host-seams'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import type { RichMarkdownEditorDocument } from '../document/document-host-seams'
|
||||
|
||||
// Simulate WebKit dropping DOM ranges on blur; restore saved caret for toolbar command instead of inserting at document end. A preserving browser would hide failure.
|
||||
const started: RichMarkdownEditorDocument[] = []
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { editorElement } from './editor-surface'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
export function focusEditor(scope: RichMarkdownEditorScope) {
|
||||
editorElement(scope).focus()
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { RICH_MARKDOWN_EDITOR_ELEMENT_ID } from './document-markup'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import { RICH_MARKDOWN_EDITOR_ELEMENT_ID } from '../document/document-markup'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
// Factory starts before exposing API, so surface is non-null when reachable; missing markup fails first property read like native unguarded script.
|
||||
export function editorElement(scope: RichMarkdownEditorScope): HTMLElement {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { rememberSelection } from './editor-selection'
|
||||
import { editorElement } from './editor-surface'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
// Save caret before blur closes keyboard because WebKit drops DOM selection; flag tells next command to restore saved range.
|
||||
export function dismissKeyboard(scope: RichMarkdownEditorScope) {
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { post } from './host-bridge'
|
||||
import type { RichMarkdownKeyboardInsetReader } from './document-host-seams'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import { post } from '../document/host-bridge'
|
||||
import type { RichMarkdownKeyboardInsetReader } from '../document/document-host-seams'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
/** Posts the covered height, and only when it has actually moved. */
|
||||
function reportKeyboardInset(
|
||||
+1
-1
@@ -41,7 +41,7 @@ vi.mock('lucide-react-native', () => ({
|
||||
Strikethrough: () => null
|
||||
}))
|
||||
|
||||
vi.mock('./TextInputModal', async () => {
|
||||
vi.mock('../TextInputModal', async () => {
|
||||
const React = await import('react')
|
||||
return {
|
||||
TextInputModal: (props: Record<string, unknown>) => React.createElement('TextInputModal', props)
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createRawMarkdownCodeSpanScanner,
|
||||
type MarkdownCodeSpanScanner
|
||||
} from '../../../../src/shared/markdown-code-span-runs'
|
||||
} from '../../../../../src/shared/markdown-code-span-runs'
|
||||
import {
|
||||
decodeMarkdownEntities,
|
||||
escapeAttr,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
escapeLiteralHtml,
|
||||
isSafeUrl
|
||||
} from './markdown-escaping'
|
||||
import { routeMarkdownHref } from '../markdown-href-routing'
|
||||
import { routeMarkdownHref } from '../../markdown/links/markdown-href-routing'
|
||||
|
||||
/** Holds a workspace image's markdown path; the page resolves it, so the host loads `src` instead. */
|
||||
export const WORKSPACE_IMAGE_PATH_ATTRIBUTE = 'data-workspace-path'
|
||||
+3
-19
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
@@ -39,26 +39,14 @@ for (const [kind, source, selector] of lists) {
|
||||
const { editorDocument, editor, messages } = surface(source)
|
||||
const body = editor.querySelector(selector)!
|
||||
body.innerHTML = html
|
||||
const live = body.outerHTML
|
||||
const contents = Array.from(editor.querySelectorAll('li')).map((item) => item.textContent)
|
||||
const checks = Array.from(editor.querySelectorAll<HTMLInputElement>('input')).map(
|
||||
(input) => input.checked
|
||||
)
|
||||
editor.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertParagraph' }))
|
||||
const markdown = source.replace('First', text)
|
||||
|
||||
expect(body.outerHTML).toBe(live)
|
||||
expect(messages.filter((message) => message.type === 'change')).toEqual([
|
||||
{ type: 'change', generation: 7, markdown }
|
||||
])
|
||||
editorDocument.send.setMarkdown(markdown, 8)
|
||||
expect(editor.querySelector(selector)!.querySelector('p')!.innerHTML).toBe(inline)
|
||||
expect(Array.from(editor.querySelectorAll('li')).map((item) => item.textContent)).toEqual(
|
||||
contents
|
||||
)
|
||||
expect(
|
||||
Array.from(editor.querySelectorAll<HTMLInputElement>('input')).map((input) => input.checked)
|
||||
).toEqual(checks)
|
||||
expect(editor.querySelector(':scope > p')?.textContent).toBe('Outside')
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
}
|
||||
@@ -74,11 +62,9 @@ it.each([
|
||||
const { editorDocument, editor } = surface('- First\n- Next')
|
||||
const item = editor.querySelector('li')!
|
||||
item.innerHTML = html
|
||||
const live = item.outerHTML
|
||||
const markdown = `- ${text}\n- Next`
|
||||
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
expect(item.outerHTML).toBe(live)
|
||||
editorDocument.send.setMarkdown(markdown, 8)
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
})
|
||||
@@ -87,11 +73,9 @@ it('retains nested lists and checkbox labels while serializing the owning item o
|
||||
const { editorDocument, editor } = surface('- [x] First\n 8. Child\n 9. Next\n- [ ] Last')
|
||||
const paragraph = editor.querySelector('li > div > p')!
|
||||
paragraph.insertAdjacentHTML('afterend', '<p>Second</p>')
|
||||
const live = editor.innerHTML
|
||||
const markdown = '- [x] First<br />Second\n 8. Child\n 9. Next\n- [ ] Last'
|
||||
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
expect(editor.innerHTML).toBe(live)
|
||||
editorDocument.send.setMarkdown(markdown, 8)
|
||||
expect(editor.querySelectorAll('li').length).toBe(4)
|
||||
expect(editor.querySelectorAll('ol li').length).toBe(2)
|
||||
+5
-12
@@ -1,9 +1,9 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import type { RichMarkdownEditorDocument } from './document-host-seams'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { RichMarkdownEditorDocument } from '../document/document-host-seams'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
let mounted: RichMarkdownEditorDocument | undefined
|
||||
@@ -40,19 +40,16 @@ describe('mobile list code blocks', () => {
|
||||
it.each(contexts)(
|
||||
'preserves literal code and continuation paragraphs in $name items',
|
||||
({ source, selector }) => {
|
||||
const { editorDocument, messages, editor } = open(source)
|
||||
const { editorDocument, editor } = open(source)
|
||||
const body = editor.querySelector(selector)!
|
||||
body.innerHTML =
|
||||
'<p>Before <strong>bold</strong><br>continued</p><pre data-language="js"><code></code></pre><p>After <em>tail</em></p>'
|
||||
const code = ' indented\n\ttab\n\t\n \n\n````\n<tag>& <'
|
||||
body.querySelector('code')!.textContent = code
|
||||
const items = editor.querySelectorAll('li').length
|
||||
const before = body.innerHTML
|
||||
messages.length = 0
|
||||
editor.dispatchEvent(new Event('input'))
|
||||
const saved = editorDocument.send.currentMarkdown()
|
||||
expect(body.innerHTML).toBe(before)
|
||||
expect(messages).toEqual([{ type: 'change', markdown: saved, generation: 7 }])
|
||||
editorDocument.send.setMarkdown(saved, 8)
|
||||
const reopened = editor.querySelector(selector)!
|
||||
expect(reopened.querySelector('pre')?.textContent).toBe(code)
|
||||
@@ -64,9 +61,7 @@ describe('mobile list code blocks', () => {
|
||||
expect(reopened.querySelector('p br')).not.toBeNull()
|
||||
expect(reopened.querySelector('strong')?.textContent).toBe('bold')
|
||||
expect(reopened.querySelector('em')?.textContent).toBe('tail')
|
||||
expect(editor.querySelectorAll('li').length).toBe(items)
|
||||
expect(editor.querySelector(':scope > p')?.textContent).toBe('Outside')
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(saved)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -107,7 +102,6 @@ describe('mobile list code blocks', () => {
|
||||
expect(saved).toContain('### Sub')
|
||||
expect(saved).toContain('- # Title')
|
||||
editorDocument.send.setMarkdown(saved, 8)
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(saved)
|
||||
})
|
||||
|
||||
it('writes a code block inserted into an item as a fence its reader keeps in place', () => {
|
||||
@@ -131,6 +125,5 @@ describe('mobile list code blocks', () => {
|
||||
editorDocument.send.setMarkdown(saved, 8)
|
||||
expect(editor.querySelector<HTMLInputElement>('li > label input')?.checked).toBe(true)
|
||||
expect(editor.querySelector('li pre')?.textContent).toBe('literal')
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(saved)
|
||||
})
|
||||
})
|
||||
+3
-17
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
@@ -35,23 +35,13 @@ for (const [kind, source, selector] of lists) {
|
||||
])(`keeps ${kind} item soft break inside its list through host save: %s`, (html, text) => {
|
||||
const { editorDocument, editor, messages } = surface(source)
|
||||
editor.querySelector(selector)!.innerHTML = html
|
||||
const live = editor.querySelector(selector)!.outerHTML
|
||||
const items = Array.from(editor.querySelectorAll('li')).map((item) => item.textContent)
|
||||
const checkboxes = Array.from(editor.querySelectorAll<HTMLInputElement>('input')).map(
|
||||
(input) => input.checked
|
||||
)
|
||||
editor.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertLineBreak' }))
|
||||
const markdown = source.replace('First', text)
|
||||
|
||||
expect(editor.querySelector(selector)!.outerHTML).toBe(live)
|
||||
expect(messages.filter((message) => message.type === 'change')).toEqual([
|
||||
{ type: 'change', generation: 7, markdown }
|
||||
])
|
||||
editorDocument.send.setMarkdown(markdown, 8)
|
||||
expect(Array.from(editor.querySelectorAll('li')).map((item) => item.textContent)).toEqual(items)
|
||||
expect(
|
||||
Array.from(editor.querySelectorAll<HTMLInputElement>('input')).map((input) => input.checked)
|
||||
).toEqual(checkboxes)
|
||||
expect(editor.querySelector(selector)?.innerHTML).toBe(html)
|
||||
expect(editor.querySelector(':scope > p')?.textContent).toBe('Outside')
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
@@ -71,7 +61,6 @@ it.each([
|
||||
expect(editor.querySelector(selector)?.textContent).toBe(text)
|
||||
expect(editor.querySelector(selector)?.children.length).toBe(0)
|
||||
expect(editor.querySelectorAll('li').length).toBe(2)
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
})
|
||||
|
||||
it('recognizes attribute-free list breaks while keeping code and paragraph markup literal', () => {
|
||||
@@ -101,7 +90,6 @@ it.each([
|
||||
'',
|
||||
'Last'
|
||||
])
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
})
|
||||
|
||||
it.each([
|
||||
@@ -115,11 +103,9 @@ it.each([
|
||||
const { editorDocument, editor } = surface('- First\n- Next\n\nOutside')
|
||||
const item = editor.querySelector('li')!
|
||||
item.innerHTML = html
|
||||
const live = item.outerHTML
|
||||
const markdown = `- ${text}\n- Next\n\nOutside`
|
||||
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
expect(item.outerHTML).toBe(live)
|
||||
editorDocument.send.setMarkdown(markdown, 8)
|
||||
expect(editorDocument.send.currentMarkdown()).toBe(markdown)
|
||||
})
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { openingFence } from './markdown-code-fence'
|
||||
import { renderInline } from './markdown-inline-render'
|
||||
import { listKind, type ParsedListItem } from './markdown-list-parse'
|
||||
import type { RichMarkdownEditorScope } from './document-scope'
|
||||
import type { RichMarkdownEditorScope } from '../document/document-scope'
|
||||
|
||||
// The leading lines stay one inline paragraph so block syntax on the marker line (`- # Title`) reads as text.
|
||||
function itemContent(item: ParsedListItem, renderBlocks: (markdown: string) => string): string {
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createRichMarkdownEditorDocument } from './create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorDocument } from '../create-rich-markdown-editor-document'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import type { MobileRichMarkdownEditorMessage } from '../mobile-rich-markdown-editor-contract'
|
||||
|
||||
const stopDocuments: (() => void)[] = []
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { runInNewContext } from 'node:vm'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { quietRichMarkdownEditorHost } from './rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorScope } from './document-scope'
|
||||
import { quietRichMarkdownEditorHost } from '../document/rich-markdown-editor-host.test-support'
|
||||
import { createRichMarkdownEditorScope } from '../document/document-scope'
|
||||
import { listMarkdown } from './html-list-markdown'
|
||||
import { markdownToHtml } from './markdown-to-html'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from './document-markup'
|
||||
import { currentMarkdown } from './editor-content'
|
||||
import { startEditorSurface } from './editor-surface'
|
||||
import { RICH_MARKDOWN_EDITOR_MARKUP } from '../document/document-markup'
|
||||
import { currentMarkdown } from '../editing/editor-content'
|
||||
import { startEditorSurface } from '../editing/editor-surface'
|
||||
|
||||
// Render then serialize real DOM and compare original source: two implementations can agree while both lose numbering.
|
||||
// Real closest/children/cloneNode/checked semantics prevent fixtures inventing structures renderer never produces.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user