@nshipster/sosumi 1.0.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/LICENSE.md +19 -0
- package/README.md +304 -0
- package/bin/sosumi.mjs +76 -0
- package/package.json +53 -0
- package/public/_headers +2 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +7 -0
- package/public/icons/square.and.pencil.svg +15 -0
- package/public/index.html +898 -0
- package/public/llms.txt +184 -0
- package/public/sosumi.m4a +0 -0
- package/src/cli.ts +214 -0
- package/src/index.ts +507 -0
- package/src/lib/cli-endpoints.ts +106 -0
- package/src/lib/external/fetch.ts +133 -0
- package/src/lib/external/index.ts +8 -0
- package/src/lib/external/policy.ts +308 -0
- package/src/lib/external/types.ts +10 -0
- package/src/lib/fetch.ts +43 -0
- package/src/lib/hig/fetch.ts +186 -0
- package/src/lib/hig/index.ts +9 -0
- package/src/lib/hig/render.ts +514 -0
- package/src/lib/hig/types.ts +206 -0
- package/src/lib/hig/util.ts +30 -0
- package/src/lib/mcp.ts +315 -0
- package/src/lib/reference/fetch.ts +53 -0
- package/src/lib/reference/index.ts +8 -0
- package/src/lib/reference/render.ts +739 -0
- package/src/lib/reference/types.ts +31 -0
- package/src/lib/search.ts +221 -0
- package/src/lib/types.ts +334 -0
- package/src/lib/url.ts +55 -0
- package/src/lib/video/index.ts +179 -0
- package/wrangler.jsonc +27 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human Interface Guidelines (HIG) rendering functionality
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { extractTitleFromIdentifier } from "../reference/render"
|
|
6
|
+
import type { ContentItem, TextFragment } from "../types"
|
|
7
|
+
import type {
|
|
8
|
+
HIGExternalReference,
|
|
9
|
+
HIGImageReference,
|
|
10
|
+
HIGPageJSON,
|
|
11
|
+
HIGReference,
|
|
12
|
+
HIGTableOfContents,
|
|
13
|
+
HIGTocItem,
|
|
14
|
+
HIGTopicSection,
|
|
15
|
+
} from "./types"
|
|
16
|
+
import { isHIGImageReference } from "./util"
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// RENDERING FUNCTIONS
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Render HIG page JSON to markdown
|
|
24
|
+
*/
|
|
25
|
+
export async function renderHIGFromJSON(jsonData: HIGPageJSON, sourceUrl: string): Promise<string> {
|
|
26
|
+
let markdown = ""
|
|
27
|
+
|
|
28
|
+
// Generate front matter
|
|
29
|
+
markdown += generateHIGFrontMatter(jsonData, sourceUrl)
|
|
30
|
+
|
|
31
|
+
// Add navigation breadcrumbs for HIG
|
|
32
|
+
const breadcrumbs = generateHIGBreadcrumbs(sourceUrl)
|
|
33
|
+
if (breadcrumbs) {
|
|
34
|
+
markdown += breadcrumbs
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Add role heading if available
|
|
38
|
+
if (jsonData.metadata?.role) {
|
|
39
|
+
const roleDisplay =
|
|
40
|
+
jsonData.metadata.role === "collectionGroup" ? "Guide Collection" : jsonData.metadata.role
|
|
41
|
+
markdown += `**${roleDisplay}**\n\n`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Add title
|
|
45
|
+
const title = jsonData.metadata?.title || ""
|
|
46
|
+
if (title) {
|
|
47
|
+
markdown += `# ${title}\n\n`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add abstract
|
|
51
|
+
if (jsonData.abstract && Array.isArray(jsonData.abstract)) {
|
|
52
|
+
const abstractText = jsonData.abstract
|
|
53
|
+
.filter((item: TextFragment) => item.type === "text")
|
|
54
|
+
.map((item: TextFragment) => item.text)
|
|
55
|
+
.join("")
|
|
56
|
+
|
|
57
|
+
if (abstractText.trim()) {
|
|
58
|
+
markdown += `> ${abstractText}\n\n`
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Add primary content sections
|
|
63
|
+
if (jsonData.primaryContentSections) {
|
|
64
|
+
for (const section of jsonData.primaryContentSections) {
|
|
65
|
+
if (section.kind === "content" && section.content) {
|
|
66
|
+
markdown += renderHIGContent(section.content, jsonData.references)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add regular content sections
|
|
72
|
+
if (jsonData.sections && jsonData.sections.length > 0) {
|
|
73
|
+
markdown += renderHIGContent(jsonData.sections, jsonData.references)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Add topic sections (unless hidden)
|
|
77
|
+
if (jsonData.topicSections && jsonData.topicSectionsStyle !== "hidden") {
|
|
78
|
+
markdown += renderHIGTopicSections(jsonData.topicSections, jsonData.references)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Trim whitespace
|
|
82
|
+
markdown = markdown.trim()
|
|
83
|
+
|
|
84
|
+
// Add footer
|
|
85
|
+
markdown += `\n\n---\n\n`
|
|
86
|
+
markdown += `*Extracted by [sosumi.ai](https://sosumi.ai) - Making Apple docs AI-readable.*\n`
|
|
87
|
+
markdown += `*This is unofficial content. All Human Interface Guidelines belong to Apple Inc.*\n`
|
|
88
|
+
|
|
89
|
+
return markdown
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Render HIG table of contents to markdown
|
|
94
|
+
*/
|
|
95
|
+
export async function renderHIGTableOfContents(tocData: HIGTableOfContents): Promise<string> {
|
|
96
|
+
let markdown = ""
|
|
97
|
+
|
|
98
|
+
// Generate front matter
|
|
99
|
+
markdown += `---\n`
|
|
100
|
+
markdown += `title: Human Interface Guidelines\n`
|
|
101
|
+
markdown += `description: Apple's Human Interface Guidelines - Complete table of contents\n`
|
|
102
|
+
markdown += `source: https://developer.apple.com/design/human-interface-guidelines/\n`
|
|
103
|
+
markdown += `timestamp: ${new Date().toISOString()}\n`
|
|
104
|
+
markdown += `---\n\n`
|
|
105
|
+
|
|
106
|
+
// Add title and introduction
|
|
107
|
+
markdown += `# Human Interface Guidelines\n\n`
|
|
108
|
+
markdown += `> Apple's comprehensive guide to designing interfaces for all Apple platforms.\n\n`
|
|
109
|
+
|
|
110
|
+
// Render the table of contents
|
|
111
|
+
if (tocData.interfaceLanguages?.swift) {
|
|
112
|
+
markdown += renderHIGTocItems(tocData.interfaceLanguages.swift, 2)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add footer
|
|
116
|
+
markdown += `\n\n---\n\n`
|
|
117
|
+
markdown += `*Extracted by [sosumi.ai](https://sosumi.ai) - Making Apple docs AI-readable.*\n`
|
|
118
|
+
markdown += `*This is unofficial content. All Human Interface Guidelines belong to Apple Inc.*\n`
|
|
119
|
+
|
|
120
|
+
return markdown
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// PRIVATE HELPER FUNCTIONS
|
|
125
|
+
// ============================================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate front matter for HIG pages
|
|
129
|
+
*/
|
|
130
|
+
function generateHIGFrontMatter(jsonData: HIGPageJSON, sourceUrl: string): string {
|
|
131
|
+
const frontMatter: Record<string, string> = {}
|
|
132
|
+
|
|
133
|
+
if (jsonData.metadata?.title) {
|
|
134
|
+
frontMatter.title = jsonData.metadata.title
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (jsonData.abstract && Array.isArray(jsonData.abstract)) {
|
|
138
|
+
const description = jsonData.abstract
|
|
139
|
+
.filter((item: TextFragment) => item.type === "text")
|
|
140
|
+
.map((item: TextFragment) => item.text)
|
|
141
|
+
.join("")
|
|
142
|
+
.trim()
|
|
143
|
+
if (description) {
|
|
144
|
+
frontMatter.description = description
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
frontMatter.source = sourceUrl
|
|
149
|
+
frontMatter.timestamp = new Date().toISOString()
|
|
150
|
+
|
|
151
|
+
// Convert to YAML format
|
|
152
|
+
const yamlLines = Object.entries(frontMatter).map(([key, value]) => `${key}: ${value}`)
|
|
153
|
+
return `---\n${yamlLines.join("\n")}\n---\n\n`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generate breadcrumb navigation for HIG
|
|
158
|
+
*/
|
|
159
|
+
function generateHIGBreadcrumbs(sourceUrl: string): string {
|
|
160
|
+
const url = new URL(sourceUrl)
|
|
161
|
+
const pathParts = url.pathname.split("/").filter(Boolean)
|
|
162
|
+
// pathParts will be: ["design", "human-interface-guidelines", "foundations", "color"] for foundations/color
|
|
163
|
+
|
|
164
|
+
if (pathParts.length < 3) return "" // Need at least /design/human-interface-guidelines
|
|
165
|
+
|
|
166
|
+
let breadcrumbs = `**Navigation:** [Human Interface Guidelines](/design/human-interface-guidelines)`
|
|
167
|
+
|
|
168
|
+
// Add breadcrumbs for all parts after /design/human-interface-guidelines
|
|
169
|
+
// This includes both intermediate and final parts
|
|
170
|
+
for (let i = 3; i < pathParts.length; i++) {
|
|
171
|
+
const part = pathParts[i]
|
|
172
|
+
// Build path up to this point
|
|
173
|
+
const path = `/${pathParts.slice(0, i + 1).join("/")}`
|
|
174
|
+
const formattedPart = part.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase())
|
|
175
|
+
breadcrumbs += ` › [${formattedPart}](${path})`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return `${breadcrumbs}\n\n`
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Render HIG content items
|
|
183
|
+
*/
|
|
184
|
+
function renderHIGContent(
|
|
185
|
+
content: ContentItem[],
|
|
186
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
187
|
+
): string {
|
|
188
|
+
let markdown = ""
|
|
189
|
+
|
|
190
|
+
for (const item of content) {
|
|
191
|
+
if (item.type === "links" && item.items && item.style === "compactGrid") {
|
|
192
|
+
// Handle the special case of link grids (like on the getting started page)
|
|
193
|
+
for (const linkId of item.items) {
|
|
194
|
+
if (typeof linkId === "string") {
|
|
195
|
+
const reference = references[linkId]
|
|
196
|
+
if (reference && !isHIGImageReference(reference)) {
|
|
197
|
+
const title = reference.title || "Untitled"
|
|
198
|
+
const url = reference.url || "#"
|
|
199
|
+
const refAbstract = (reference as HIGReference).abstract
|
|
200
|
+
const abstract = Array.isArray(refAbstract)
|
|
201
|
+
? refAbstract.map((a: TextFragment) => a.text).join("")
|
|
202
|
+
: ""
|
|
203
|
+
|
|
204
|
+
markdown += `- [${title}](${url})`
|
|
205
|
+
if (abstract) {
|
|
206
|
+
markdown += ` - ${abstract}`
|
|
207
|
+
}
|
|
208
|
+
markdown += "\n"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
markdown += "\n"
|
|
213
|
+
} else {
|
|
214
|
+
// Handle other content types using the existing content renderer
|
|
215
|
+
markdown += renderContentItem(item, references)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return markdown
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Render individual content item
|
|
224
|
+
*/
|
|
225
|
+
function renderContentItem(
|
|
226
|
+
item: ContentItem,
|
|
227
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
228
|
+
): string {
|
|
229
|
+
let markdown = ""
|
|
230
|
+
|
|
231
|
+
if (item.type === "heading") {
|
|
232
|
+
const level = Math.min(item.level || 2, 6)
|
|
233
|
+
const hashes = "#".repeat(level)
|
|
234
|
+
markdown += `${hashes} ${item.text}\n\n`
|
|
235
|
+
} else if (item.type === "paragraph") {
|
|
236
|
+
if (item.inlineContent) {
|
|
237
|
+
const text = renderHIGInlineContent(item.inlineContent, references)
|
|
238
|
+
markdown += `${text}\n\n`
|
|
239
|
+
}
|
|
240
|
+
} else if (item.type === "codeListing") {
|
|
241
|
+
let code = ""
|
|
242
|
+
if (Array.isArray(item.code)) {
|
|
243
|
+
code = item.code.join("\n")
|
|
244
|
+
} else {
|
|
245
|
+
code = String(item.code || "")
|
|
246
|
+
}
|
|
247
|
+
const syntax = item.syntax || "swift"
|
|
248
|
+
markdown += `\`\`\`${syntax}\n${code}\n\`\`\`\n\n`
|
|
249
|
+
} else if (item.type === "unorderedList" && item.items) {
|
|
250
|
+
for (const listItem of item.items) {
|
|
251
|
+
const itemText = renderHIGContent(listItem.content || [], references)
|
|
252
|
+
markdown += `- ${itemText.replace(/\n\n$/, "")}\n`
|
|
253
|
+
}
|
|
254
|
+
markdown += "\n"
|
|
255
|
+
} else if (item.type === "orderedList" && item.items) {
|
|
256
|
+
item.items.forEach((listItem: ContentItem, index: number) => {
|
|
257
|
+
const itemText = renderHIGContent(listItem.content || [], references)
|
|
258
|
+
markdown += `${index + 1}. ${itemText.replace(/\n\n$/, "")}\n`
|
|
259
|
+
})
|
|
260
|
+
markdown += "\n"
|
|
261
|
+
} else if (item.type === "table") {
|
|
262
|
+
markdown += renderHIGTable(item, references)
|
|
263
|
+
} else if (item.type === "aside") {
|
|
264
|
+
markdown += renderHIGAside(item, references)
|
|
265
|
+
} else if (item.type === "row") {
|
|
266
|
+
markdown += renderHIGRow(item, references)
|
|
267
|
+
} else if (item.type === "video") {
|
|
268
|
+
markdown += renderHIGVideo(item, references)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return markdown
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Render a HIG table to markdown.
|
|
276
|
+
* HIG tables use header: "row" and rows[rowIndex][cellIndex] = ContentItem[].
|
|
277
|
+
*/
|
|
278
|
+
function renderHIGTable(
|
|
279
|
+
item: ContentItem,
|
|
280
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
281
|
+
): string {
|
|
282
|
+
const table = item as ContentItem & {
|
|
283
|
+
header?: string
|
|
284
|
+
rows?: ContentItem[][][]
|
|
285
|
+
}
|
|
286
|
+
const rows = table.rows ?? []
|
|
287
|
+
if (rows.length === 0) return ""
|
|
288
|
+
|
|
289
|
+
const escapeCell = (s: string) => s.replace(/\|/g, "\\|").replace(/\n/g, " ").trim()
|
|
290
|
+
const renderCell = (cell: ContentItem | ContentItem[]) => {
|
|
291
|
+
const items = Array.isArray(cell) ? cell : [cell]
|
|
292
|
+
const text = renderHIGContent(items, references)
|
|
293
|
+
return escapeCell(text)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const firstRowIsHeader = table.header === "row"
|
|
297
|
+
let markdown = ""
|
|
298
|
+
|
|
299
|
+
rows.forEach((row, rowIndex) => {
|
|
300
|
+
const cells = row.map((cell) => renderCell(cell))
|
|
301
|
+
if (cells.length === 0) return
|
|
302
|
+
markdown += `| ${cells.join(" | ")} |\n`
|
|
303
|
+
if (firstRowIsHeader && rowIndex === 0) {
|
|
304
|
+
markdown += `| ${cells.map(() => "---").join(" | ")} |\n`
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
return markdown ? `${markdown}\n` : ""
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Render a HIG aside/callout block to markdown.
|
|
313
|
+
*/
|
|
314
|
+
function renderHIGAside(
|
|
315
|
+
item: ContentItem,
|
|
316
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
317
|
+
): string {
|
|
318
|
+
const aside = item as ContentItem & { style?: string; name?: string }
|
|
319
|
+
const rawType = (aside.style || aside.name || "note").toLowerCase()
|
|
320
|
+
const calloutType = mapHIGAsideStyleToCallout(rawType)
|
|
321
|
+
const asideContent = item.content ? renderHIGContent(item.content, references) : ""
|
|
322
|
+
const cleanContent = asideContent.trim().replace(/\n/g, "\n> ")
|
|
323
|
+
if (!cleanContent) return ""
|
|
324
|
+
return `> [!${calloutType}]\n> ${cleanContent}\n\n`
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Render a HIG row block by rendering each column content in order.
|
|
329
|
+
*/
|
|
330
|
+
function renderHIGRow(
|
|
331
|
+
item: ContentItem,
|
|
332
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
333
|
+
): string {
|
|
334
|
+
const row = item as ContentItem & {
|
|
335
|
+
columns?: Array<{
|
|
336
|
+
content?: ContentItem[]
|
|
337
|
+
}>
|
|
338
|
+
}
|
|
339
|
+
if (!row.columns || row.columns.length === 0) return ""
|
|
340
|
+
|
|
341
|
+
let markdown = ""
|
|
342
|
+
for (const column of row.columns) {
|
|
343
|
+
if (column.content && column.content.length > 0) {
|
|
344
|
+
markdown += renderHIGContent(column.content, references)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return markdown
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Render a HIG video block as a markdown link.
|
|
352
|
+
*/
|
|
353
|
+
function renderHIGVideo(
|
|
354
|
+
item: ContentItem,
|
|
355
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
356
|
+
): string {
|
|
357
|
+
const video = item as ContentItem & {
|
|
358
|
+
identifier?: string
|
|
359
|
+
metadata?: {
|
|
360
|
+
abstract?: TextFragment[]
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (!video.identifier) return ""
|
|
364
|
+
|
|
365
|
+
const reference = references[video.identifier] as
|
|
366
|
+
| {
|
|
367
|
+
type?: string
|
|
368
|
+
alt?: string
|
|
369
|
+
variants?: Array<{ url?: string }>
|
|
370
|
+
}
|
|
371
|
+
| undefined
|
|
372
|
+
|
|
373
|
+
const videoUrl = reference?.variants?.[0]?.url
|
|
374
|
+
if (!videoUrl) return ""
|
|
375
|
+
|
|
376
|
+
const abstractText = (video.metadata?.abstract ?? [])
|
|
377
|
+
.filter((fragment) => fragment.type === "text")
|
|
378
|
+
.map((fragment) => fragment.text)
|
|
379
|
+
.join("")
|
|
380
|
+
.trim()
|
|
381
|
+
const label = abstractText || reference?.alt || "Video"
|
|
382
|
+
|
|
383
|
+
return `[${label}](${videoUrl})\n\n`
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Render HIG inline content
|
|
388
|
+
*/
|
|
389
|
+
function renderHIGInlineContent(
|
|
390
|
+
inlineContent: ContentItem[],
|
|
391
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
392
|
+
): string {
|
|
393
|
+
return inlineContent
|
|
394
|
+
.map((item) => {
|
|
395
|
+
if (item.type === "text") {
|
|
396
|
+
return item.text
|
|
397
|
+
} else if (item.type === "codeVoice") {
|
|
398
|
+
return `\`${item.code}\``
|
|
399
|
+
} else if (item.type === "reference") {
|
|
400
|
+
const reference = item.identifier ? references[item.identifier] : undefined
|
|
401
|
+
const refTitle =
|
|
402
|
+
reference && !isHIGImageReference(reference)
|
|
403
|
+
? (reference as HIGReference | HIGExternalReference).title
|
|
404
|
+
: undefined
|
|
405
|
+
const title =
|
|
406
|
+
item.title ||
|
|
407
|
+
item.text ||
|
|
408
|
+
refTitle ||
|
|
409
|
+
(item.identifier ? extractTitleFromIdentifier(item.identifier) : "")
|
|
410
|
+
const url = reference ? (isHIGImageReference(reference) ? "#" : reference.url) : "#"
|
|
411
|
+
return `[${title}](${url})`
|
|
412
|
+
} else if (item.type === "emphasis") {
|
|
413
|
+
return `*${
|
|
414
|
+
item.inlineContent ? renderHIGInlineContent(item.inlineContent, references) : ""
|
|
415
|
+
}*`
|
|
416
|
+
} else if (item.type === "strong") {
|
|
417
|
+
return `**${
|
|
418
|
+
item.inlineContent ? renderHIGInlineContent(item.inlineContent, references) : ""
|
|
419
|
+
}**`
|
|
420
|
+
} else if (item.type === "image" && item.identifier) {
|
|
421
|
+
const reference = references[item.identifier]
|
|
422
|
+
if (reference && isHIGImageReference(reference)) {
|
|
423
|
+
const imageUrl = reference.variants?.[0]?.url
|
|
424
|
+
if (imageUrl) {
|
|
425
|
+
return ``
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return ""
|
|
429
|
+
}
|
|
430
|
+
return item.text || ""
|
|
431
|
+
})
|
|
432
|
+
.join("")
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Render HIG topic sections
|
|
437
|
+
*/
|
|
438
|
+
function renderHIGTopicSections(
|
|
439
|
+
topicSections: HIGTopicSection[],
|
|
440
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>,
|
|
441
|
+
): string {
|
|
442
|
+
let markdown = ""
|
|
443
|
+
|
|
444
|
+
for (const section of topicSections) {
|
|
445
|
+
if (section.title) {
|
|
446
|
+
markdown += `## ${section.title}\n\n`
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (section.identifiers) {
|
|
450
|
+
for (const id of section.identifiers) {
|
|
451
|
+
const reference = references[id]
|
|
452
|
+
if (reference && !isHIGImageReference(reference)) {
|
|
453
|
+
const title = reference.title || "Untitled"
|
|
454
|
+
const url = reference.url || "#"
|
|
455
|
+
const refAbstract = (reference as HIGReference).abstract
|
|
456
|
+
const abstract = Array.isArray(refAbstract)
|
|
457
|
+
? refAbstract.map((a: TextFragment) => a.text).join("")
|
|
458
|
+
: ""
|
|
459
|
+
|
|
460
|
+
markdown += `- [${title}](${url})`
|
|
461
|
+
if (abstract) {
|
|
462
|
+
markdown += ` - ${abstract}`
|
|
463
|
+
}
|
|
464
|
+
markdown += "\n"
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
markdown += "\n"
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return markdown
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function mapHIGAsideStyleToCallout(style: string): string {
|
|
475
|
+
switch (style.toLowerCase()) {
|
|
476
|
+
case "warning":
|
|
477
|
+
return "WARNING"
|
|
478
|
+
case "important":
|
|
479
|
+
return "IMPORTANT"
|
|
480
|
+
case "caution":
|
|
481
|
+
return "CAUTION"
|
|
482
|
+
case "tip":
|
|
483
|
+
return "TIP"
|
|
484
|
+
case "deprecated":
|
|
485
|
+
return "WARNING"
|
|
486
|
+
default:
|
|
487
|
+
return "NOTE"
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Render HIG table of contents items
|
|
493
|
+
*/
|
|
494
|
+
function renderHIGTocItems(items: HIGTocItem[], headingLevel: number): string {
|
|
495
|
+
let markdown = ""
|
|
496
|
+
|
|
497
|
+
for (const item of items) {
|
|
498
|
+
if (item.type === "module" || item.type === "symbol") {
|
|
499
|
+
// Main sections get headings
|
|
500
|
+
const hashes = "#".repeat(Math.min(headingLevel, 6))
|
|
501
|
+
markdown += `${hashes} ${item.title}\n\n`
|
|
502
|
+
|
|
503
|
+
if (item.children) {
|
|
504
|
+
markdown += renderHIGTocItems(item.children, headingLevel + 1)
|
|
505
|
+
}
|
|
506
|
+
} else if (item.type === "article") {
|
|
507
|
+
// Articles get listed as links
|
|
508
|
+
const url = item.path
|
|
509
|
+
markdown += `- [${item.title}](${url})\n`
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return markdown
|
|
514
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human Interface Guidelines (HIG) specific types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ContentItem, PrimaryContentSection, TextFragment } from "../types"
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// HIG TYPES
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents an icon reference in the HIG ToC
|
|
13
|
+
*/
|
|
14
|
+
export interface HIGIconReference {
|
|
15
|
+
alt: string
|
|
16
|
+
identifier: string
|
|
17
|
+
type: "image"
|
|
18
|
+
variants: Array<{
|
|
19
|
+
traits: string[]
|
|
20
|
+
url: string
|
|
21
|
+
}>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Represents an item in the HIG table of contents
|
|
26
|
+
*/
|
|
27
|
+
export interface HIGTocItem {
|
|
28
|
+
children?: HIGTocItem[]
|
|
29
|
+
icon?: string
|
|
30
|
+
path: string
|
|
31
|
+
title: string
|
|
32
|
+
type: "module" | "symbol" | "article"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Represents the complete HIG table of contents structure
|
|
37
|
+
*/
|
|
38
|
+
export interface HIGTableOfContents {
|
|
39
|
+
includedArchiveIdentifiers: string[]
|
|
40
|
+
interfaceLanguages: {
|
|
41
|
+
swift: HIGTocItem[]
|
|
42
|
+
}
|
|
43
|
+
references: Record<string, HIGIconReference>
|
|
44
|
+
schemaVersion: {
|
|
45
|
+
major: number
|
|
46
|
+
minor: number
|
|
47
|
+
patch: number
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* HIG-specific image metadata with card/icon types
|
|
53
|
+
*/
|
|
54
|
+
export interface HIGImage {
|
|
55
|
+
identifier: string
|
|
56
|
+
type: "icon" | "card" | "image"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* HIG page metadata with locale and image support
|
|
61
|
+
*/
|
|
62
|
+
export interface HIGMetadata {
|
|
63
|
+
role: string
|
|
64
|
+
title: string
|
|
65
|
+
images?: HIGImage[]
|
|
66
|
+
availableLocales?: string[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* HIG-specific identifier with interface language
|
|
71
|
+
*/
|
|
72
|
+
export interface HIGIdentifier {
|
|
73
|
+
interfaceLanguage: string
|
|
74
|
+
url: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* HIG hierarchy information
|
|
79
|
+
*/
|
|
80
|
+
export interface HIGHierarchy {
|
|
81
|
+
paths: string[][]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* HIG topic section structure
|
|
86
|
+
*/
|
|
87
|
+
export interface HIGTopicSection {
|
|
88
|
+
title?: string
|
|
89
|
+
identifiers: string[]
|
|
90
|
+
anchor?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* HIG image variant with resolution and color mode support
|
|
95
|
+
*/
|
|
96
|
+
export interface HIGImageVariant {
|
|
97
|
+
traits: string[]
|
|
98
|
+
url: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* HIG image reference with variants
|
|
103
|
+
*/
|
|
104
|
+
export interface HIGImageReference {
|
|
105
|
+
alt: string | null
|
|
106
|
+
identifier: string
|
|
107
|
+
type: "icon" | "card" | "image"
|
|
108
|
+
variants: HIGImageVariant[]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* HIG reference item (for linked articles and topics)
|
|
113
|
+
*/
|
|
114
|
+
export interface HIGReference {
|
|
115
|
+
kind: string
|
|
116
|
+
role?: string
|
|
117
|
+
title: string
|
|
118
|
+
url: string
|
|
119
|
+
abstract?: TextFragment[]
|
|
120
|
+
identifier: string
|
|
121
|
+
images?: HIGImage[]
|
|
122
|
+
type: "topic"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* External/non-topic reference appearing in HIG references map
|
|
127
|
+
*/
|
|
128
|
+
export interface HIGExternalReference {
|
|
129
|
+
title: string
|
|
130
|
+
identifier: string
|
|
131
|
+
titleInlineContent?: TextFragment[]
|
|
132
|
+
url: string
|
|
133
|
+
type: string // e.g. "link"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* HIG legal notices
|
|
138
|
+
*/
|
|
139
|
+
export interface HIGLegalNotices {
|
|
140
|
+
copyright: string
|
|
141
|
+
termsOfUse: string
|
|
142
|
+
privacy?: string
|
|
143
|
+
privacyPolicy?: string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The main HIG page JSON structure
|
|
148
|
+
*/
|
|
149
|
+
export interface HIGPageJSON {
|
|
150
|
+
// Metadata and identification
|
|
151
|
+
metadata: HIGMetadata
|
|
152
|
+
kind: "article"
|
|
153
|
+
identifier: HIGIdentifier
|
|
154
|
+
hierarchy: HIGHierarchy
|
|
155
|
+
|
|
156
|
+
// Content sections
|
|
157
|
+
sections: ContentItem[]
|
|
158
|
+
primaryContentSections: PrimaryContentSection[]
|
|
159
|
+
abstract: TextFragment[]
|
|
160
|
+
|
|
161
|
+
// Topic organization
|
|
162
|
+
topicSections?: HIGTopicSection[]
|
|
163
|
+
topicSectionsStyle?: "hidden" | "list" | "compactGrid"
|
|
164
|
+
|
|
165
|
+
// References to other content and images
|
|
166
|
+
references: Record<string, HIGReference | HIGImageReference | HIGExternalReference>
|
|
167
|
+
|
|
168
|
+
// Schema version
|
|
169
|
+
schemaVersion: {
|
|
170
|
+
major: number
|
|
171
|
+
minor: number
|
|
172
|
+
patch: number
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Legal information
|
|
176
|
+
legalNotices?: HIGLegalNotices
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// TYPE GUARDS
|
|
181
|
+
// ============================================================================
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Type guard to check if a reference is an image reference
|
|
185
|
+
*/
|
|
186
|
+
export function isHIGImageReference(
|
|
187
|
+
ref: HIGReference | HIGImageReference | HIGExternalReference,
|
|
188
|
+
): ref is HIGImageReference {
|
|
189
|
+
return "alt" in ref && "variants" in ref
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Type guard to check if a reference is a topic reference
|
|
194
|
+
*/
|
|
195
|
+
export function isHIGTopicReference(
|
|
196
|
+
ref: HIGReference | HIGImageReference | HIGExternalReference,
|
|
197
|
+
): ref is HIGReference {
|
|
198
|
+
return ref.type === "topic"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Type guard to check if a ToC item has children
|
|
203
|
+
*/
|
|
204
|
+
export function hasChildren(item: HIGTocItem): boolean {
|
|
205
|
+
return item.children !== undefined && item.children.length > 0
|
|
206
|
+
}
|