Skip to content

Commit f7a8887

Browse files
committed
Fix social shares highlighter
1 parent 2bc8805 commit f7a8887

13 files changed

Lines changed: 784 additions & 631 deletions

File tree

_TODO.md

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,6 @@ if (window.matchMedia) {
9393

9494
Maybe change the "ready..." in the hero to "get in touch..."
9595

96-
## Tags
97-
98-
Add a "coreCategory" boolean prop to tags, and sort to only show those on the /articles path. That way we can have languages like Terraform or tech like Helm as tag categories.
99-
100-
Link to the second-order category from the "Skills/Technologies Preview" icons on the home page. They should have descriptions of the tech.
101-
10296
## Search Box
10397

10498
- Show article titles only in drop-down search result box, and dedupe results
@@ -131,6 +125,10 @@ Need a tooltip component for consistency. List to add tooltips to:
131125
- "Report a Bug" in footer
132126
- RSS feed icon in footer
133127

128+
[rerender diagrams when I switched color-scheme](https://github.com/mermaid-js/mermaid/issues/1945)
129+
[mermaid init](https://github.com/hbstack/mermaid/blob/main/assets/hb/modules/mermaid/init.ts)
130+
[theming](https://mermaid.ai/open-source/config/theming.html)
131+
134132
## Services
135133

136134
COAK has two pricing tiers:
@@ -201,56 +199,7 @@ Let's work through each article section by section based on the H2 headers in th
201199

202200
## Stylings
203201

204-
- Better appearance of details and summary elements. The styling in rehype-tailwind is applying to the detail and summary elements generated by remark-custom-blocks.
205202
- [Link](url) [[color=pink target=_blank .centered]] is not picking up styles
206-
- remark-mark-plus highlight color needs adjusted.
207-
- Highlighter component not rendering social share modal on hover and focus.
208-
- Are we using breakpoints correctly - isn't iPhone wider than our `sm` breakpoint?
203+
- Are we using breakpoints correctly - isn't iPhone wider than our `sm` breakpoint? (logical width of iphone 15 is logical width remains 393 or 430 points)
209204
- Add scroll bar under header to show how far down you are on the page while reading
210-
211-
## Add theming to Mermaid
212-
213-
Mermaid theming, horizontal layout for desktop, vertical for mobile, using groups like in Code Tabs to add the media selectors e.g. `[g1:desktop]`
214-
215-
`graph TD`: Top-to-Bottom (Vertical).
216-
`graph LR`: Left-to-Right (Horizontal).
217-
218-
`strategy: 'pre-mermaid'` in Mermaid config in `astro.config.ts`
219-
220-
This strategy replaces the element with a <pre class="mermaid"> element with only the diagram as its child. Given the example, this yields:
221-
222-
```html
223-
<html>
224-
<head>
225-
<meta charset="utf-8" />
226-
</head>
227-
<body>
228-
<pre class="mermaid">
229-
graph TD;
230-
A-->B;
231-
A-->C;
232-
B-->D;
233-
C-->D;
234-
</pre>
235-
<pre class="mermaid">
236-
graph TD;
237-
A-->B;
238-
A-->C;
239-
B-->D;
240-
C-->D;
241-
</pre>
242-
</body>
243-
</html>
244-
```
245-
246-
This allows Mermaid to render the diagram on the client side, for example using:
247-
248-
````typescript
249-
import mermaid from 'mermaid'
250-
251-
mermaid.initialize({ startOnLoad: true })
252-
```
253-
254-
[rerender diagrams when I switched color-scheme](https://github.com/mermaid-js/mermaid/issues/1945)
255-
[mermaid init](https://github.com/hbstack/mermaid/blob/main/assets/hb/modules/mermaid/init.ts)
256-
[theming](https://mermaid.ai/open-source/config/theming.html)
205+
- Add 'featured' to tags

src/components/Code/CodeTabs/client/selectors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,11 @@ export function queryCheckIconElement(scope: ParentNode): HTMLElement | null {
2929
const element = scope.querySelector(SELECTORS.checkIcon)
3030
return isSpanElement(element) ? element : null
3131
}
32+
33+
export function hasHighlighterElement(scope: ParentNode = document): boolean {
34+
return scope.querySelector('highlighter-element') !== null
35+
}
36+
37+
export function hasMastodonModalElement(scope: ParentNode = document): boolean {
38+
return scope.querySelector('mastodon-modal-element') !== null
39+
}

src/components/Code/CodeTabs/index.astro

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,35 @@
88

99
<script>
1010
import { registerCodeTabsWebComponent } from './client'
11+
import { hasHighlighterElement, hasMastodonModalElement } from './client/selectors'
1112
registerCodeTabsWebComponent()
13+
14+
const registerOptionalMarkdownWebComponents = async () => {
15+
if (hasHighlighterElement()) {
16+
try {
17+
const { registerHighlighterWebComponent } = await import(
18+
'@components/Social/Highlighter/client'
19+
)
20+
registerHighlighterWebComponent()
21+
} catch {
22+
// Best effort only.
23+
}
24+
}
25+
26+
if (hasMastodonModalElement()) {
27+
try {
28+
const { registerMastodonModalWebComponent } = await import(
29+
'@components/Social/Mastodon/client'
30+
)
31+
registerMastodonModalWebComponent()
32+
} catch {
33+
// Best effort only.
34+
}
35+
}
36+
}
37+
38+
registerOptionalMarkdownWebComponents()
39+
document.addEventListener('astro:page-load', () => {
40+
registerOptionalMarkdownWebComponents()
41+
})
1242
</script>

src/components/Social/Highlighter/client/__tests__/index.spec.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TestError } from '@test/errors'
44
import HighlighterFixture from '@components/Social/Highlighter/client/__fixtures__/index.fixture.astro'
55
import type { HighlighterElement } from '@components/Social/Highlighter/client/index'
66
import type { WebComponentModule } from '@components/scripts/@types/webComponentModule'
7-
import { copyToClipboard, nativeShare } from '@components/Social/common'
7+
import { nativeShare } from '@components/Social/common'
88
import { MastodonModal } from '@components/Social/Mastodon/client'
99
import * as elementUtils from '@components/scripts/utils'
1010
import { executeRender, withJsdomEnvironment } from '@test/unit/helpers/litRuntime'
@@ -55,7 +55,6 @@ vi.mock('@components/scripts/errors', () => ({
5555

5656
vi.mock('@components/Social/common', () => ({
5757
platforms: mockPlatforms,
58-
copyToClipboard: vi.fn().mockResolvedValue(true),
5958
nativeShare: vi.fn().mockResolvedValue(false),
6059
}))
6160

@@ -66,7 +65,6 @@ vi.mock('@components/Social/Mastodon/client', () => ({
6665
}))
6766

6867
type HighlighterModule = WebComponentModule<HighlighterElement>
69-
const mockCopyToClipboard = vi.mocked(copyToClipboard)
7068
const mockNativeShare = vi.mocked(nativeShare)
7169
const mockMastodonModal = vi.mocked(MastodonModal)
7270
const originalDefineCustomElement = elementUtils.defineCustomElement
@@ -158,7 +156,7 @@ describe('HighlighterElement', () => {
158156
await renderHighlighter(async ({ element }) => {
159157
expect(element.shadowRoot).toBeNull()
160158
expect(element.querySelector('.share-dialog')).not.toBeNull()
161-
expect(element.querySelectorAll('.share-button')).toHaveLength(mockPlatforms.length + 1)
159+
expect(element.querySelectorAll('.share-button')).toHaveLength(mockPlatforms.length)
162160
expect(element.getAttribute('aria-label')).toBe(defaultProps.ariaLabel)
163161

164162
const trigger = element.querySelector('.highlighter__trigger') as HTMLButtonElement | null
@@ -199,32 +197,35 @@ describe('HighlighterElement', () => {
199197
})
200198
})
201199

202-
const getLastShareEvent = (listener: ReturnType<typeof vi.fn>) =>
203-
listener.mock.calls.at(-1)?.[0] as CustomEvent<{ platform: string }> | undefined
204-
205-
test('copy button copies highlighted text and emits event', async () => {
200+
test('shows dialog when trigger receives focus-visible', async () => {
206201
await renderHighlighter(async ({ element, window }) => {
207-
const shareListener = vi.fn()
208-
element.addEventListener('highlighter:share', shareListener)
209-
210-
mockCopyToClipboard.mockResolvedValueOnce(true)
211-
getShareButton(element, 'copy').click()
202+
const dialog = element.querySelector('.share-dialog') as HTMLElement | null
203+
expect(dialog?.getAttribute('aria-hidden')).toBe('true')
212204

213-
await flushMicrotasks()
205+
const trigger = element.querySelector('.highlighter__trigger') as HTMLButtonElement | null
206+
expect(trigger).toBeTruthy()
214207

215-
expect(mockCopyToClipboard).toHaveBeenCalledWith(
216-
`"${defaultProps.content}" ${window.location.href}`
217-
)
218-
expect(shareListener).toHaveBeenCalledTimes(1)
219-
expect(getLastShareEvent(shareListener)?.detail.platform).toBe('copy')
208+
if (!trigger) {
209+
return
210+
}
220211

221-
const status = element.querySelector('[data-highlighter-status]') as HTMLElement | null
222-
expect(status?.textContent).toContain('Link copied')
212+
const originalMatches = trigger.matches.bind(trigger)
213+
trigger.matches = (selector: string) => {
214+
if (selector === ':focus-visible') {
215+
return true
216+
}
217+
return originalMatches(selector)
218+
}
223219

224-
element.removeEventListener('highlighter:share', shareListener)
220+
trigger.dispatchEvent(new window.FocusEvent('focusin', { bubbles: true }))
221+
expect(dialog?.getAttribute('aria-hidden')).toBe('false')
222+
expect(trigger.getAttribute('aria-expanded')).toBe('true')
225223
})
226224
})
227225

226+
const getLastShareEvent = (listener: ReturnType<typeof vi.fn>) =>
227+
listener.mock.calls.at(-1)?.[0] as CustomEvent<{ platform: string }> | undefined
228+
228229
test('opens share URL when native share is unavailable', async () => {
229230
await renderHighlighter(async ({ element, window }) => {
230231
mockNativeShare.mockResolvedValueOnce(false)

0 commit comments

Comments
 (0)