@agimon-ai/po-native-components 0.0.0-alpha.21 → 0.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/components/CodeBlock.d.mts.map +1 -1
  2. package/dist/components/CodeBlock.mjs +4 -33
  3. package/dist/components/CodeBlock.mjs.map +1 -1
  4. package/dist/components/CopyButton.d.mts +13 -0
  5. package/dist/components/CopyButton.d.mts.map +1 -0
  6. package/dist/components/CopyButton.mjs +43 -0
  7. package/dist/components/CopyButton.mjs.map +1 -0
  8. package/dist/components/HashlineLines.d.mts +13 -0
  9. package/dist/components/HashlineLines.d.mts.map +1 -0
  10. package/dist/components/HashlineLines.mjs +137 -0
  11. package/dist/components/HashlineLines.mjs.map +1 -0
  12. package/dist/components/Markdown.d.mts +8 -1
  13. package/dist/components/Markdown.d.mts.map +1 -1
  14. package/dist/components/Markdown.mjs +198 -17
  15. package/dist/components/Markdown.mjs.map +1 -1
  16. package/dist/components/SyntaxLine.d.mts +19 -0
  17. package/dist/components/SyntaxLine.d.mts.map +1 -0
  18. package/dist/components/SyntaxLine.mjs +132 -0
  19. package/dist/components/SyntaxLine.mjs.map +1 -0
  20. package/dist/components/ToolPathLink.d.mts +10 -0
  21. package/dist/components/ToolPathLink.d.mts.map +1 -0
  22. package/dist/components/ToolPathLink.mjs +40 -0
  23. package/dist/components/ToolPathLink.mjs.map +1 -0
  24. package/dist/icons/iconGlyphs.d.mts +16 -0
  25. package/dist/icons/iconGlyphs.d.mts.map +1 -1
  26. package/dist/icons/iconGlyphs.mjs +16 -0
  27. package/dist/icons/iconGlyphs.mjs.map +1 -1
  28. package/dist/icons/icons.d.mts +16 -0
  29. package/dist/icons/icons.d.mts.map +1 -1
  30. package/dist/icons/icons.mjs +5 -1
  31. package/dist/icons/icons.mjs.map +1 -1
  32. package/dist/index.d.mts +8 -4
  33. package/dist/index.mjs +7 -3
  34. package/dist/lib/markdown.mjs +101 -5
  35. package/dist/lib/markdown.mjs.map +1 -1
  36. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlock.d.mts","names":[],"sources":["../../src/components/CodeBlock.tsx"],"mappings":";iBA4BiB;;EAEf;;EAEA;;wBAGc,YAAY,MAAM,YAAY,iCAAc,IAAA"}
1
+ {"version":3,"file":"CodeBlock.d.mts","names":[],"sources":["../../src/components/CodeBlock.tsx"],"mappings":";iBAciB;;EAEf;;EAEA;;wBAGc,YAAY,MAAM,YAAY,iCAAc,IAAA"}
@@ -1,30 +1,13 @@
1
1
  import { fontOf } from "../lib/fonts.mjs";
2
- import { Button } from "./Button.mjs";
3
- import { CheckIcon, CopyIcon } from "../icons/icons.mjs";
2
+ import { CopyButton } from "./CopyButton.mjs";
4
3
  import { View } from "react-native-unistyles/components/native/View";
5
4
  import { StyleSheet } from "react-native-unistyles";
6
5
  import { Text } from "react-native-unistyles/components/native/Text";
7
- import { useEffect, useState } from "react";
8
6
  import { jsx, jsxs } from "react/jsx-runtime";
9
7
  import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
10
- import { setStringAsync } from "expo-clipboard";
11
8
 
12
9
  //#region src/components/CodeBlock.tsx
13
- /** How long the control stays acknowledged before returning to its icon. */
14
- const FEEDBACK_MS = 1500;
15
- const LABELS = {
16
- idle: "copy",
17
- copied: "copied",
18
- failed: "copying failed"
19
- };
20
10
  function CodeBlock({ text, language }) {
21
- const [state, setState] = useState("idle");
22
- useEffect(() => {
23
- if (state === "idle") return void 0;
24
- const timer = setTimeout(() => setState("idle"), FEEDBACK_MS);
25
- return () => clearTimeout(timer);
26
- }, [state]);
27
- const label = LABELS[state];
28
11
  return /* @__PURE__ */ jsxs(View, {
29
12
  style: styles.block,
30
13
  accessibilityLabel: language === void 0 ? void 0 : `${language} code`,
@@ -37,21 +20,9 @@ function CodeBlock({ text, language }) {
37
20
  style: styles.code,
38
21
  children: text
39
22
  })
40
- }), /* @__PURE__ */ jsx(Button, {
41
- variant: "ghost",
42
- size: "icon",
43
- accessibilityLabel: label,
44
- onPress: () => {
45
- setStringAsync(text).then((saved) => setState(saved ? "copied" : "failed"), () => setState("failed"));
46
- },
47
- style: styles.copy,
48
- children: state === "copied" ? /* @__PURE__ */ jsx(CheckIcon, {
49
- size: 12,
50
- color: "green"
51
- }) : /* @__PURE__ */ jsx(CopyIcon, {
52
- size: 12,
53
- color: "faint"
54
- })
23
+ }), /* @__PURE__ */ jsx(CopyButton, {
24
+ text,
25
+ style: styles.copy
55
26
  })]
56
27
  });
