@mannisto/astro-metadata 1.0.1 → 2.0.0-alpha.1
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/README.md +427 -82
- package/package.json +35 -22
- package/src/assets/icons/LICENSE.md +35 -0
- package/src/assets/icons/binoculars.svg +1 -0
- package/src/assets/icons/caret-right.svg +1 -0
- package/src/assets/icons/check.svg +1 -0
- package/src/assets/icons/copy.svg +1 -0
- package/src/assets/icons/seal-check-duotone.svg +1 -0
- package/src/assets/icons/seal-question-duotone.svg +1 -0
- package/src/assets/icons/warning-circle-duotone.svg +1 -0
- package/src/assets/icons/warning-duotone.svg +1 -0
- package/src/assets/icons/x.svg +1 -0
- package/src/components/Metadata.astro +145 -0
- package/src/components/index.ts +2 -0
- package/src/integration.d.ts +112 -0
- package/src/integration.js +508 -0
- package/src/lib/checks.d.ts +70 -0
- package/src/lib/checks.js +193 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/favicon-files.d.ts +29 -0
- package/src/lib/favicon-files.js +103 -0
- package/src/lib/favicon-image.ts +124 -0
- package/src/lib/favicon.ts +167 -0
- package/src/lib/head.d.ts +25 -0
- package/src/lib/head.js +141 -0
- package/src/lib/manifest.ts +126 -0
- package/src/lib/rules.d.ts +73 -0
- package/src/lib/rules.js +902 -0
- package/src/lib/schema.ts +208 -0
- package/src/lib/site.ts +72 -0
- package/src/lib/url.ts +79 -0
- package/src/routes/favicon.ts +14 -0
- package/src/routes/manifest.ts +18 -0
- package/src/runtime.ts +16 -0
- package/src/toolbar/app.ts +72 -0
- package/src/toolbar/assets.ts +81 -0
- package/src/toolbar/data.ts +282 -0
- package/src/toolbar/define.ts +7 -0
- package/src/toolbar/env.d.ts +10 -0
- package/src/toolbar/highlight.ts +83 -0
- package/src/toolbar/styles/shared.css +18 -0
- package/src/toolbar/styles/tokens.css +182 -0
- package/src/toolbar/toolbar-badge.ts +78 -0
- package/src/toolbar/ui/alert/alert.css +126 -0
- package/src/toolbar/ui/alert/alert.ts +102 -0
- package/src/toolbar/ui/badge/badge.css +41 -0
- package/src/toolbar/ui/badge/badge.ts +24 -0
- package/src/toolbar/ui/code-block/code-block.css +90 -0
- package/src/toolbar/ui/code-block/code-block.ts +68 -0
- package/src/toolbar/ui/color-swatch/color-swatch.css +15 -0
- package/src/toolbar/ui/color-swatch/color-swatch.ts +31 -0
- package/src/toolbar/ui/icon/icon.css +11 -0
- package/src/toolbar/ui/icon/icon.ts +46 -0
- package/src/toolbar/ui/image/image.css +18 -0
- package/src/toolbar/ui/image/image.ts +58 -0
- package/src/toolbar/ui/inline-code/inline-code.css +16 -0
- package/src/toolbar/ui/inline-code/inline-code.ts +41 -0
- package/src/toolbar/ui/section/section.css +88 -0
- package/src/toolbar/ui/section/section.ts +78 -0
- package/src/toolbar/ui/tab/tab.css +7 -0
- package/src/toolbar/ui/tab/tab.ts +47 -0
- package/src/toolbar/ui/tab-group/tab-group.css +42 -0
- package/src/toolbar/ui/tab-group/tab-group.ts +101 -0
- package/src/toolbar/ui/table/table.css +124 -0
- package/src/toolbar/ui/table/table.ts +155 -0
- package/src/toolbar/views/code-blocks.ts +35 -0
- package/src/toolbar/views/metadata-panel.css +101 -0
- package/src/toolbar/views/metadata-panel.ts +125 -0
- package/src/toolbar/views/overview-view.css +27 -0
- package/src/toolbar/views/overview-view.ts +401 -0
- package/src/toolbar/views/previews/discord.css +70 -0
- package/src/toolbar/views/previews/discord.ts +60 -0
- package/src/toolbar/views/previews/facebook.css +65 -0
- package/src/toolbar/views/previews/facebook.ts +36 -0
- package/src/toolbar/views/previews/google.css +75 -0
- package/src/toolbar/views/previews/google.ts +36 -0
- package/src/toolbar/views/previews/linkedin.css +58 -0
- package/src/toolbar/views/previews/linkedin.ts +34 -0
- package/src/toolbar/views/previews/page.ts +129 -0
- package/src/toolbar/views/previews/platform.ts +14 -0
- package/src/toolbar/views/previews/previews-view.css +37 -0
- package/src/toolbar/views/previews/previews-view.ts +52 -0
- package/src/toolbar/views/previews/slack.css +61 -0
- package/src/toolbar/views/previews/slack.ts +47 -0
- package/src/toolbar/views/previews/twitter.css +89 -0
- package/src/toolbar/views/previews/twitter.ts +57 -0
- package/src/toolbar/views/previews/whatsapp.css +88 -0
- package/src/toolbar/views/previews/whatsapp.ts +65 -0
- package/src/toolbar/views/raw-view.ts +41 -0
- package/index.ts +0 -31
- package/src/components/Canonical.astro +0 -11
- package/src/components/Description.astro +0 -9
- package/src/components/Favicon.astro +0 -88
- package/src/components/Head.astro +0 -96
- package/src/components/Keywords.astro +0 -9
- package/src/components/LanguageAlternates.astro +0 -18
- package/src/components/OpenGraph.astro +0 -75
- package/src/components/Robots.astro +0 -25
- package/src/components/Schema.astro +0 -9
- package/src/components/Title.astro +0 -10
- package/src/components/Twitter.astro +0 -25
- 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,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
|
+
}
|