@mannisto/astro-metadata 1.0.2 → 2.0.0-alpha.2

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 (102) hide show
  1. package/README.md +427 -82
  2. package/package.json +25 -14
  3. package/src/assets/icons/LICENSE.md +35 -0
  4. package/src/assets/icons/binoculars.svg +1 -0
  5. package/src/assets/icons/caret-right.svg +1 -0
  6. package/src/assets/icons/check.svg +1 -0
  7. package/src/assets/icons/copy.svg +1 -0
  8. package/src/assets/icons/seal-check-duotone.svg +1 -0
  9. package/src/assets/icons/seal-question-duotone.svg +1 -0
  10. package/src/assets/icons/warning-circle-duotone.svg +1 -0
  11. package/src/assets/icons/warning-duotone.svg +1 -0
  12. package/src/assets/icons/x.svg +1 -0
  13. package/src/components/Metadata.astro +145 -0
  14. package/src/components/index.ts +2 -0
  15. package/src/integration.d.ts +112 -0
  16. package/src/integration.js +508 -0
  17. package/src/lib/checks.d.ts +70 -0
  18. package/src/lib/checks.js +193 -0
  19. package/src/lib/config.ts +111 -0
  20. package/src/lib/favicon-files.d.ts +29 -0
  21. package/src/lib/favicon-files.js +103 -0
  22. package/src/lib/favicon-image.ts +124 -0
  23. package/src/lib/favicon.ts +167 -0
  24. package/src/lib/head.d.ts +25 -0
  25. package/src/lib/head.js +141 -0
  26. package/src/lib/manifest.ts +126 -0
  27. package/src/lib/rules.d.ts +73 -0
  28. package/src/lib/rules.js +929 -0
  29. package/src/lib/schema.ts +208 -0
  30. package/src/lib/site.ts +72 -0
  31. package/src/lib/url.ts +79 -0
  32. package/src/routes/favicon.ts +14 -0
  33. package/src/routes/manifest.ts +18 -0
  34. package/src/runtime.ts +16 -0
  35. package/src/toolbar/app.ts +72 -0
  36. package/src/toolbar/assets.ts +81 -0
  37. package/src/toolbar/data.ts +282 -0
  38. package/src/toolbar/define.ts +7 -0
  39. package/src/toolbar/env.d.ts +10 -0
  40. package/src/toolbar/highlight.ts +83 -0
  41. package/src/toolbar/styles/shared.css +18 -0
  42. package/src/toolbar/styles/tokens.css +182 -0
  43. package/src/toolbar/toolbar-badge.ts +78 -0
  44. package/src/toolbar/ui/alert/alert.css +126 -0
  45. package/src/toolbar/ui/alert/alert.ts +102 -0
  46. package/src/toolbar/ui/badge/badge.css +41 -0
  47. package/src/toolbar/ui/badge/badge.ts +24 -0
  48. package/src/toolbar/ui/code-block/code-block.css +90 -0
  49. package/src/toolbar/ui/code-block/code-block.ts +68 -0
  50. package/src/toolbar/ui/color-swatch/color-swatch.css +15 -0
  51. package/src/toolbar/ui/color-swatch/color-swatch.ts +31 -0
  52. package/src/toolbar/ui/icon/icon.css +11 -0
  53. package/src/toolbar/ui/icon/icon.ts +46 -0
  54. package/src/toolbar/ui/image/image.css +18 -0
  55. package/src/toolbar/ui/image/image.ts +58 -0
  56. package/src/toolbar/ui/inline-code/inline-code.css +16 -0
  57. package/src/toolbar/ui/inline-code/inline-code.ts +41 -0
  58. package/src/toolbar/ui/section/section.css +88 -0
  59. package/src/toolbar/ui/section/section.ts +78 -0
  60. package/src/toolbar/ui/tab/tab.css +7 -0
  61. package/src/toolbar/ui/tab/tab.ts +47 -0
  62. package/src/toolbar/ui/tab-group/tab-group.css +42 -0
  63. package/src/toolbar/ui/tab-group/tab-group.ts +101 -0
  64. package/src/toolbar/ui/table/table.css +124 -0
  65. package/src/toolbar/ui/table/table.ts +155 -0
  66. package/src/toolbar/views/code-blocks.ts +35 -0
  67. package/src/toolbar/views/metadata-panel.css +105 -0
  68. package/src/toolbar/views/metadata-panel.ts +125 -0
  69. package/src/toolbar/views/overview-view.css +27 -0
  70. package/src/toolbar/views/overview-view.ts +401 -0
  71. package/src/toolbar/views/previews/discord.css +70 -0
  72. package/src/toolbar/views/previews/discord.ts +60 -0
  73. package/src/toolbar/views/previews/facebook.css +65 -0
  74. package/src/toolbar/views/previews/facebook.ts +36 -0
  75. package/src/toolbar/views/previews/google.css +75 -0
  76. package/src/toolbar/views/previews/google.ts +36 -0
  77. package/src/toolbar/views/previews/linkedin.css +58 -0
  78. package/src/toolbar/views/previews/linkedin.ts +34 -0
  79. package/src/toolbar/views/previews/page.ts +129 -0
  80. package/src/toolbar/views/previews/platform.ts +14 -0
  81. package/src/toolbar/views/previews/previews-view.css +37 -0
  82. package/src/toolbar/views/previews/previews-view.ts +52 -0
  83. package/src/toolbar/views/previews/slack.css +61 -0
  84. package/src/toolbar/views/previews/slack.ts +47 -0
  85. package/src/toolbar/views/previews/twitter.css +89 -0
  86. package/src/toolbar/views/previews/twitter.ts +57 -0
  87. package/src/toolbar/views/previews/whatsapp.css +88 -0
  88. package/src/toolbar/views/previews/whatsapp.ts +65 -0
  89. package/src/toolbar/views/raw-view.ts +41 -0
  90. package/index.ts +0 -31
  91. package/src/components/Canonical.astro +0 -11
  92. package/src/components/Description.astro +0 -9
  93. package/src/components/Favicon.astro +0 -96
  94. package/src/components/Head.astro +0 -94
  95. package/src/components/Keywords.astro +0 -9
  96. package/src/components/LanguageAlternates.astro +0 -18
  97. package/src/components/OpenGraph.astro +0 -75
  98. package/src/components/Robots.astro +0 -25
  99. package/src/components/Schema.astro +0 -18
  100. package/src/components/Title.astro +0 -13
  101. package/src/components/Twitter.astro +0 -27
  102. package/src/lib/metadata.ts +0 -77