57
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlock.mjs","names":["setStringAsync","useEffect","useState","StyleSheet","CheckIcon","CopyIcon","fontOf","Button","FEEDBACK_MS","CopyState","LABELS","Readonly","Record","idle","copied","failed","CodeBlockProps","text","language","CodeBlock","state","setState","undefined","timer","setTimeout","clearTimeout","label","styles","block","scroll","code","then","saved","copy","create","theme","borderRadius","DEFAULT","borderWidth","borderColor","colors","border","backgroundColor","deep","uni__dependencies","padding","spacing","paddingRight","color","fontFamily","mono","fontSize","sm","lineHeight","lineHeightRelaxed","position","top","right"],"sources":["../../src/components/CodeBlock.tsx"],"sourcesContent":["import { setStringAsync } from 'expo-clipboard';\nimport { useEffect, useState } from 'react';\nimport { ScrollView, Text, View } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { CheckIcon, CopyIcon } from '../icons/icons';\nimport { fontOf } from '../lib/fonts';\nimport { Button } from './Button';\n\n/*\n * A fenced code block: the source in selectable mono type that scrolls\n * sideways rather than wrapping, and a copy button that says when it worked.\n *\n * ponytail: no syntax colouring and no mermaid diagrams yet; a mermaid fence\n * shows its source, which is still the whole content.\n */\n\n/** How long the control stays acknowledged before returning to its icon. */\nconst FEEDBACK_MS = 1500;\n\ntype CopyState = 'idle' | 'copied' | 'failed';\n\nconst LABELS: Readonly<Record<CopyState, string>> = {\n idle: 'copy',\n copied: 'copied',\n failed: 'copying failed',\n};\n\nexport interface CodeBlockProps {\n /** The block's source, without the fence lines. */\n text: string;\n /** The fence's language word, such as `ts`; the web reads it from a `language-*` class. */\n language?: string;\n}\n\nexport function CodeBlock({ text, language }: CodeBlockProps) {\n const [state, setState] = useState<CopyState>('idle');\n\n useEffect(() => {\n if (state === 'idle') return undefined;\n const timer = setTimeout(() => setState('idle'), FEEDBACK_MS);\n return () => clearTimeout(timer);\n }, [state]);\n\n const label = LABELS[state];\n return (\n <View style={styles.block} accessibilityLabel={language === undefined ? undefined : `${language} code`}>\n <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.scroll}>\n <Text selectable style={styles.code}>\n {text}\n </Text>\n </ScrollView>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n accessibilityLabel={label}\n onPress={() => {\n setStringAsync(text).then(\n (saved) => setState(saved ? 'copied' : 'failed'),\n () => setState('failed'),\n );\n }}\n style={styles.copy}\n >\n {state === 'copied' ? <CheckIcon size={12} color=\"green\" /> : <CopyIcon size={12} color=\"faint\" />}\n </Button>\n </View>\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n block: {\n borderRadius: theme.borderRadius.DEFAULT,\n borderWidth: 1,\n borderColor: theme.colors.border,\n backgroundColor: theme.colors.deep,\n },\n // The right padding keeps the first line clear of the copy button.\n scroll: { padding: theme.spacing(2.5), paddingRight: theme.spacing(9) },\n code: {\n color: theme.colors.text,\n fontFamily: fontOf(theme.fontFamily.mono),\n fontSize: theme.fontSize.sm,\n lineHeight: theme.lineHeightRelaxed.sm,\n },\n copy: { position: 'absolute', top: theme.spacing(1.5), right: theme.spacing(1.5) },\n}));\n"],"mappings":";;;;;;;;;;;;;AAkBA,MAAMQ,cAAc;AAIpB,MAAME,SAA8C;CAClDG,MAAM;CACNC,QAAQ;CACRC,QAAQ;AACV;AASA,SAAgBI,UAAU,EAAEF,MAAMC,YAA4B;CAC5D,MAAM,CAACE,OAAOC,YAAYnB,SAAoB,MAAM;CAEpDD,gBAAgB;EACd,IAAImB,UAAU,QAAQ,OAAOE;EAC7B,MAAMC,QAAQC,iBAAiBH,SAAS,MAAM,GAAGb,WAAW;EAC5D,aAAaiB,aAAaF,KAAK;CACjC,GAAG,CAACH,KAAK,CAAC;CAEV,MAAMM,QAAQhB,OAAOU;CACrB,OACE,qBAAC,MAAD;EAAM,OAAOO,OAAOC;EAAO,oBAAoBV,aAAaI,SAAYA,SAAY,GAAGJ,SAAQ;YAA/F,CACE,oBAAC,YAAD;GAAY;GAAW,gCAAgC;GAAO,uBAAuBS,OAAOE;aAC1F,oBAAC,MAAD;IAAM;IAAW,OAAOF,OAAOG;cAC5Bb;GACG;EACI,IACZ,oBAAC,QAAD;GACE,SAAQ;GACR,MAAK;GACL,oBAAoBS;GACpB,eAAe;IACb1B,eAAeiB,IAAI,CAAC,CAACc,MAClBC,UAAUX,SAASW,QAAQ,WAAW,QAAQ,SACzCX,SAAS,QAAQ,CACzB;GACF;GACA,OAAOM,OAAOM;aAEbb,UAAU,WAAW,oBAAC,WAAD;IAAW,MAAM;IAAI,OAAM;GAAO,KAAM,oBAAC,UAAD;IAAU,MAAM;IAAI,OAAM;GAAO;EACzF,EACJ;;AAEV;AAEA,MAAMO,SAASxB,WAAW+B,QAAQC,WAAW;CAC3CP,OAAO;EACLQ,cAAcD,MAAMC,aAAaC;EACjCC,aAAa;EACbC,aAAaJ,MAAMK,OAAOC;EAC1BC,iBAAiBP,MAAMK,OAAOG;EAAIC,mBAAA,CAAA,CAAA;CACpC;CAEAf,QAAQ;EAAEgB,SAASV,MAAMW,QAAQ,GAAG;EAAGC,cAAcZ,MAAMW,QAAQ,CAAC;EAACF,mBAAA,CAAA,CAAA;CAAC;CACtEd,MAAM;EACJkB,OAAOb,MAAMK,OAAOvB;EACpBgC,YAAY3C,OAAO6B,MAAMc,WAAWC,IAAI;EACxCC,UAAUhB,MAAMgB,SAASC;EACzBC,YAAYlB,MAAMmB,kBAAkBF;EAAER,mBAAA,CAAA,CAAA;CACxC;CACAX,MAAM;EAAEsB,UAAU;EAAYC,KAAKrB,MAAMW,QAAQ,GAAG;EAAGW,OAAOtB,MAAMW,QAAQ,GAAG;EAACF,mBAAA,CAAA,CAAA;CAAC;AACnF,EAAE"}
1
+ {"version":3,"file":"CodeBlock.mjs","names":["StyleSheet","fontOf","CopyButton","CodeBlockProps","text","language","CodeBlock","styles","block","undefined","scroll","code","copy","create","theme","borderRadius","DEFAULT","borderWidth","borderColor","colors","border","backgroundColor","deep","uni__dependencies","padding","spacing","paddingRight","color","fontFamily","mono","fontSize","sm","lineHeight","lineHeightRelaxed","position","top","right"],"sources":["../../src/components/CodeBlock.tsx"],"sourcesContent":["import { ScrollView, Text, View } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { fontOf } from '../lib/fonts';\nimport { CopyButton } from './CopyButton';\n\n/*\n * A fenced code block: the source in selectable mono type that scrolls\n * sideways rather than wrapping, and a copy button that says when it worked.\n *\n * ponytail: no syntax colouring and no mermaid diagrams yet; a mermaid fence\n * shows its source, which is still the whole content.\n */\n\nexport interface CodeBlockProps {\n /** The block's source, without the fence lines. */\n text: string;\n /** The fence's language word, such as `ts`; the web reads it from a `language-*` class. */\n language?: string;\n}\n\nexport function CodeBlock({ text, language }: CodeBlockProps) {\n return (\n <View style={styles.block} accessibilityLabel={language === undefined ? undefined : `${language} code`}>\n <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.scroll}>\n <Text selectable style={styles.code}>\n {text}\n </Text>\n </ScrollView>\n <CopyButton text={text} style={styles.copy} />\n </View>\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n block: {\n borderRadius: theme.borderRadius.DEFAULT,\n borderWidth: 1,\n borderColor: theme.colors.border,\n backgroundColor: theme.colors.deep,\n },\n // The right padding keeps the first line clear of the copy button.\n scroll: { padding: theme.spacing(2.5), paddingRight: theme.spacing(9) },\n code: {\n color: theme.colors.text,\n fontFamily: fontOf(theme.fontFamily.mono),\n fontSize: theme.fontSize.sm,\n lineHeight: theme.lineHeightRelaxed.sm,\n },\n copy: { position: 'absolute', top: theme.spacing(1.5), right: theme.spacing(1.5) },\n}));\n"],"mappings":";;;;;;;;;AAqBA,SAAgBM,UAAU,EAAEF,MAAMC,YAA4B;CAC5D,OACE,qBAAC,MAAD;EAAM,OAAOE,OAAOC;EAAO,oBAAoBH,aAAaI,SAAYA,SAAY,GAAGJ,SAAQ;YAA/F,CACE,oBAAC,YAAD;GAAY;GAAW,gCAAgC;GAAO,uBAAuBE,OAAOG;aAC1F,oBAAC,MAAD;IAAM;IAAW,OAAOH,OAAOI;cAC5BP;GACG;EACI,IACZ,oBAAC,YAAD;GAAkBA;GAAM,OAAOG,OAAOK;EAAK,EACvC;;AAEV;AAEA,MAAML,SAASP,WAAWa,QAAQC,WAAW;CAC3CN,OAAO;EACLO,cAAcD,MAAMC,aAAaC;EACjCC,aAAa;EACbC,aAAaJ,MAAMK,OAAOC;EAC1BC,iBAAiBP,MAAMK,OAAOG;EAAIC,mBAAA,CAAA,CAAA;CACpC;CAEAb,QAAQ;EAAEc,SAASV,MAAMW,QAAQ,GAAG;EAAGC,cAAcZ,MAAMW,QAAQ,CAAC;EAACF,mBAAA,CAAA,CAAA;CAAC;CACtEZ,MAAM;EACJgB,OAAOb,MAAMK,OAAOf;EACpBwB,YAAY3B,OAAOa,MAAMc,WAAWC,IAAI;EACxCC,UAAUhB,MAAMgB,SAASC;EACzBC,YAAYlB,MAAMmB,kBAAkBF;EAAER,mBAAA,CAAA,CAAA;CACxC;CACAX,MAAM;EAAEsB,UAAU;EAAYC,KAAKrB,MAAMW,QAAQ,GAAG;EAAGW,OAAOtB,MAAMW,QAAQ,GAAG;EAACF,mBAAA,CAAA,CAAA;CAAC;AACnF,EAAE"}
@@ -0,0 +1,13 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ //#region src/components/CopyButton.d.ts
3
+ export interface CopyButtonProps {
4
+ /** The text the press puts on the clipboard. */
5
+ text: string;
6
+ /** What is copied, announced before the state, such as `path`. */
7
+ label?: string;
8
+ style?: StyleProp<ViewStyle>;
9
+ }
10
+ /** A quiet icon button that copies text and says, briefly, whether it worked. */
11
+ export declare function CopyButton({ text, label, style }: CopyButtonProps): import("react").JSX.Element;
12
+ //#endregion
13
+ //# sourceMappingURL=CopyButton.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyButton.d.mts","names":[],"sources":["../../src/components/CopyButton.tsx"],"mappings":";;iBAkBiB;;EAEf;;EAEA;EACA,QAAQ,UAAU;;;wBAIJ,aAAa,MAAM,OAAO,SAAS,kCAAe,IAAA"}
@@ -0,0 +1,43 @@
1
+ import { Button } from "./Button.mjs";
2
+ import { CheckIcon, CopyIcon } from "../icons/icons.mjs";
3
+ import { useEffect, useState } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { setStringAsync } from "expo-clipboard";
6
+
7
+ //#region src/components/CopyButton.tsx
8
+ /** How long the control stays acknowledged before returning to its icon. */
9
+ const FEEDBACK_MS = 1500;
10
+ const LABELS = {
11
+ idle: "copy",
12
+ copied: "copied",
13
+ failed: "copying failed"
14
+ };
15
+ /** A quiet icon button that copies text and says, briefly, whether it worked. */
16
+ function CopyButton({ text, label, style }) {
17
+ const [state, setState] = useState("idle");
18
+ useEffect(() => {
19
+ if (state === "idle") return void 0;
20
+ const timer = setTimeout(() => setState("idle"), FEEDBACK_MS);
21
+ return () => clearTimeout(timer);
22
+ }, [state]);
23
+ return /* @__PURE__ */ jsx(Button, {
24
+ variant: "ghost",
25
+ size: "icon",
26
+ accessibilityLabel: label === void 0 ? LABELS[state] : `${LABELS[state]} ${label}`,
27
+ onPress: () => {
28
+ setStringAsync(text).then((saved) => setState(saved ? "copied" : "failed"), () => setState("failed"));
29
+ },
30
+ style,
31
+ children: state === "copied" ? /* @__PURE__ */ jsx(CheckIcon, {
32
+ size: 12,
33
+ color: "green"
34
+ }) : /* @__PURE__ */ jsx(CopyIcon, {
35
+ size: 12,
36
+ color: "faint"
37
+ })
38
+ });
39
+ }
40
+
41
+ //#endregion
42
+ export { CopyButton };
43
+ //# sourceMappingURL=CopyButton.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyButton.mjs","names":["setStringAsync","useEffect","useState","StyleProp","ViewStyle","CheckIcon","CopyIcon","Button","FEEDBACK_MS","CopyState","LABELS","Readonly","Record","idle","copied","failed","CopyButtonProps","text","label","style","CopyButton","state","setState","undefined","timer","setTimeout","clearTimeout","then","saved"],"sources":["../../src/components/CopyButton.tsx"],"sourcesContent":["import { setStringAsync } from 'expo-clipboard';\nimport { useEffect, useState } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\n\nimport { CheckIcon, CopyIcon } from '../icons/icons';\nimport { Button } from './Button';\n\n/** How long the control stays acknowledged before returning to its icon. */\nconst FEEDBACK_MS = 1500;\n\ntype CopyState = 'idle' | 'copied' | 'failed';\n\nconst LABELS: Readonly<Record<CopyState, string>> = {\n idle: 'copy',\n copied: 'copied',\n failed: 'copying failed',\n};\n\nexport interface CopyButtonProps {\n /** The text the press puts on the clipboard. */\n text: string;\n /** What is copied, announced before the state, such as `path`. */\n label?: string;\n style?: StyleProp<ViewStyle>;\n}\n\n/** A quiet icon button that copies text and says, briefly, whether it worked. */\nexport function CopyButton({ text, label, style }: CopyButtonProps) {\n const [state, setState] = useState<CopyState>('idle');\n\n useEffect(() => {\n if (state === 'idle') return undefined;\n const timer = setTimeout(() => setState('idle'), FEEDBACK_MS);\n return () => clearTimeout(timer);\n }, [state]);\n\n return (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n accessibilityLabel={label === undefined ? LABELS[state] : `${LABELS[state]} ${label}`}\n onPress={() => {\n setStringAsync(text).then(\n (saved) => setState(saved ? 'copied' : 'failed'),\n () => setState('failed'),\n );\n }}\n style={style}\n >\n {state === 'copied' ? <CheckIcon size={12} color=\"green\" /> : <CopyIcon size={12} color=\"faint\" />}\n </Button>\n );\n}\n"],"mappings":";;;;;;;;AAQA,MAAMQ,cAAc;AAIpB,MAAME,SAA8C;CAClDG,MAAM;CACNC,QAAQ;CACRC,QAAQ;AACV;;AAWA,SAAgBK,WAAW,EAAEH,MAAMC,OAAOC,SAA0B;CAClE,MAAM,CAACE,OAAOC,YAAYpB,SAAoB,MAAM;CAEpDD,gBAAgB;EACd,IAAIoB,UAAU,QAAQ,OAAOE;EAC7B,MAAMC,QAAQC,iBAAiBH,SAAS,MAAM,GAAGd,WAAW;EAC5D,aAAakB,aAAaF,KAAK;CACjC,GAAG,CAACH,KAAK,CAAC;CAEV,OACE,oBAAC,QAAD;EACE,SAAQ;EACR,MAAK;EACL,oBAAoBH,UAAUK,SAAYb,OAAOW,SAAS,GAAGX,OAAOW,OAAM,GAAIH;EAC9E,eAAe;GACblB,eAAeiB,IAAI,CAAC,CAACU,MAClBC,UAAUN,SAASM,QAAQ,WAAW,QAAQ,SACzCN,SAAS,QAAQ,CACzB;EACF;EACOH;YAENE,UAAU,WAAW,oBAAC,WAAD;GAAW,MAAM;GAAI,OAAM;EAAO,KAAM,oBAAC,UAAD;GAAU,MAAM;GAAI,OAAM;EAAO;CACzF;AAEZ"}
@@ -0,0 +1,13 @@
1
+ import { PresentedLine } from "@agimon-ai/po-web-components/text";
2
+ import { ViewProps } from "react-native";
3
+ //#region src/components/HashlineLines.d.ts
4
+ export interface HashlineLinesProps extends Omit<ViewProps, 'children'> {
5
+ lines: readonly PresentedLine[];
6
+ /** Width of the line-number column, in characters, from `hashlineBody`. */
7
+ gutter: number;
8
+ /** The file a body of bare anchored lines came from, for highlighting. */
9
+ path?: string | undefined;
10
+ }
11
+ export declare function HashlineLines({ lines, gutter, path, style, ...props }: HashlineLinesProps): import("react").JSX.Element;
12
+ //#endregion
13
+ //# sourceMappingURL=HashlineLines.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HashlineLines.d.mts","names":[],"sources":["../../src/components/HashlineLines.tsx"],"mappings":";;;iBA0BiB,2BAA2B,KAAK;EAC/C,gBAAgB;;EAEhB;;EAEA;;wBAsCc,gBAAgB,OAAO,QAAQ,MAAM,UAAU,SAAS,qCAAkB,IAAA"}
@@ -0,0 +1,137 @@
1
+ import { fontOf } from "../lib/fonts.mjs";
2
+ import { SyntaxLine } from "./SyntaxLine.mjs";
3
+ import { detectGrammar, hashlineGroups, hashlineGroupsKey, highlightToLines } from "@agimon-ai/po-web-components/text";
4
+ import { View } from "react-native-unistyles/components/native/View";
5
+ import { StyleSheet } from "react-native-unistyles";
6
+ import { Text } from "react-native-unistyles/components/native/Text";
7
+ import { useEffect, useMemo, useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+
10
+ //#region src/components/HashlineLines.tsx
11
+ const NO_SPANS = /* @__PURE__ */ new Map();
12
+ /** Spans per presented-line index, kept with the body they belong to, as the web's hook. */
13
+ function useHashlineSpans(lines, path) {
14
+ const groups = useMemo(() => hashlineGroups(lines, path), [lines, path]);
15
+ const key = hashlineGroupsKey(groups);
16
+ const [done, setDone] = useState();
17
+ useEffect(() => {
18
+ let live = true;
19
+ const next = /* @__PURE__ */ new Map();
20
+ const work = groups.map(async (group) => {
21
+ const grammar = detectGrammar({
22
+ path: group.path,
23
+ text: group.text
24
+ });
25
+ if (grammar === void 0) return;
26
+ const highlighted = await highlightToLines(group.text, grammar);
27
+ if (highlighted === void 0) return;
28
+ for (const [offset, index] of group.indices.entries()) {
29
+ const line = highlighted[offset];
30
+ if (line !== void 0) next.set(index, line);
31
+ }
32
+ });
33
+ Promise.allSettled(work).then(() => {
34
+ if (live && next.size > 0) setDone({
35
+ key,
36
+ spans: next
37
+ });
38
+ });
39
+ return () => {
40
+ live = false;
41
+ };
42
+ }, [key]);
43
+ return done?.key === key ? done.spans : NO_SPANS;
44
+ }
45
+ function HashlineLines({ lines, gutter, path, style, ...props }) {
46
+ const spans = useHashlineSpans(lines, path);
47
+ return /* @__PURE__ */ jsx(View, {
48
+ style,
49
+ ...props,
50
+ children: lines.map((line, index) => {
51
+ if (line.type === "file") return /* @__PURE__ */ jsx(Text, {
52
+ numberOfLines: 1,
53
+ style: [
54
+ styles.mono,
55
+ styles.faint,
56
+ index > 0 && styles.fileGap
57
+ ],
58
+ children: line.path
59
+ }, index);
60
+ if (line.type === "plain") return /* @__PURE__ */ jsx(Text, {
61
+ selectable: true,
62
+ style: [styles.mono, styles.dim],
63
+ children: line.text
64
+ }, index);
65
+ const { value } = line;
66
+ const match = value.marker === "match";
67
+ return /* @__PURE__ */ jsxs(View, {
68
+ style: styles.row,
69
+ children: [
70
+ value.marker !== void 0 ? /* @__PURE__ */ jsx(Text, {
71
+ style: [styles.mono, match ? styles.marker : styles.faint],
72
+ children: match ? ">>" : " "
73
+ }) : null,
74
+ /* @__PURE__ */ jsx(Text, {
75
+ style: [styles.mono, styles.faint],
76
+ children: String(value.line).padStart(gutter)
77
+ }),
78
+ /* @__PURE__ */ jsx(SyntaxLine, {
79
+ selectable: true,
80
+ spans: spans.get(index),
81
+ text: value.content,
82
+ style: [
83
+ styles.mono,
84
+ styles.content,
85
+ match ? styles.hi : styles.text
86
+ ]
87
+ })
88
+ ]
89
+ }, index);
90
+ })
91
+ });
92
+ }
93
+ const styles = StyleSheet.create((theme) => ({
94
+ row: {
95
+ flexDirection: "row",
96
+ gap: theme.spacing(2),
97
+ uni__dependencies: [0]
98
+ },
99
+ fileGap: {
100
+ marginTop: theme.spacing(1),
101
+ uni__dependencies: [0]
102
+ },
103
+ mono: {
104
+ fontFamily: fontOf(theme.fontFamily.mono),
105
+ fontSize: theme.fontSize.sm,
106
+ lineHeight: theme.lineHeightRelaxed.sm,
107
+ uni__dependencies: [0]
108
+ },
109
+ content: {
110
+ flex: 1,
111
+ minWidth: 0
112
+ },
113
+ marker: {
114
+ color: theme.colors.blue,
115
+ uni__dependencies: [0]
116
+ },
117
+ faint: {
118
+ color: theme.colors.faint,
119
+ uni__dependencies: [0]
120
+ },
121
+ dim: {
122
+ color: theme.colors.dim,
123
+ uni__dependencies: [0]
124
+ },
125
+ text: {
126
+ color: theme.colors.text,
127
+ uni__dependencies: [0]
128
+ },
129
+ hi: {
130
+ color: theme.colors.hi,
131
+ uni__dependencies: [0]
132
+ }
133
+ }));
134
+
135
+ //#endregion
136
+ export { HashlineLines };
137
+ //# sourceMappingURL=HashlineLines.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HashlineLines.mjs","names":["detectGrammar","hashlineGroups","hashlineGroupsKey","highlightToLines","PresentedLine","SyntaxSpan","useEffect","useMemo","useState","ViewProps","StyleSheet","fontOf","SyntaxLine","HashlineLinesProps","Omit","lines","gutter","path","NO_SPANS","ReadonlyMap","Map","useHashlineSpans","groups","key","done","setDone","spans","live","next","work","map","group","grammar","text","undefined","highlighted","offset","index","indices","entries","line","set","Promise","allSettled","then","size","HashlineLines","style","props","type","styles","mono","faint","fileGap","dim","value","match","marker","row","String","padStart","get","content","hi","create","theme","flexDirection","gap","spacing","uni__dependencies","marginTop","fontFamily","fontSize","sm","lineHeight","lineHeightRelaxed","flex","minWidth","color","colors","blue"],"sources":["../../src/components/HashlineLines.tsx"],"sourcesContent":["import {\n detectGrammar,\n hashlineGroups,\n hashlineGroupsKey,\n highlightToLines,\n type PresentedLine,\n type SyntaxSpan,\n} from '@agimon-ai/po-web-components/text';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Text, View, type ViewProps } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { fontOf } from '../lib/fonts';\nimport { SyntaxLine } from './SyntaxLine';\n\n/*\n * The anchored body shared by hashline results, as the web's HashlineLines: a\n * file heading per group, a right-aligned line-number gutter, and a `>>`\n * marker on grep matches. Mono type makes a padded number the gutter, so the\n * column needs no measuring.\n *\n * The anchored lines are coloured with the web's grammars and palette when the\n * body names a file, or when `path` names one. Colour arrives late and never\n * blocks the text: an unhighlighted body is the same body without the colours.\n */\n\nexport interface HashlineLinesProps extends Omit<ViewProps, 'children'> {\n lines: readonly PresentedLine[];\n /** Width of the line-number column, in characters, from `hashlineBody`. */\n gutter: number;\n /** The file a body of bare anchored lines came from, for highlighting. */\n path?: string | undefined;\n}\n\nconst NO_SPANS: ReadonlyMap<number, readonly SyntaxSpan[]> = new Map();\n\n/** Spans per presented-line index, kept with the body they belong to, as the web's hook. */\nfunction useHashlineSpans(lines: readonly PresentedLine[], path: string | undefined) {\n const groups = useMemo(() => hashlineGroups(lines, path), [lines, path]);\n const key = hashlineGroupsKey(groups);\n const [done, setDone] = useState<{ key: string; spans: ReadonlyMap<number, readonly SyntaxSpan[]> }>();\n\n useEffect(() => {\n let live = true;\n const next = new Map<number, readonly SyntaxSpan[]>();\n const work = groups.map(async (group) => {\n const grammar = detectGrammar({ path: group.path, text: group.text });\n if (grammar === undefined) return;\n const highlighted = await highlightToLines(group.text, grammar);\n if (highlighted === undefined) return;\n for (const [offset, index] of group.indices.entries()) {\n const line = highlighted[offset];\n if (line !== undefined) next.set(index, line);\n }\n });\n // A grammar that fails to load leaves its run plain: colour is decoration.\n void Promise.allSettled(work).then(() => {\n if (live && next.size > 0) setDone({ key, spans: next });\n });\n return () => {\n live = false;\n };\n // The groups are rebuilt with the lines; their key is what actually changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key]);\n\n return done?.key === key ? done.spans : NO_SPANS;\n}\n\nexport function HashlineLines({ lines, gutter, path, style, ...props }: HashlineLinesProps) {\n const spans = useHashlineSpans(lines, path);\n return (\n <View style={style} {...props}>\n {lines.map((line, index) => {\n if (line.type === 'file') {\n return (\n <Text key={index} numberOfLines={1} style={[styles.mono, styles.faint, index > 0 && styles.fileGap]}>\n {line.path}\n </Text>\n );\n }\n if (line.type === 'plain') {\n return (\n <Text key={index} selectable style={[styles.mono, styles.dim]}>\n {line.text}\n </Text>\n );\n }\n const { value } = line;\n const match = value.marker === 'match';\n return (\n <View key={index} style={styles.row}>\n {value.marker !== undefined ? (\n <Text style={[styles.mono, match ? styles.marker : styles.faint]}>{match ? '>>' : ' '}</Text>\n ) : null}\n <Text style={[styles.mono, styles.faint]}>{String(value.line).padStart(gutter)}</Text>\n <SyntaxLine\n selectable\n spans={spans.get(index)}\n text={value.content}\n style={[styles.mono, styles.content, match ? styles.hi : styles.text]}\n />\n </View>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n row: { flexDirection: 'row', gap: theme.spacing(2) },\n fileGap: { marginTop: theme.spacing(1) },\n mono: {\n fontFamily: fontOf(theme.fontFamily.mono),\n fontSize: theme.fontSize.sm,\n lineHeight: theme.lineHeightRelaxed.sm,\n },\n content: { flex: 1, minWidth: 0 },\n marker: { color: theme.colors.blue },\n faint: { color: theme.colors.faint },\n dim: { color: theme.colors.dim },\n text: { color: theme.colors.text },\n hi: { color: theme.colors.hi },\n}));\n"],"mappings":";;;;;;;;;;AAkCA,MAAMkB,2BAAuD,IAAIE,IAAI;;AAGrE,SAASC,iBAAiBN,OAAiCE,MAA0B;CACnF,MAAMK,SAASf,cAAcN,eAAec,OAAOE,IAAI,GAAG,CAACF,OAAOE,IAAI,CAAC;CACvE,MAAMM,MAAMrB,kBAAkBoB,MAAM;CACpC,MAAM,CAACE,MAAMC,WAAWjB,SAA6E;CAErGF,gBAAgB;EACd,IAAIqB,OAAO;EACX,MAAMC,uBAAO,IAAIR,IAAmC;EACpD,MAAMS,OAAOP,OAAOQ,IAAI,OAAOC,UAAU;GACvC,MAAMC,UAAUhC,cAAc;IAAEiB,MAAMc,MAAMd;IAAMgB,MAAMF,MAAME;GAAK,CAAC;GACpE,IAAID,YAAYE,QAAW;GAC3B,MAAMC,cAAc,MAAMhC,iBAAiB4B,MAAME,MAAMD,OAAO;GAC9D,IAAIG,gBAAgBD,QAAW;GAC/B,KAAK,MAAM,CAACE,QAAQC,UAAUN,MAAMO,QAAQC,QAAQ,GAAG;IACrD,MAAMC,OAAOL,YAAYC;IACzB,IAAII,SAASN,QAAWN,KAAKa,IAAIJ,OAAOG,IAAI;GAC9C;EACF,CAAC;EAED,AAAKE,QAAQC,WAAWd,IAAI,CAAC,CAACe,WAAW;GACvC,IAAIjB,QAAQC,KAAKiB,OAAO,GAAGpB,QAAQ;IAAEF;IAAKG,OAAOE;GAAK,CAAC;EACzD,CAAC;EACD,aAAa;GACXD,OAAO;EACT;CAGF,GAAG,CAACJ,GAAG,CAAC;CAER,OAAOC,MAAMD,QAAQA,MAAMC,KAAKE,QAAQR;AAC1C;AAEA,SAAgB4B,cAAc,EAAE/B,OAAOC,QAAQC,MAAM8B,OAAO,GAAGC,SAA6B;CAC1F,MAAMtB,QAAQL,iBAAiBN,OAAOE,IAAI;CAC1C,OACE,oBAAC,MAAD;EAAa8B;EAAO,GAAIC;YACrBjC,MAAMe,KAAKU,MAAMH,UAAU;GAC1B,IAAIG,KAAKS,SAAS,QAChB,OACE,oBAAC,MAAD;IAAkB,eAAe;IAAG,OAAO;KAACC,OAAOC;KAAMD,OAAOE;KAAOf,QAAQ,KAAKa,OAAOG;IAAO;cAC/Fb,KAAKvB;GACF,GAFKoB,KAEL;GAGV,IAAIG,KAAKS,SAAS,SAChB,OACE,oBAAC,MAAD;IAAkB;IAAW,OAAO,CAACC,OAAOC,MAAMD,OAAOI,GAAG;cACzDd,KAAKP;GACF,GAFKI,KAEL;GAGV,MAAM,EAAEkB,UAAUf;GAClB,MAAMgB,QAAQD,MAAME,WAAW;GAC/B,OACE,qBAAC,MAAD;IAAkB,OAAOP,OAAOQ;cAAhC;KACGH,MAAME,WAAWvB,SAChB,oBAAC,MAAD;MAAM,OAAO,CAACgB,OAAOC,MAAMK,QAAQN,OAAOO,SAASP,OAAOE,KAAK;gBAAII,QAAQ,OAAO;KAAW,KAC3F;KACJ,oBAAC,MAAD;MAAM,OAAO,CAACN,OAAOC,MAAMD,OAAOE,KAAK;gBAAIO,OAAOJ,MAAMf,IAAI,CAAC,CAACoB,SAAS5C,MAAM;KAAQ;KACrF,oBAAC,YAAD;MACE;MACA,OAAOU,MAAMmC,IAAIxB,KAAK;MACtB,MAAMkB,MAAMO;MACZ,OAAO;OAACZ,OAAOC;OAAMD,OAAOY;OAASN,QAAQN,OAAOa,KAAKb,OAAOjB;MAAI;KAAE;IAEpE;MAXKI,KAWL;EAEV,CAAC;CACG;AAEV;AAEA,MAAMa,SAASxC,WAAWsD,QAAQC,WAAW;CAC3CP,KAAK;EAAEQ,eAAe;EAAOC,KAAKF,MAAMG,QAAQ,CAAC;EAACC,mBAAA,CAAA,CAAA;CAAC;CACnDhB,SAAS;EAAEiB,WAAWL,MAAMG,QAAQ,CAAC;EAACC,mBAAA,CAAA,CAAA;CAAC;CACvClB,MAAM;EACJoB,YAAY5D,OAAOsD,MAAMM,WAAWpB,IAAI;EACxCqB,UAAUP,MAAMO,SAASC;EACzBC,YAAYT,MAAMU,kBAAkBF;EAAEJ,mBAAA,CAAA,CAAA;CACxC;CACAP,SAAS;EAAEc,MAAM;EAAGC,UAAU;CAAE;CAChCpB,QAAQ;EAAEqB,OAAOb,MAAMc,OAAOC;EAAIX,mBAAA,CAAA,CAAA;CAAC;CACnCjB,OAAO;EAAE0B,OAAOb,MAAMc,OAAO3B;EAAKiB,mBAAA,CAAA,CAAA;CAAC;CACnCf,KAAK;EAAEwB,OAAOb,MAAMc,OAAOzB;EAAGe,mBAAA,CAAA,CAAA;CAAC;CAC/BpC,MAAM;EAAE6C,OAAOb,MAAMc,OAAO9C;EAAIoC,mBAAA,CAAA,CAAA;CAAC;CACjCN,IAAI;EAAEe,OAAOb,MAAMc,OAAOhB;EAAEM,mBAAA,CAAA,CAAA;CAAC;AAC/B,EAAE"}
@@ -5,11 +5,18 @@
5
5
  * contract as the web Markdown's handler.
6
6
  */
7
7
  export type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;
8
+ /** Loads a relative image path from the host; the same contract as the web Markdown's `loadImage`. */
9
+ export type MarkdownImageLoader = (path: string) => Promise<{
10
+ url: string;
11
+ dispose(): void;
12
+ }>;
8
13
  export interface MarkdownProps {
9
14
  text: string;
10
15
  onFileLink?: FileLinkHandler;
16
+ /** Loads relative image paths; without it they show as unavailable. */
17
+ loadImage?: MarkdownImageLoader;
11
18
  }
12
19
  /** Markdown in Po's shared presentation, parsed by the package's own small parser. */
13
- export declare function Markdown({ text, onFileLink }: MarkdownProps): import("react").JSX.Element;
20
+ export declare function Markdown({ text, onFileLink, loadImage }: MarkdownProps): import("react").JSX.Element;
14
21
  //#endregion
15
22
  //# sourceMappingURL=Markdown.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.d.mts","names":[],"sources":["../../src/components/Markdown.tsx"],"mappings":";;;;;;YAqBY,mBAAmB,cAAc;iBAgH5B;EACf;EACA,aAAa;;;wBAIC,WAAW,MAAM,cAAc,gCAAa,IAAA"}
1
+ {"version":3,"file":"Markdown.d.mts","names":[],"sources":["../../src/components/Markdown.tsx"],"mappings":";;;;;;YAoBY,mBAAmB,cAAc;;YAMjC,uBAAuB,iBAAiB;EAAU;EAAa;;iBA0O1D;EACf;EACA,aAAa;;EAEb,YAAY;;;wBAIE,WAAW,MAAM,YAAY,aAAa,gCAAa,IAAA"}
@@ -5,13 +5,20 @@ import { View } from "react-native-unistyles/components/native/View";
5
5
  import { StyleSheet } from "react-native-unistyles";
6
6
  import { Linking } from "react-native";
7
7
  import { Text } from "react-native-unistyles/components/native/Text";
8
- import { createContext, useContext, useMemo } from "react";
8
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
11
+ import { Image } from "react-native-unistyles/components/native/Image";
10
12
 
11
13
  //#region src/components/Markdown.tsx
12
14
  /** Passed by context so the inline renderers stay module functions. */
13
15
  const FileLinkContext = createContext(void 0);
16
+ const ImageContext = createContext(void 0);
14
17
  const URL_OR_ANCHOR = /^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i;
18
+ /** Sources an Image may load as written; any other scheme stays its alt text. */
19
+ const DIRECT_IMAGE = /^(?:https?:|data:image\/)/i;
20
+ /** Until the image reports its size, it holds a landscape box rather than collapsing. */
21
+ const PLACEHOLDER_RATIO = 16 / 9;
15
22
  function decoded(href) {
16
23
  try {
17
24
  return decodeURIComponent(href);
@@ -49,6 +56,72 @@ function InlineLink({ href, children }) {
49
56
  children
50
57
  });
51
58
  }
59
+ /** One image: a file path through the host's loader, or a web or data address as written. */
60
+ function MarkdownImage({ src, alt }) {
61
+ const loader = useContext(ImageContext);
62
+ const path = URL_OR_ANCHOR.test(src) ? void 0 : decoded(src.split(/[?#]/u, 1)[0]) ?? "";
63
+ const [loaded, setLoaded] = useState();
64
+ const [ratio, setRatio] = useState(PLACEHOLDER_RATIO);
65
+ useEffect(() => {
66
+ if (loader === void 0 || path === void 0) return;
67
+ let active = true;
68
+ let asset;
69
+ Promise.resolve().then(() => {
70
+ if (path === "") throw new Error("Invalid image path.");
71
+ return loader(path);
72
+ }).then((result) => {
73
+ if (!active) return result.dispose();
74
+ asset = result;
75
+ setLoaded({
76
+ path,
77
+ url: result.url
78
+ });
79
+ }).catch(() => {
80
+ if (active) setLoaded({
81
+ path,
82
+ failed: true
83
+ });
84
+ });
85
+ return () => {
86
+ active = false;
87
+ asset?.dispose();
88
+ };
89
+ }, [path, loader]);
90
+ const current = loaded?.path === path ? loaded : void 0;
91
+ const uri = path === void 0 ? DIRECT_IMAGE.test(src) ? src : void 0 : current?.url;
92
+ if (uri === void 0 || current?.failed) {
93
+ const waiting = path !== void 0 && loader !== void 0 && current === void 0;
94
+ return /* @__PURE__ */ jsxs(Text, {
95
+ style: [styles.prose, styles.imageState],
96
+ accessibilityRole: waiting ? "text" : "alert",
97
+ children: [
98
+ alt || path || "Image",
99
+ " (",
100
+ waiting ? "loading…" : "unavailable",
101
+ ")"
102
+ ]
103
+ });
104
+ }
105
+ return /* @__PURE__ */ jsx(Image, {
106
+ source: { uri },
107
+ accessibilityLabel: alt || "Image",
108
+ accessibilityIgnoresInvertColors: true,
109
+ resizeMode: "contain",
110
+ onLoad: ({ nativeEvent }) => {
111
+ const fit = (width, height) => {
112
+ if (width > 0 && height > 0) setRatio(width / height);
113
+ };
114
+ const size = nativeEvent?.source;
115
+ if (size !== void 0) fit(size.width, size.height);
116
+ else Image.getSize(uri, fit, () => void 0);
117
+ },
118
+ onError: () => setLoaded({
119
+ path: path ?? src,
120
+ failed: true
121
+ }),
122
+ style: [styles.image, { aspectRatio: ratio }]
123
+ });
124
+ }
52
125
  function renderInline(nodes) {
53
126
  return nodes.map((node, index) => {
54
127
  switch (node.type) {
@@ -66,34 +139,84 @@ function renderInline(nodes) {
66
139
  href: node.href,
67
140
  children: renderInline(node.children)
68
141
  }, index);
142
+ case "image": return node.alt;
69
143
  }
70
144
  });
71
145
  }
72
- function Block({ block, first }) {
146
+ /**
147
+ * A table read column by column: every cell keeps to one line inside the sideways scroll, so each
148
+ * column takes its widest cell and the rows line up without a table layout.
149
+ */
150
+ function MarkdownTable({ header, rows }) {
151
+ return /* @__PURE__ */ jsx(ScrollView, {
152
+ horizontal: true,
153
+ showsHorizontalScrollIndicator: false,
154
+ children: /* @__PURE__ */ jsx(View, {
155
+ style: styles.table,
156
+ children: header.map((cell, column) => /* @__PURE__ */ jsxs(View, { children: [/* @__PURE__ */ jsx(Text, {
157
+ style: [
158
+ styles.prose,
159
+ styles.cell,
160
+ styles.headCell
161
+ ],
162
+ children: renderInline(cell)
163
+ }), rows.map((row, index) => /* @__PURE__ */ jsx(Text, {
164
+ style: [styles.prose, styles.cell],
165
+ children: renderInline(row[column] ?? [])
166
+ }, index))] }, column))
167
+ })
168
+ });
169
+ }
170
+ function Block({ block, first, quoted = false }) {
171
+ const prose = [styles.prose, quoted && styles.quoted];
73
172
  switch (block.type) {
74
173
  case "heading": return /* @__PURE__ */ jsx(Text, {
75
174
  accessibilityRole: "header",
76
175
  style: [
77
- styles.prose,
176
+ ...prose,
78
177
  styles.heading(block.level),
79
- first && styles.firstHeading
178
+ first && styles.firstHeading,
179
+ quoted && styles.quoted
80
180
  ],
81
181
  children: renderInline(block.children)
82
182
  });
83
- case "paragraph": return /* @__PURE__ */ jsx(Text, {
84
- style: styles.prose,
85
- children: renderInline(block.children)
86
- });
183
+ case "paragraph": {
184
+ const parts = [];
185
+ let run = [];
186
+ const flush = () => {
187
+ if (run.length > 0) parts.push(/* @__PURE__ */ jsx(Text, {
188
+ style: prose,
189
+ children: renderInline(run)
190
+ }, parts.length));
191
+ run = [];
192
+ };
193
+ for (const node of block.children) {
194
+ if (node.type !== "image") {
195
+ run.push(node);
196
+ continue;
197
+ }
198
+ flush();
199
+ parts.push(/* @__PURE__ */ jsx(MarkdownImage, {
200
+ src: node.src,
201
+ alt: node.alt
202
+ }, parts.length));
203
+ }
204
+ flush();
205
+ return parts.length === 1 ? parts[0] : /* @__PURE__ */ jsx(View, {
206
+ style: styles.root,
207
+ children: parts
208
+ });
209
+ }
87
210
  case "list": return /* @__PURE__ */ jsx(View, {
88
211
  accessibilityRole: "list",
89
212
  style: styles.list,
90
213
  children: block.items.map((item, index) => /* @__PURE__ */ jsxs(View, {
91
214
  style: styles.item,
92
215
  children: [/* @__PURE__ */ jsx(Text, {
93
- style: [styles.prose, styles.marker],
216
+ style: [...prose, styles.marker],
94
217
  children: block.ordered ? `${block.start + index}.` : "•"
95
218
  }), /* @__PURE__ */ jsx(Text, {
96
- style: [styles.prose, styles.itemText],
219
+ style: [...prose, styles.itemText],
97
220
  children: renderInline(item)
98
221
  })]
99
222
  }, index))
@@ -106,19 +229,34 @@ function Block({ block, first }) {
106
229
  accessible: false,
107
230
  style: styles.rule
108
231
  });
232
+ case "quote": return /* @__PURE__ */ jsx(View, {
233
+ style: [styles.root, styles.quote],
234
+ children: block.children.map((child, index) => /* @__PURE__ */ jsx(Block, {
235
+ block: child,
236
+ first: index === 0,
237
+ quoted: true
238
+ }, index))
239
+ });
240
+ case "table": return /* @__PURE__ */ jsx(MarkdownTable, {
241
+ header: block.header,
242
+ rows: block.rows
243
+ });
109
244
  }
110
245
  }
111
246
  /** Markdown in Po's shared presentation, parsed by the package's own small parser. */
112
- function Markdown({ text, onFileLink }) {
247
+ function Markdown({ text, onFileLink, loadImage }) {
113
248
  const blocks = useMemo(() => parseMarkdown(text), [text]);
114
249
  return /* @__PURE__ */ jsx(FileLinkContext.Provider, {
115
250
  value: onFileLink,
116
- children: /* @__PURE__ */ jsx(View, {
117
- style: styles.root,
118
- children: blocks.map((block, index) => /* @__PURE__ */ jsx(Block, {
119
- block,
120
- first: index === 0
121
- }, index))
251
+ children: /* @__PURE__ */ jsx(ImageContext.Provider, {
252
+ value: loadImage,
253
+ children: /* @__PURE__ */ jsx(View, {
254
+ style: styles.root,
255
+ children: blocks.map((block, index) => /* @__PURE__ */ jsx(Block, {
256
+ block,
257
+ first: index === 0
258
+ }, index))
259
+ })
122
260
  })
123
261
  });
124
262
  }
@@ -211,6 +349,49 @@ const styles = StyleSheet.create((theme) => ({
211
349
  height: 1,
212
350
  backgroundColor: theme.colors["border-soft"],
213
351
  uni__dependencies: [0]
352
+ },
353
+ quote: {
354
+ borderLeftWidth: 2,
355
+ borderLeftColor: theme.colors.border,
356
+ paddingLeft: theme.spacing(3),
357
+ uni__dependencies: [0]
358
+ },
359
+ quoted: {
360
+ color: theme.colors.dim,
361
+ uni__dependencies: [0]
362
+ },
363
+ table: {
364
+ flexDirection: "row",
365
+ borderTopWidth: 1,
366
+ borderLeftWidth: 1,
367
+ borderColor: theme.colors.border,
368
+ uni__dependencies: [0]
369
+ },
370
+ cell: {
371
+ paddingHorizontal: theme.spacing(2),
372
+ paddingVertical: theme.spacing(1),
373
+ borderRightWidth: 1,
374
+ borderBottomWidth: 1,
375
+ borderColor: theme.colors.border,
376
+ uni__dependencies: [0]
377
+ },
378
+ headCell: {
379
+ backgroundColor: theme.colors.deep,
380
+ fontWeight: theme.fontWeight.semibold,
381
+ color: theme.colors.hi,
382
+ uni__dependencies: [0]
383
+ },
384
+ image: {
385
+ width: "100%",
386
+ maxHeight: 480,
387
+ borderRadius: theme.borderRadius.md,
388
+ borderWidth: 1,
389
+ borderColor: theme.colors.border,
390
+ uni__dependencies: [0]
391
+ },
392
+ imageState: {
393
+ color: theme.colors.dim,
394
+ uni__dependencies: [0]
214
395
  }
215
396
  }));
216
397