@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.
- package/dist/components/CodeBlock.d.mts.map +1 -1
- package/dist/components/CodeBlock.mjs +4 -33
- package/dist/components/CodeBlock.mjs.map +1 -1
- package/dist/components/CopyButton.d.mts +13 -0
- package/dist/components/CopyButton.d.mts.map +1 -0
- package/dist/components/CopyButton.mjs +43 -0
- package/dist/components/CopyButton.mjs.map +1 -0
- package/dist/components/HashlineLines.d.mts +13 -0
- package/dist/components/HashlineLines.d.mts.map +1 -0
- package/dist/components/HashlineLines.mjs +137 -0
- package/dist/components/HashlineLines.mjs.map +1 -0
- package/dist/components/Markdown.d.mts +8 -1
- package/dist/components/Markdown.d.mts.map +1 -1
- package/dist/components/Markdown.mjs +198 -17
- package/dist/components/Markdown.mjs.map +1 -1
- package/dist/components/SyntaxLine.d.mts +19 -0
- package/dist/components/SyntaxLine.d.mts.map +1 -0
- package/dist/components/SyntaxLine.mjs +132 -0
- package/dist/components/SyntaxLine.mjs.map +1 -0
- package/dist/components/ToolPathLink.d.mts +10 -0
- package/dist/components/ToolPathLink.d.mts.map +1 -0
- package/dist/components/ToolPathLink.mjs +40 -0
- package/dist/components/ToolPathLink.mjs.map +1 -0
- package/dist/icons/iconGlyphs.d.mts +16 -0
- package/dist/icons/iconGlyphs.d.mts.map +1 -1
- package/dist/icons/iconGlyphs.mjs +16 -0
- package/dist/icons/iconGlyphs.mjs.map +1 -1
- package/dist/icons/icons.d.mts +16 -0
- package/dist/icons/icons.d.mts.map +1 -1
- package/dist/icons/icons.mjs +5 -1
- package/dist/icons/icons.mjs.map +1 -1
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +7 -3
- package/dist/lib/markdown.mjs +101 -5
- package/dist/lib/markdown.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.d.mts","names":[],"sources":["../../src/components/CodeBlock.tsx"],"mappings":";
|
|
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 {
|
|
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(
|
|
41
|
-
|
|
42
|
-
|
|
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":["
|
|
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":"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"}
|
|
@@ -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":";;;;;;
|
|
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
|
-
|
|
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
|
-
|
|
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":
|
|
84
|
-
|
|
85
|
-
|
|
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: [
|
|
216
|
+
style: [...prose, styles.marker],
|
|
94
217
|
children: block.ordered ? `${block.start + index}.` : "•"
|
|
95
218
|
}), /* @__PURE__ */ jsx(Text, {
|
|
96
|
-
style: [
|
|
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(
|
|
117
|
-
|
|
118
|
-
children:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|