@@ -0,0 +1,88 @@
1
+ /* A box around the section, so its content belongs together */
2
+ :host {
3
+ display: block;
4
+ padding: var(--space-300);
5
+ border: 1px solid var(--zinc-800);
6
+ border-radius: var(--radius);
7
+ background: var(--zinc-900);
8
+ }
9
+
10
+ /* A closed or empty section shows only its heading */
11
+ :host([closed]),
12
+ :host([empty]) {
13
+ h2 {
14
+ margin-bottom: 0;
15
+ }
16
+ }
17
+
18
+ /* An empty section has no caret */
19
+ :host([empty]) astro-metadata-icon {
20
+ visibility: hidden;
21
+ }
22
+
23
+ /* The caret turns down when the section is open */
24
+ :host(:not([closed])) astro-metadata-icon {
25
+ transform: rotate(90deg);
26
+ }
27
+
28
+ /* The heading: small, monospace, uppercase, wide letter spacing */
29
+ h2 {
30
+ margin: 0 0 var(--space-200);
31
+ font-family: var(--font-mono);
32
+ font-size: var(--font-size-small);
33
+ font-weight: 500;
34
+ color: var(--zinc-400);
35
+ text-transform: uppercase;
36
+ letter-spacing: 0.05em;
37
+ }
38
+
39
+ /* The heading is a button that closes or opens the section */
40
+ button {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: var(--space-100);
44
+ width: 100%;
45
+ padding: 0;
46
+ font: inherit;
47
+ color: inherit;
48
+ text-align: left;
49
+ text-transform: inherit;
50
+ letter-spacing: inherit;
51
+ cursor: pointer;
52
+ border: 0;
53
+ background: none;
54
+
55
+ &:hover:not(:disabled) {
56
+ color: var(--zinc-100);
57
+ }
58
+
59
+ /* An empty section: a dim heading and "None" at the right */
60
+ &:disabled {
61
+ color: var(--zinc-600);
62
+ cursor: default;
63
+ }
64
+
65
+ astro-metadata-icon {
66
+ transition: transform 120ms;
67
+
68
+ --icon-size: 12px;
69
+ }
70
+ }
71
+
72
+ .none {
73
+ margin-left: auto;
74
+ font-size: var(--font-size-tiny);
75
+ text-transform: none;
76
+ letter-spacing: normal;
77
+ }
78
+
79
+ /* minmax(0, 1fr): a long line, e.g. in a code block, scrolls inside its block */
80
+ .content {
81
+ display: grid;
82
+ grid-template-columns: minmax(0, 1fr);
83
+ gap: var(--space-200);
84
+
85
+ &[hidden] {
86
+ display: none;
87
+ }
88
+ }
@@ -0,0 +1,78 @@
1
+ import { html, LitElement, nothing, unsafeCSS } from "lit"
2
+
3
+ import { define } from "../../define.ts"
4
+
5
+ import "../icon/icon.ts"
6
+
7
+ import styles from "./section.css?inline"
8
+
9
+ /**
10
+ * A part of a view: a heading and its content. Click the heading to close or open the section.
11
+ * At the start, the first section of a view is open and the other sections are closed.
12
+ * A section without a heading is always open.
13
+ * An `empty` section shows only its heading and "None", and the user can not open it.
14
+ *
15
+ * @example <astro-metadata-section heading="General">...</astro-metadata-section>
16
+ */
17
+ export class Section extends LitElement {
18
+ static properties = {
19
+ heading: {},
20
+ closed: { type: Boolean, reflect: true },
21
+ empty: { type: Boolean, reflect: true },
22
+ }
23
+
24
+ static styles = unsafeCSS(styles)
25
+
26
+ declare heading: string | undefined
27
+ declare closed: boolean
28
+ declare empty: boolean
29
+
30
+ // True after the first connect. A section that moves keeps its state.
31
+ private started = false
32
+
33
+ constructor() {
34
+ super()
35
+ this.closed = false
36
+ this.empty = false
37
+ }
38
+
39
+ connectedCallback(): void {
40
+ super.connectedCallback()
41
+ if (this.started) return
42
+
43
+ this.started = true
44
+ const first = this.parentNode?.querySelector("astro-metadata-section")
45
+ this.closed = Boolean(this.heading) && first !== this
46
+ }
47
+
48
+ toggle(): void {
49
+ if (!this.heading) return
50
+
51
+ this.closed = !this.closed
52
+ }
53
+
54
+ render() {
55
+ return html`
56
+ ${
57
+ this.heading
58
+ ? html`
59
+ <h2>
60
+ <button
61
+ type="button"
62
+ aria-expanded=${!this.closed && !this.empty}
63
+ ?disabled=${this.empty}
64
+ @click=${this.toggle}
65
+ >
66
+ <astro-metadata-icon name="caret-right"></astro-metadata-icon>
67
+ ${this.heading} ${this.empty ? html`<span class="none">None</span>` : nothing}
68
+ </button>
69
+ </h2>
70
+ `
71
+ : nothing
72
+ }
73
+ <div class="content" ?hidden=${this.closed || this.empty}><slot></slot></div>
74
+ `
75
+ }
76
+ }
77
+
78
+ define("astro-metadata-section", Section)
@@ -0,0 +1,7 @@
1
+ :host {
2
+ display: block;
3
+ }
4
+
5
+ :host([hidden]) {
6
+ display: none;
7
+ }
@@ -0,0 +1,47 @@
1
+ import { html, LitElement, unsafeCSS } from "lit"
2
+
3
+ import { define } from "../../define.ts"
4
+ import type { Level } from "../badge/badge.ts"
5
+ import styles from "./tab.css?inline"
6
+
7
+ /**
8
+ * One tab of a `<astro-metadata-tab-group>`: its label in the tab bar, and its content.
9
+ *
10
+ * @example <astro-metadata-tab name="overview" label="Overview" count="2" level="warning">
11
+ */
12
+ export class Tab extends LitElement {
13
+ static properties = {
14
+ name: {},
15
+ label: {},
16
+ count: { type: Number },
17
+ level: {},
18
+ }
19
+
20
+ static styles = unsafeCSS(styles)
21
+
22
+ declare name: string
23
+ declare label: string
24
+ /** A number in a badge after the label. A count of 0 shows no badge. */
25
+ declare count: number | undefined
26
+ /** The level of the badge. */
27
+ declare level: Level | undefined
28
+
29
+ constructor() {
30
+ super()
31
+ this.name = ""
32
+ this.label = ""
33
+ this.count = undefined
34
+ this.level = undefined
35
+ }
36
+
37
+ // Tells the tab group that the label or the count changed
38
+ updated(): void {
39
+ this.dispatchEvent(new Event("tab-update", { bubbles: true }))
40
+ }
41
+
42
+ render() {
43
+ return html`<slot></slot>`
44
+ }
45
+ }
46
+
47
+ define("astro-metadata-tab", Tab)
@@ -0,0 +1,42 @@
1
+ :host {
2
+ display: flex;
3
+ flex-direction: column;
4
+ min-height: 0;
5
+ }
6
+
7
+ nav {
8
+ display: flex;
9
+ gap: var(--space-100);
10
+ padding: var(--space-200) var(--space-400) 0;
11
+ border-bottom: 1px solid var(--zinc-800);
12
+ }
13
+
14
+ button {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: var(--space-150);
18
+ padding: var(--space-200);
19
+ font: inherit;
20
+ color: var(--zinc-400);
21
+ cursor: pointer;
22
+ background: none;
23
+ border: 0;
24
+ border-bottom: 2px solid transparent;
25
+ transition: color 120ms;
26
+
27
+ &:hover {
28
+ color: var(--zinc-100);
29
+ }
30
+
31
+ &[aria-selected="true"] {
32
+ color: var(--zinc-100);
33
+ border-bottom-color: var(--zinc-100);
34
+ }
35
+ }
36
+
37
+ /* The content of the selected tab scrolls, the tab bar stays */
38
+ .panels {
39
+ flex: 1;
40
+ padding: var(--space-400);
41
+ overflow: auto;
42
+ }
@@ -0,0 +1,101 @@
1
+ import { html, LitElement, nothing, unsafeCSS } from "lit"
2
+
3
+ import "../badge/badge.ts"
4
+
5
+ import { define } from "../../define.ts"
6
+ import type { Tab } from "../tab/tab.ts"
7
+
8
+ import "../tab/tab.ts"
9
+
10
+ import styles from "./tab-group.css?inline"
11
+
12
+ /**
13
+ * A tab bar and the content of the selected tab. The tabs are its
14
+ * `<astro-metadata-tab>` children. It fires a `change` event with the name of the tab.
15
+ *
16
+ * @example
17
+ * <astro-metadata-tab-group selected="overview">
18
+ * <astro-metadata-tab name="overview" label="Overview">...</astro-metadata-tab>
19
+ * <astro-metadata-tab name="code" label="Code">...</astro-metadata-tab>
20
+ * </astro-metadata-tab-group>
21
+ */
22
+ export class TabGroup extends LitElement {
23
+ static properties = {
24
+ selected: { reflect: true },
25
+ tabs: { state: true },
26
+ }
27
+
28
+ static styles = unsafeCSS(styles)
29
+
30
+ declare selected: string | undefined
31
+ declare tabs: Tab[]
32
+
33
+ constructor() {
34
+ super()
35
+ this.selected = undefined
36
+ this.tabs = []
37
+ this.addEventListener("tab-update", () => {
38
+ this.requestUpdate()
39
+ })
40
+ }
41
+
42
+ onSlotChange(event: Event): void {
43
+ const slot = event.target as HTMLSlotElement
44
+ this.tabs = slot.assignedElements().filter((element): element is Tab => {
45
+ return element.tagName.toLowerCase() === "astro-metadata-tab"
46
+ })
47
+ }
48
+
49
+ select(name: string): void {
50
+ this.selected = name
51
+ this.dispatchEvent(new CustomEvent("change", { detail: name }))
52
+ }
53
+
54
+ // Shows only the selected tab. Without a selection, the first tab shows.
55
+ updated(): void {
56
+ const selected = this.current()
57
+ for (const tab of this.tabs) {
58
+ tab.hidden = tab.name !== selected
59
+ }
60
+ }
61
+
62
+ current(): string | undefined {
63
+ const known = this.tabs.some((tab) => {
64
+ return tab.name === this.selected
65
+ })
66
+ return known ? this.selected : this.tabs[0]?.name
67
+ }
68
+
69
+ render() {
70
+ const selected = this.current()
71
+ return html`
72
+ <nav role="tablist">
73
+ ${this.tabs.map((tab) => {
74
+ return html`
75
+ <button
76
+ role="tab"
77
+ aria-selected=${tab.name === selected}
78
+ @click=${() => {
79
+ this.select(tab.name)
80
+ }}
81
+ >
82
+ ${tab.label}
83
+ ${
84
+ tab.count
85
+ ? html`<astro-metadata-badge level=${tab.level ?? "info"}>
86
+ ${tab.count}
87
+ </astro-metadata-badge>`
88
+ : nothing
89
+ }
90
+ </button>
91
+ `
92
+ })}
93
+ </nav>
94
+ <div class="panels" role="tabpanel">
95
+ <slot @slotchange=${this.onSlotChange}></slot>
96
+ </div>
97
+ `
98
+ }
99
+ }
100
+
101
+ define("astro-metadata-tab-group", TabGroup)
@@ -0,0 +1,124 @@
1
+ /* A framed container, a hairline between the rows, and a hover color */
2
+ :host {
3
+ display: block;
4
+ }
5
+
6
+ table {
7
+ width: 100%;
8
+ overflow: hidden;
9
+ font-size: var(--font-size-small);
10
+ border-spacing: 0;
11
+ border: 1px solid var(--zinc-800);
12
+ border-radius: var(--radius-medium);
13
+ background: var(--zinc-950);
14
+ }
15
+
16
+ th,
17
+ td {
18
+ padding: var(--space-200) var(--space-400);
19
+ text-align: left;
20
+ vertical-align: top;
21
+ border-bottom: 1px solid var(--zinc-800);
22
+ transition: background-color 120ms;
23
+ }
24
+
25
+ tr {
26
+ &:last-child :is(th, td) {
27
+ border-bottom: 0;
28
+ }
29
+
30
+ &:hover :is(th, td) {
31
+ background: var(--zinc-800);
32
+ }
33
+
34
+ /* The row of a problem after a click on the problem */
35
+ &.highlight :is(th, td) {
36
+ animation: highlight 1.6s ease-out;
37
+ }
38
+
39
+ /* The child rows are indented under their row */
40
+ &.child th {
41
+ padding-left: calc(var(--space-400) + var(--space-400));
42
+ }
43
+ }
44
+
45
+ th {
46
+ width: 25%;
47
+ font-weight: 400;
48
+ color: var(--zinc-400);
49
+ }
50
+
51
+ td {
52
+ color: var(--zinc-100);
53
+ overflow-wrap: anywhere;
54
+
55
+ /* The third column: a short note, e.g. the length of a text */
56
+ &.note {
57
+ width: 1%;
58
+ padding-left: 0;
59
+ font-family: var(--font-mono);
60
+ font-size: var(--font-size-tiny);
61
+ color: var(--zinc-500);
62
+ text-align: right;
63
+ white-space: nowrap;
64
+ word-spacing: -0.2em;
65
+
66
+ &.near {
67
+ color: var(--yellow-400);
68
+ }
69
+
70
+ &.over {
71
+ color: var(--orange-400);
72
+ }
73
+ }
74
+ }
75
+
76
+ /* A value with a small image before it, e.g. the favicon */
77
+ .favicon {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: var(--space-200);
81
+
82
+ img {
83
+ flex: none;
84
+ object-fit: contain;
85
+ }
86
+ }
87
+
88
+ .empty {
89
+ color: var(--zinc-400);
90
+ }
91
+
92
+ /* A row with child rows: its label opens and closes them */
93
+ .toggle {
94
+ display: inline-flex;
95
+ align-items: center;
96
+ gap: var(--space-100);
97
+ margin-left: calc(var(--space-100) * -1);
98
+ padding: 0;
99
+ font: inherit;
100
+ color: inherit;
101
+ cursor: pointer;
102
+ background: none;
103
+ border: 0;
104
+
105
+ --icon-size: 12px;
106
+
107
+ &:hover {
108
+ color: var(--zinc-100);
109
+ }
110
+
111
+ astro-metadata-icon {
112
+ transition: transform 120ms;
113
+ }
114
+
115
+ &.open astro-metadata-icon {
116
+ transform: rotate(90deg);
117
+ }
118
+ }
119
+
120
+ @keyframes highlight {
121
+ from {
122
+ background: var(--blue-900);
123
+ }
124
+ }
@@ -0,0 +1,155 @@
1
+ import { html, LitElement, nothing, unsafeCSS } from "lit"
2
+
3
+ import { define } from "../../define.ts"
4
+
5
+ import "../icon/icon.ts"
6
+
7
+ import { code } from "../inline-code/inline-code.ts"
8
+ import styles from "./table.css?inline"
9
+
10
+ /**
11
+ * A row of a table: a label and a value. A row without a value shows "none".
12
+ */
13
+ export type TableRow = {
14
+ label: string
15
+ /** A key to find the row, e.g. "og:title". `highlight()` uses it. */
16
+ key?: string
17
+ value?: unknown
18
+ /** A short note in a third column, e.g. the length of a text: "30 / 60". */
19
+ note?: string
20
+ /** The color of the note: "near" a limit (yellow), or "over" it (orange). */
21
+ noteLevel?: "near" | "over"
22
+ /** Shows the label as code, e.g. a tag name such as "og:title". */
23
+ labelCode?: boolean
24
+ /** Rows under this row. A click on the label opens and closes them. */
25
+ children?: TableRow[]
26
+ }
27
+
28
+ /**
29
+ * A table of labels and values.
30
+ *
31
+ * @example <astro-metadata-table .rows=${[{ label: "Title", value: title }]}></astro-metadata-table>
32
+ */
33
+ export class Table extends LitElement {
34
+ static properties = {
35
+ rows: { attribute: false },
36
+ opened: { state: true },
37
+ highlighted: { state: true },
38
+ }
39
+ static styles = unsafeCSS(styles)
40
+
41
+ declare rows: TableRow[]
42
+ /** The indexes of the open rows. */
43
+ declare opened: Set<number>
44
+ /** The key of the marked row. */
45
+ declare highlighted: string | undefined
46
+
47
+ constructor() {
48
+ super()
49
+ this.rows = []
50
+ this.opened = new Set()
51
+ this.highlighted = undefined
52
+ }
53
+
54
+ /**
55
+ * Scrolls to the row with a key and marks it for a moment.
56
+ *
57
+ * @returns False when the table has no row with the key.
58
+ */
59
+ async highlight(key: string): Promise<boolean> {
60
+ const found = this.rows.some((row) => {
61
+ return row.key === key
62
+ })
63
+ if (!found) return false
64
+
65
+ // A new value restarts the animation of a row that is already marked
66
+ this.highlighted = undefined
67
+ await this.updateComplete
68
+ this.highlighted = key
69
+ await this.updateComplete
70
+ this.renderRoot
71
+ .querySelector("tr.highlight")
72
+ ?.scrollIntoView({ block: "center", behavior: "smooth" })
73
+ return true
74
+ }
75
+
76
+ renderValue(row: TableRow) {
77
+ if (row.value === undefined || row.value === null || row.value === "") {
78
+ return html`<span class="empty">none</span>`
79
+ }
80
+ return row.value
81
+ }
82
+
83
+ toggle(index: number): void {
84
+ const opened = new Set(this.opened)
85
+ if (opened.has(index)) opened.delete(index)
86
+ else opened.add(index)
87
+ this.opened = opened
88
+ }
89
+
90
+ renderLabel(row: TableRow, index: number) {
91
+ const label = row.labelCode ? code(row.label) : row.label
92
+ if (!row.children?.length) return label
93
+
94
+ const open = this.opened.has(index)
95
+ return html`
96
+ <button
97
+ class="toggle ${open ? "open" : ""}"
98
+ aria-expanded=${open}
99
+ @click=${() => {
100
+ this.toggle(index)
101
+ }}
102
+ >
103
+ <astro-metadata-icon name="caret-right"></astro-metadata-icon>
104
+ ${label}
105
+ </button>
106
+ `
107
+ }
108
+
109
+ renderRow(row: TableRow, label: unknown, notes: boolean, child: boolean) {
110
+ return html`
111
+ <tr
112
+ class="${child ? "child" : ""} ${row.key && row.key === this.highlighted ? "highlight" : ""}"
113
+ >
114
+ <th>${label}</th>
115
+ <td>${this.renderValue(row)}</td>
116
+ ${
117
+ notes
118
+ ? html`<td class="note ${row.noteLevel ?? ""}">${row.note ?? nothing}</td>`
119
+ : nothing
120
+ }
121
+ </tr>
122
+ `
123
+ }
124
+
125
+ render() {
126
+ // The third column only shows in a table with notes
127
+ const all = this.rows.flatMap((row) => {
128
+ return [row, ...(row.children ?? [])]
129
+ })
130
+ const notes = all.some((row) => {
131
+ return row.note
132
+ })
133
+
134
+ return html`
135
+ <table>
136
+ ${this.rows.map((row, index) => {
137
+ const children = this.opened.has(index) ? (row.children ?? []) : []
138
+ return html`
139
+ ${this.renderRow(row, this.renderLabel(row, index), notes, false)}
140
+ ${children.map((child) => {
141
+ return this.renderRow(
142
+ child,
143
+ child.labelCode ? code(child.label) : child.label,
144
+ notes,
145
+ true
146
+ )
147
+ })}
148
+ `
149
+ })}
150
+ </table>
151
+ `
152
+ }
153
+ }
154
+
155
+ define("astro-metadata-table", Table)
@@ -0,0 +1,35 @@
1
+ import { html, nothing } from "lit"
2
+
3
+ import type { JsonLd, ManifestData } from "../data.ts"
4
+
5
+ import "../ui/code-block/code-block.ts"
6
+
7
+ /**
8
+ * The JSON-LD scripts as code blocks, with their types as the label.
9
+ * A script with invalid JSON shows its source and the error.
10
+ */
11
+ export function schemaBlocks(schemas: JsonLd[]) {
12
+ return schemas.map((schema) => {
13
+ const source = schema.error ? schema.source : JSON.stringify(schema.data, null, 2)
14
+ const label = schema.error ? `Invalid JSON: ${schema.error}` : schema.types.join(", ")
15
+ return html`
16
+ <astro-metadata-code .code=${source} language="json" label=${label}></astro-metadata-code>
17
+ `
18
+ })
19
+ }
20
+
21
+ /**
22
+ * The web app manifest as a code block, with its URL as the label, or the load error.
23
+ */
24
+ export function manifestBlock(manifest: ManifestData | undefined) {
25
+ if (!manifest) return nothing
26
+ if (manifest.error) return html`<p class="empty">${manifest.url}: ${manifest.error}</p>`
27
+
28
+ return html`
29
+ <astro-metadata-code
30
+ .code=${JSON.stringify(manifest.data, null, 2)}
31
+ language="json"
32
+ label=${manifest.url}
33
+ ></astro-metadata-code>
34
+ `
35
+ }