mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-02 22:52:58 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2ee257b19 | ||
|
|
b280ca5355 |
@@ -12,7 +12,7 @@ Every block is separated by one gap, `density.blockGap`: 12 on compact layouts,
|
||||
|
||||
Workspace file links use the shared file-link resolver. The workspace supplies its client, root and open-file action once; chat can supply the agent's more specific context. URLs use the same link press path. Recognized inline-code paths stay tappable. The library callback carries the tapped occurrence’s syntax context. Preserve it when opening files: explicit Markdown links decode URL escapes, while recognized inline-code paths preserve literal percent characters and use suffix lookup. Identical URL strings in different occurrences must stay distinct. Explicit absolute workspace links receive a relative hover title; existing authored titles remain intact.
|
||||
|
||||
Resolve workspace image paths over the existing session RPC, then give the library a cached native file URL or web blob URL. HTTP(S) images pass through unchanged. This preserves relay, SSH and local socket connections, which do not expose a daemon HTTP endpoint. Each mounted renderer owns its image acquisition identity, so a later reply reads a rewritten path again while the earlier mounted reply retains its snapshot. Image taps open the attachment lightbox. File previews resolve relative images against the Markdown file's directory. An image alone on its line becomes its own paragraph before the library sees it, so it renders as a block on every platform; the library only treats sole-child paragraph images as blocks, and with hard soft breaks every newline is a break node, which left own-line images inline on iOS and web. Images that share a line with text stay inline.
|
||||
Resolve workspace image paths over the existing session RPC, then give the library a cached native file URL or web blob URL. HTTP(S) images pass through unchanged. This preserves relay, SSH and local socket connections, which do not expose a daemon HTTP endpoint. Each mounted renderer owns its image acquisition identity, so a later reply reads a rewritten path again while the earlier mounted reply retains its snapshot. Image taps open the attachment lightbox. File previews resolve relative images against the Markdown file's directory. An image alone on its line becomes its own paragraph before the library sees it, so it renders as a block on every platform; the library only treats sole-child paragraph images as blocks, and with hard soft breaks every newline is a break node, which left own-line images inline on iOS and web. Images that share a line with text stay inline at line height and keep their aspect ratio. A linked image counts as an image, so review-bot badges such as `[](url) Confidence` stay inline on web; the library's web renderer only checked direct paragraph children, so it rendered them as full-width block images (local patch in `patches/react-native-enriched-markdown+*.patch`).
|
||||
|
||||
## Engine constraints
|
||||
|
||||
|
||||
@@ -110,6 +110,37 @@ describe("shared Markdown source adaptation", () => {
|
||||
["https://x/q.png", false],
|
||||
]);
|
||||
});
|
||||
it("renders linked images that share a line with text inline", async () => {
|
||||
// A review bot's summary header: linked badges inside a heading with the score text.
|
||||
const reviewHeader =
|
||||
'<h2><a href="https://x/retrigger"><picture><source media="(prefers-color-scheme: dark)" srcset="https://x/r-dark.svg"><img alt="Retrigger" src="https://x/r.svg" align="right"></picture></a>Confidence Score: 5/5</h2>';
|
||||
const source = [
|
||||
reviewHeader,
|
||||
"[](https://x)",
|
||||
"[](https://x) [](https://x)",
|
||||
"See [ GitHub](https://x) for more.",
|
||||
].join("\n\n");
|
||||
const tree = await parseMarkdown(prepareMarkdownSource(source).render(), {
|
||||
hardSoftBreaks: true,
|
||||
latexMath: false,
|
||||
});
|
||||
markInlineImages(tree);
|
||||
const images: ASTNode[] = [];
|
||||
const collect = (node: ASTNode) => {
|
||||
if (node.type === "Image") images.push(node);
|
||||
node.children?.forEach(collect);
|
||||
};
|
||||
collect(tree);
|
||||
expect(
|
||||
images.map((image) => [image.attributes?.url, image.attributes?.isInline === true]),
|
||||
).toEqual([
|
||||
["https://x/r.svg", true],
|
||||
["https://x/alone.png", false],
|
||||
["https://x/a.png", true],
|
||||
["https://x/b.png", true],
|
||||
["https://x/gh.png", true],
|
||||
]);
|
||||
});
|
||||
it("escapes spoiler delimiters only in prose", () => {
|
||||
expect(prepareMarkdownSource("Show ||words|| and `||code||`.").render()).toBe(
|
||||
"Show \\|\\|words\\|\\| and `||code||`.",
|
||||
|
||||
@@ -56,6 +56,9 @@ function spawnRelayDevServer(port: number): ChildProcess {
|
||||
wranglerCliPath,
|
||||
"dev",
|
||||
"--local",
|
||||
// Local execution still inherits production vars; exercise the local Durable Object.
|
||||
"--var",
|
||||
"PASEO_RELAY_UPSTREAM:",
|
||||
"--ip",
|
||||
"127.0.0.1",
|
||||
"--port",
|
||||
|
||||
@@ -3521,7 +3521,21 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/module/normalizeMar
|
||||
diff --git a/node_modules/react-native-enriched-markdown/src/web/styles.ts b/node_modules/react-native-enriched-markdown/src/web/styles.ts
|
||||
--- a/node_modules/react-native-enriched-markdown/src/web/styles.ts
|
||||
+++ b/node_modules/react-native-enriched-markdown/src/web/styles.ts
|
||||
@@ -290,7 +290,7 @@
|
||||
@@ -260,9 +260,12 @@ function videoStyle(style: MarkdownStyleInternal): CSSProperties {
|
||||
|
||||
function inlineImageStyle(style: MarkdownStyleInternal): CSSProperties {
|
||||
const size = style.inlineImage.size;
|
||||
+ // Inline images match the line height and keep their aspect ratio, so wide
|
||||
+ // badges stay legible instead of shrinking into a square.
|
||||
return {
|
||||
- width: size,
|
||||
+ width: 'auto',
|
||||
height: size,
|
||||
+ maxWidth: '100%',
|
||||
verticalAlign: 'middle',
|
||||
display: 'inline',
|
||||
};
|
||||
@@ -290,7 +293,7 @@ function codeStyle(style: MarkdownStyleInternal): CSSProperties {
|
||||
const code = style.code;
|
||||
return {
|
||||
fontFamily: normalizeFontFamily(code.fontFamily),
|
||||
@@ -3533,7 +3547,21 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/styles.ts b/nod
|
||||
diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/styles.js b/node_modules/react-native-enriched-markdown/lib/module/web/styles.js
|
||||
--- a/node_modules/react-native-enriched-markdown/lib/module/web/styles.js
|
||||
+++ b/node_modules/react-native-enriched-markdown/lib/module/web/styles.js
|
||||
@@ -271,7 +271,7 @@
|
||||
@@ -243,9 +243,12 @@ function videoStyle(style) {
|
||||
}
|
||||
function inlineImageStyle(style) {
|
||||
const size = style.inlineImage.size;
|
||||
+ // Inline images match the line height and keep their aspect ratio, so wide
|
||||
+ // badges stay legible instead of shrinking into a square.
|
||||
return {
|
||||
- width: size,
|
||||
+ width: 'auto',
|
||||
height: size,
|
||||
+ maxWidth: '100%',
|
||||
verticalAlign: 'middle',
|
||||
display: 'inline'
|
||||
};
|
||||
@@ -271,7 +274,7 @@ function codeStyle(style) {
|
||||
const code = style.code;
|
||||
return {
|
||||
fontFamily: normalizeFontFamily(code.fontFamily),
|
||||
@@ -3884,3 +3912,87 @@ diff --git a/node_modules/react-native-enriched-markdown/android/generated/jni/r
|
||||
}
|
||||
auto tmp_color = map.find("color");
|
||||
if (tmp_color != map.end()) {
|
||||
diff --git a/node_modules/react-native-enriched-markdown/src/web/utils.ts b/node_modules/react-native-enriched-markdown/src/web/utils.ts
|
||||
--- a/node_modules/react-native-enriched-markdown/src/web/utils.ts
|
||||
+++ b/node_modules/react-native-enriched-markdown/src/web/utils.ts
|
||||
@@ -34,20 +34,32 @@ export function indexTaskItems(node: ASTNode, counter = { value: 0 }): void {
|
||||
* Stamps Image nodes with `isInline` when they appear inside a paragraph
|
||||
* that also contains other content (text, links, etc.).
|
||||
* Matches native behavior: sole-child images are block-level, mixed are inline.
|
||||
+ * A link wrapping only an image counts as an image, so `[](y) text`
|
||||
+ * stamps the nested image inline while a lone `[](z)` stays a block.
|
||||
* Mutates the AST in-place (safe: called once on the freshly-parsed result).
|
||||
*/
|
||||
export function markInlineImages(node: ASTNode): void {
|
||||
if (node.type === 'Paragraph' && node.children) {
|
||||
- const hasNonImageChild = node.children.some(
|
||||
- (child) => child.type !== 'Image'
|
||||
- );
|
||||
- if (hasNonImageChild) {
|
||||
- for (const child of node.children) {
|
||||
- if (child.type === 'Image') {
|
||||
- child.attributes = { ...child.attributes, isInline: true };
|
||||
- }
|
||||
- }
|
||||
+ if (!node.children.every(isImageLike)) {
|
||||
+ node.children.forEach(stampInlineImages);
|
||||
}
|
||||
}
|
||||
node.children?.forEach(markInlineImages);
|
||||
}
|
||||
+
|
||||
+function isImageLike(node: ASTNode): boolean {
|
||||
+ if (node.type === 'Image') return true;
|
||||
+ return (
|
||||
+ node.type === 'Link' &&
|
||||
+ node.children?.length === 1 &&
|
||||
+ isImageLike(node.children[0]!)
|
||||
+ );
|
||||
+}
|
||||
+
|
||||
+function stampInlineImages(node: ASTNode): void {
|
||||
+ if (node.type === 'Image') {
|
||||
+ node.attributes = { ...node.attributes, isInline: true };
|
||||
+ return;
|
||||
+ }
|
||||
+ node.children?.forEach(stampInlineImages);
|
||||
+}
|
||||
diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/utils.js b/node_modules/react-native-enriched-markdown/lib/module/web/utils.js
|
||||
--- a/node_modules/react-native-enriched-markdown/lib/module/web/utils.js
|
||||
+++ b/node_modules/react-native-enriched-markdown/lib/module/web/utils.js
|
||||
@@ -40,18 +40,24 @@ export function indexTaskItems(node, counter = {
|
||||
*/
|
||||
export function markInlineImages(node) {
|
||||
if (node.type === 'Paragraph' && node.children) {
|
||||
- const hasNonImageChild = node.children.some(child => child.type !== 'Image');
|
||||
- if (hasNonImageChild) {
|
||||
- for (const child of node.children) {
|
||||
- if (child.type === 'Image') {
|
||||
- child.attributes = {
|
||||
- ...child.attributes,
|
||||
- isInline: true
|
||||
- };
|
||||
- }
|
||||
- }
|
||||
+ if (!node.children.every(isImageLike)) {
|
||||
+ node.children.forEach(stampInlineImages);
|
||||
}
|
||||
}
|
||||
node.children?.forEach(markInlineImages);
|
||||
}
|
||||
+function isImageLike(node) {
|
||||
+ if (node.type === 'Image') return true;
|
||||
+ return node.type === 'Link' && node.children?.length === 1 && isImageLike(node.children[0]);
|
||||
+}
|
||||
+function stampInlineImages(node) {
|
||||
+ if (node.type === 'Image') {
|
||||
+ node.attributes = {
|
||||
+ ...node.attributes,
|
||||
+ isInline: true
|
||||
+ };
|
||||
+ return;
|
||||
+ }
|
||||
+ node.children?.forEach(stampInlineImages);
|
||||
+}
|
||||
//# sourceMappingURL=utils.js.map
|
||||
\ No newline at end of file
|
||||
|
||||
Reference in New Issue
Block a user