@mawaru/sdk 0.39.0 → 0.40.0
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/_schemas/graph.test.ts +15 -9
- package/_schemas/graph.ts +20 -6
- package/_schemas/human-manifest.test.ts +129 -0
- package/_schemas/human-manifest.ts +158 -0
- package/_schemas/port-spec.test.ts +40 -40
- package/_schemas/port-spec.ts +10 -24
- package/dist/_cli/login.js +1 -15
- package/dist/_cli/openBrowser.d.ts +1 -0
- package/dist/_cli/openBrowser.js +15 -0
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +8 -0
- package/dist/_cli/previewCommand.js +95 -37
- package/dist/_cli/previewFiles.d.ts +2 -1
- package/dist/_cli/previewFiles.js +42 -19
- package/dist/_cli/previewSlack.d.ts +1 -1
- package/dist/_cli/previewSlack.js +1 -1
- package/dist/_schemas/graph.d.ts +21 -3
- package/dist/_schemas/graph.js +17 -6
- package/dist/_schemas/human-manifest.d.ts +27 -0
- package/dist/_schemas/human-manifest.js +121 -0
- package/dist/_schemas/port-spec.d.ts +6 -9
- package/dist/_schemas/port-spec.js +7 -7
- package/dist/cli.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +22 -3
- package/dist/typegen.d.ts +3 -1
- package/dist/typegen.js +96 -14
- package/dist/validate.js +55 -0
- package/dist/views/_approval/ApprovalBar.d.ts +20 -0
- package/dist/views/_approval/ApprovalBar.js +74 -0
- package/dist/views/_approvalActions.d.ts +18 -0
- package/dist/views/_approvalActions.js +10 -0
- package/dist/views/_bridge.d.ts +12 -0
- package/dist/views/_bridge.js +10 -0
- package/dist/views/_parts/DocumentCard.d.ts +2 -0
- package/dist/views/_parts/DocumentCard.js +16 -0
- package/dist/views/_parts/FilePreview.d.ts +5 -0
- package/dist/views/_parts/FilePreview.js +101 -0
- package/dist/views/_parts/Linkify.d.ts +7 -0
- package/dist/views/_parts/Linkify.js +28 -0
- package/dist/views/_parts/Markdown.d.ts +44 -0
- package/dist/views/_parts/Markdown.js +313 -0
- package/dist/views/_parts/SchemaForm.d.ts +19 -0
- package/dist/views/_parts/SchemaForm.js +310 -0
- package/dist/views/_parts/fileRef.d.ts +7 -0
- package/dist/views/_parts/fileRef.js +11 -0
- package/dist/views/_parts/useNarrow.d.ts +5 -0
- package/dist/views/_parts/useNarrow.js +23 -0
- package/dist/views/host.d.ts +18 -0
- package/dist/views/host.js +20 -0
- package/dist/views/human.d.ts +73 -0
- package/dist/views/human.js +6 -0
- package/dist/views/index.d.ts +8 -0
- package/dist/views/index.js +12 -0
- package/dist/views/protocol.d.ts +34 -0
- package/dist/views/protocol.js +5 -0
- package/dist/views/runtime.d.ts +3 -0
- package/dist/views/runtime.js +86 -0
- package/dist/{slack-view.d.ts → views/slack.d.ts} +1 -1
- package/docs/development.md +40 -2
- package/index.ts +1 -0
- package/package.json +36 -4
- package/skills/create-loop/SKILL.md +6 -3
- package/views/_approval/ApprovalBar.tsx +239 -0
- package/views/_approvalActions.tsx +31 -0
- package/views/_bridge.tsx +36 -0
- package/views/_parts/DocumentCard.ts +18 -0
- package/views/_parts/FilePreview.tsx +180 -0
- package/views/_parts/Linkify.test.ts +40 -0
- package/views/_parts/Linkify.tsx +40 -0
- package/views/_parts/Markdown.test.ts +131 -0
- package/views/_parts/Markdown.tsx +472 -0
- package/views/_parts/SchemaForm.tsx +742 -0
- package/views/_parts/fileRef.ts +21 -0
- package/views/_parts/useNarrow.ts +24 -0
- package/views/host.ts +23 -0
- package/views/human.ts +91 -0
- package/views/index.ts +16 -0
- package/views/protocol.ts +40 -0
- package/views/runtime.tsx +132 -0
- package/views/slack.test.ts +40 -0
- package/{slack-view.ts → views/slack.ts} +3 -3
- /package/dist/{slack-view.js → views/slack.js} +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { type CSSProperties, useEffect, useState } from "react"
|
|
2
|
+
import { useViewBridge } from "../_bridge.js"
|
|
3
|
+
import type { FileRef } from "./fileRef.js"
|
|
4
|
+
|
|
5
|
+
// file 参照の閲覧表示:private バケットなので描画のたびに閲覧用署名 URL を(ホストに頼んで)取り、
|
|
6
|
+
// 画像(GIF 含む)と PDF はそのままインラインで見せる。それ以外は名前チップのリンクにする。
|
|
7
|
+
// NodeIoCards の IoFileItem と同じ経路だが、こちらは「見て判断する」ための大きさで出す。
|
|
8
|
+
// PDF は請求書・契約書のように「現物を見て承認する」書類で必要になる(承認画面の中で
|
|
9
|
+
// 完結させたいので、別タブへ飛ばさない)
|
|
10
|
+
|
|
11
|
+
const chipStyle: CSSProperties = {
|
|
12
|
+
display: "inline-flex",
|
|
13
|
+
alignItems: "center",
|
|
14
|
+
gap: "4px",
|
|
15
|
+
maxWidth: "100%",
|
|
16
|
+
border: "var(--border-w) solid var(--border)",
|
|
17
|
+
borderRadius: "var(--radius-s)",
|
|
18
|
+
background: "var(--surface-2)",
|
|
19
|
+
padding: "2px 8px",
|
|
20
|
+
fontSize: "var(--text-xs)",
|
|
21
|
+
color: "var(--ink)",
|
|
22
|
+
textDecoration: "none",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// mime_type は上流が入れてくれないことがあるので拡張子でも判定する
|
|
26
|
+
const isImageFile = (file: FileRef): boolean =>
|
|
27
|
+
file.mime_type?.startsWith("image/") ??
|
|
28
|
+
/\.(gif|png|jpe?g|webp|avif|svg)$/i.test(file.name)
|
|
29
|
+
|
|
30
|
+
const isPdfFile = (file: FileRef): boolean =>
|
|
31
|
+
file.mime_type === "application/pdf" || /\.pdf$/i.test(file.name)
|
|
32
|
+
|
|
33
|
+
export const FilePreview = ({
|
|
34
|
+
file,
|
|
35
|
+
maxHeight = "420px",
|
|
36
|
+
}: {
|
|
37
|
+
file: FileRef
|
|
38
|
+
maxHeight?: string
|
|
39
|
+
}) => {
|
|
40
|
+
const bridge = useViewBridge()
|
|
41
|
+
const [url, setUrl] = useState<string | null>(null)
|
|
42
|
+
// 署名 URL を取れなかった(ファイルが消えた・パスが違う)ことを「読み込み中」と
|
|
43
|
+
// 区別する:失敗を黙って読み込み中のまま固めると、原因が画面から分からない
|
|
44
|
+
const [failed, setFailed] = useState(false)
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
let alive = true
|
|
47
|
+
setUrl(null)
|
|
48
|
+
setFailed(false)
|
|
49
|
+
bridge
|
|
50
|
+
.fileUrl(file)
|
|
51
|
+
.then((signed) => {
|
|
52
|
+
if (alive) setUrl(signed)
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {
|
|
55
|
+
if (alive) setFailed(true)
|
|
56
|
+
})
|
|
57
|
+
return () => {
|
|
58
|
+
alive = false
|
|
59
|
+
}
|
|
60
|
+
}, [bridge, file])
|
|
61
|
+
|
|
62
|
+
const caption = (
|
|
63
|
+
<span style={{ fontSize: "var(--text-xs)", color: "var(--muted)" }}>
|
|
64
|
+
{file.name}
|
|
65
|
+
</span>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// PDF はブラウザ内蔵ビューアに任せる(依存を増やさない)。maxHeight は画像では
|
|
69
|
+
// 上限だが、PDF では枠の高さそのものになる(内側でスクロールさせるため)
|
|
70
|
+
if (isPdfFile(file)) {
|
|
71
|
+
return (
|
|
72
|
+
<figure style={{ margin: 0, display: "grid", gap: "var(--space-1)" }}>
|
|
73
|
+
{url ? (
|
|
74
|
+
<iframe
|
|
75
|
+
src={url}
|
|
76
|
+
title={file.name}
|
|
77
|
+
style={{
|
|
78
|
+
display: "block",
|
|
79
|
+
width: "100%",
|
|
80
|
+
height: maxHeight,
|
|
81
|
+
background: "var(--surface-2)",
|
|
82
|
+
borderRadius: "var(--radius-s)",
|
|
83
|
+
border: "var(--border-w) solid var(--border)",
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
) : (
|
|
87
|
+
<div
|
|
88
|
+
style={{
|
|
89
|
+
display: "grid",
|
|
90
|
+
placeItems: "center",
|
|
91
|
+
height: maxHeight,
|
|
92
|
+
background: "var(--surface-2)",
|
|
93
|
+
borderRadius: "var(--radius-s)",
|
|
94
|
+
border: "var(--border-w) dashed var(--border)",
|
|
95
|
+
fontSize: "var(--text-xs)",
|
|
96
|
+
color: "var(--muted)",
|
|
97
|
+
textAlign: "center",
|
|
98
|
+
padding: "0 var(--space-4)",
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{failed ? "ファイルを読み込めませんでした" : "読み込み中…"}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
<figcaption
|
|
105
|
+
style={{
|
|
106
|
+
display: "flex",
|
|
107
|
+
justifyContent: "space-between",
|
|
108
|
+
gap: "var(--space-2)",
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
{caption}
|
|
112
|
+
{url && (
|
|
113
|
+
<a
|
|
114
|
+
href={url}
|
|
115
|
+
target="_blank"
|
|
116
|
+
rel="noreferrer"
|
|
117
|
+
style={{ fontSize: "var(--text-xs)", color: "var(--muted)" }}
|
|
118
|
+
>
|
|
119
|
+
別タブで開く
|
|
120
|
+
</a>
|
|
121
|
+
)}
|
|
122
|
+
</figcaption>
|
|
123
|
+
</figure>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!isImageFile(file)) {
|
|
128
|
+
return url ? (
|
|
129
|
+
<a href={url} target="_blank" rel="noreferrer" style={chipStyle}>
|
|
130
|
+
<span aria-hidden="true">📎</span>
|
|
131
|
+
{file.name}
|
|
132
|
+
</a>
|
|
133
|
+
) : (
|
|
134
|
+
<span style={chipStyle}>
|
|
135
|
+
<span aria-hidden="true">📎</span>
|
|
136
|
+
{file.name}
|
|
137
|
+
</span>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<figure style={{ margin: 0, display: "grid", gap: "var(--space-1)" }}>
|
|
143
|
+
{url ? (
|
|
144
|
+
<a href={url} target="_blank" rel="noreferrer">
|
|
145
|
+
<img
|
|
146
|
+
src={url}
|
|
147
|
+
alt={file.name}
|
|
148
|
+
style={{
|
|
149
|
+
display: "block",
|
|
150
|
+
width: "100%",
|
|
151
|
+
maxHeight,
|
|
152
|
+
objectFit: "contain",
|
|
153
|
+
background: "var(--surface-2)",
|
|
154
|
+
borderRadius: "var(--radius-s)",
|
|
155
|
+
border: "var(--border-w) solid var(--border)",
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
</a>
|
|
159
|
+
) : (
|
|
160
|
+
<div
|
|
161
|
+
style={{
|
|
162
|
+
display: "grid",
|
|
163
|
+
placeItems: "center",
|
|
164
|
+
height: "160px",
|
|
165
|
+
background: "var(--surface-2)",
|
|
166
|
+
borderRadius: "var(--radius-s)",
|
|
167
|
+
border: "var(--border-w) dashed var(--border)",
|
|
168
|
+
fontSize: "var(--text-xs)",
|
|
169
|
+
color: "var(--muted)",
|
|
170
|
+
textAlign: "center",
|
|
171
|
+
padding: "0 var(--space-4)",
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{failed ? "画像を読み込めませんでした" : "読み込み中…"}
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
<figcaption>{caption}</figcaption>
|
|
178
|
+
</figure>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isValidElement } from "react"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { isUrl, linkify } from "./Linkify.js"
|
|
4
|
+
|
|
5
|
+
const hrefs = (text: string) =>
|
|
6
|
+
linkify(text).flatMap((node) =>
|
|
7
|
+
isValidElement<{ href: string }>(node) ? [node.props.href] : [],
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
describe("linkify", () => {
|
|
11
|
+
it("素の URL をリンクにし、前後の文字はそのまま残す", () => {
|
|
12
|
+
const nodes = linkify("PR: https://github.com/a/b/pull/1 を見て")
|
|
13
|
+
expect(nodes[0]).toBe("PR: ")
|
|
14
|
+
expect(hrefs("PR: https://github.com/a/b/pull/1 を見て")).toEqual([
|
|
15
|
+
"https://github.com/a/b/pull/1",
|
|
16
|
+
])
|
|
17
|
+
expect(nodes[2]).toBe(" を見て")
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("引用符・括弧・末尾の句読点は URL に含めない", () => {
|
|
21
|
+
expect(hrefs('"https://example.com/x"')).toEqual(["https://example.com/x"])
|
|
22
|
+
expect(hrefs("(https://example.com/y)")).toEqual([
|
|
23
|
+
"https://example.com/y",
|
|
24
|
+
])
|
|
25
|
+
expect(hrefs("see https://example.com/z.")).toEqual([
|
|
26
|
+
"https://example.com/z",
|
|
27
|
+
])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("URL が無ければ元の文字列だけ", () => {
|
|
31
|
+
expect(linkify("ただの文")).toEqual(["ただの文"])
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe("isUrl", () => {
|
|
36
|
+
it("文字列全体が URL のときだけ true", () => {
|
|
37
|
+
expect(isUrl("https://github.com/PathLight-jp/x/pull/12")).toBe(true)
|
|
38
|
+
expect(isUrl("詳細は https://example.com")).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
// テキスト中の素の URL(http/https)をクリックできるリンクにする。
|
|
4
|
+
// 値の表示(I/O・タイムライン・承認ビュー等)はどこでもこれを通す。
|
|
5
|
+
// 引用符・括弧・全角の区切りは URL に含めず、末尾の句読点も外す
|
|
6
|
+
const urlPattern = /https?:\/\/[^\s"'<>()[\]{}()「」『』【】、。]+/g
|
|
7
|
+
const trailingPunct = /[.,;:!?]+$/
|
|
8
|
+
|
|
9
|
+
/** 文字列全体が1つの URL か */
|
|
10
|
+
export const isUrl = (value: string): boolean =>
|
|
11
|
+
/^https?:\/\/\S+$/.test(value.trim())
|
|
12
|
+
|
|
13
|
+
export const linkify = (text: string, prefix = ""): ReactNode[] => {
|
|
14
|
+
const nodes: ReactNode[] = []
|
|
15
|
+
let last = 0
|
|
16
|
+
for (const match of text.matchAll(urlPattern)) {
|
|
17
|
+
const start = match.index ?? 0
|
|
18
|
+
const url = match[0].replace(trailingPunct, "")
|
|
19
|
+
if (url.length <= "https://".length) continue
|
|
20
|
+
if (start > last) nodes.push(text.slice(last, start))
|
|
21
|
+
nodes.push(
|
|
22
|
+
<a
|
|
23
|
+
key={`${prefix}${start}`}
|
|
24
|
+
href={url}
|
|
25
|
+
target="_blank"
|
|
26
|
+
rel="noreferrer"
|
|
27
|
+
// 行クリックで選択・開閉する親の中でも、リンクはリンクとして開く
|
|
28
|
+
onClick={(e) => e.stopPropagation()}
|
|
29
|
+
style={{ overflowWrap: "anywhere" }}
|
|
30
|
+
>
|
|
31
|
+
{url}
|
|
32
|
+
</a>,
|
|
33
|
+
)
|
|
34
|
+
last = start + url.length
|
|
35
|
+
}
|
|
36
|
+
if (last < text.length) nodes.push(text.slice(last))
|
|
37
|
+
return nodes
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const Linkify = ({ text }: { text: string }) => <>{linkify(text)}</>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { isLongText, parseBlocks } from "./Markdown.js"
|
|
3
|
+
|
|
4
|
+
// Markdown レンダラのブロック分割(描画は含まない)。
|
|
5
|
+
// AI の下書き・調査レポートに実際に出る記法だけを見る前提
|
|
6
|
+
|
|
7
|
+
describe("parseBlocks", () => {
|
|
8
|
+
it("見出し・箇条書き・段落を分ける", () => {
|
|
9
|
+
expect(
|
|
10
|
+
parseBlocks(
|
|
11
|
+
[
|
|
12
|
+
"## 調査レポート(社内向け)",
|
|
13
|
+
"",
|
|
14
|
+
"### 問い合わせ概要",
|
|
15
|
+
"- ユーザー: **不明**",
|
|
16
|
+
"- 発生時刻: 2026-08-10",
|
|
17
|
+
"",
|
|
18
|
+
"現時点で判明している原因は次の2点です。",
|
|
19
|
+
].join("\n"),
|
|
20
|
+
),
|
|
21
|
+
).toEqual([
|
|
22
|
+
{ kind: "heading", level: 2, text: "調査レポート(社内向け)" },
|
|
23
|
+
{ kind: "heading", level: 3, text: "問い合わせ概要" },
|
|
24
|
+
{
|
|
25
|
+
kind: "list",
|
|
26
|
+
ordered: false,
|
|
27
|
+
items: [
|
|
28
|
+
{ text: "ユーザー: **不明**", nested: false },
|
|
29
|
+
{ text: "発生時刻: 2026-08-10", nested: false },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{ kind: "para", lines: ["現時点で判明している原因は次の2点です。"] },
|
|
33
|
+
])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("段落は空行で切れ、段落内の改行は行として残る", () => {
|
|
37
|
+
expect(parseBlocks("一行目\n二行目\n\n次の段落")).toEqual([
|
|
38
|
+
{ kind: "para", lines: ["一行目", "二行目"] },
|
|
39
|
+
{ kind: "para", lines: ["次の段落"] },
|
|
40
|
+
])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("番号付きリストとネスト(1段)を拾う", () => {
|
|
44
|
+
expect(parseBlocks("1. 一つ目\n2. 二つ目\n 1. ぶら下がり")).toEqual([
|
|
45
|
+
{
|
|
46
|
+
kind: "list",
|
|
47
|
+
ordered: true,
|
|
48
|
+
items: [
|
|
49
|
+
{ text: "一つ目", nested: false },
|
|
50
|
+
{ text: "二つ目", nested: false },
|
|
51
|
+
{ text: "ぶら下がり", nested: true },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
])
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("コードブロックは中身を素のまま持つ", () => {
|
|
58
|
+
expect(parseBlocks("```ts\nconst a = 1\n\nconst b = 2\n```\nあと")).toEqual(
|
|
59
|
+
[
|
|
60
|
+
{ kind: "code", lines: ["const a = 1", "", "const b = 2"] },
|
|
61
|
+
{ kind: "para", lines: ["あと"] },
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("閉じない ``` でも終端まで読んで止まる(無限ループにしない)", () => {
|
|
67
|
+
expect(parseBlocks("```\nconst a = 1")).toEqual([
|
|
68
|
+
{ kind: "code", lines: ["const a = 1"] },
|
|
69
|
+
])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it("--- は水平線、引用はまとめて1ブロック", () => {
|
|
73
|
+
expect(parseBlocks("---\n> 引用の1行目\n> 2行目")).toEqual([
|
|
74
|
+
{ kind: "rule" },
|
|
75
|
+
{ kind: "quote", lines: ["引用の1行目", "2行目"] },
|
|
76
|
+
])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("表:見出し行+区切り行+本体行を1ブロックにし、寄せは区切りのコロンで決める", () => {
|
|
80
|
+
expect(
|
|
81
|
+
parseBlocks(
|
|
82
|
+
[
|
|
83
|
+
"前置き",
|
|
84
|
+
"| 項目 | 値 | 件数 |",
|
|
85
|
+
"|:-- | :-: | --:|",
|
|
86
|
+
"| ログ | **402** | 3 |",
|
|
87
|
+
"| DB | `plan=expired` | 1 |",
|
|
88
|
+
"",
|
|
89
|
+
"あと",
|
|
90
|
+
].join("\n"),
|
|
91
|
+
),
|
|
92
|
+
).toEqual([
|
|
93
|
+
{ kind: "para", lines: ["前置き"] },
|
|
94
|
+
{
|
|
95
|
+
kind: "table",
|
|
96
|
+
align: ["left", "center", "right"],
|
|
97
|
+
header: ["項目", "値", "件数"],
|
|
98
|
+
rows: [
|
|
99
|
+
["ログ", "**402**", "3"],
|
|
100
|
+
["DB", "`plan=expired`", "1"],
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{ kind: "para", lines: ["あと"] },
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("表:外側の | が無い行・セル数が足りない行も受ける(欠けは空セル)", () => {
|
|
108
|
+
expect(parseBlocks("a | b\n--- | ---\n1")).toEqual([
|
|
109
|
+
{
|
|
110
|
+
kind: "table",
|
|
111
|
+
align: [null, null],
|
|
112
|
+
header: ["a", "b"],
|
|
113
|
+
rows: [["1", ""]],
|
|
114
|
+
},
|
|
115
|
+
])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("表:区切り行が続かない | 行は段落のまま(壊さない)", () => {
|
|
119
|
+
expect(parseBlocks("| a | b |\n本文")).toEqual([
|
|
120
|
+
{ kind: "para", lines: ["| a | b |", "本文"] },
|
|
121
|
+
])
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe("isLongText", () => {
|
|
126
|
+
it("改行を含むか80文字超なら長文", () => {
|
|
127
|
+
expect(isLongText("短い一行")).toBe(false)
|
|
128
|
+
expect(isLongText("一行目\n二行目")).toBe(true)
|
|
129
|
+
expect(isLongText("あ".repeat(81))).toBe(true)
|
|
130
|
+
})
|
|
131
|
+
})
|