@slidev/client 52.19.0 → 52.19.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.
- package/builtin/CodeBlockWrapper.vue +7 -1
- package/builtin/KaTexBlockWrapper.vue +7 -1
- package/builtin/Tweet.vue +14 -3
- package/logic/tweet.test.ts +33 -0
- package/logic/tweet.ts +37 -0
- package/package.json +22 -22
|
@@ -78,13 +78,19 @@ onMounted(() => {
|
|
|
78
78
|
return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
let hiddenByRange = false
|
|
82
|
+
|
|
81
83
|
watchEffect(() => {
|
|
82
84
|
if (!el.value)
|
|
83
85
|
return
|
|
84
86
|
|
|
85
87
|
let rangeStr = props.ranges[index.value] ?? finallyRange.value
|
|
86
88
|
const hide = rangeStr === 'hide'
|
|
87
|
-
|
|
89
|
+
// Only toggle the class when a `hide` range set it, so a click directive on the same element keeps control
|
|
90
|
+
if (hide || hiddenByRange) {
|
|
91
|
+
el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)
|
|
92
|
+
hiddenByRange = hide
|
|
93
|
+
}
|
|
88
94
|
if (hide)
|
|
89
95
|
rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
|
|
90
96
|
|
|
@@ -68,13 +68,19 @@ onMounted(() => {
|
|
|
68
68
|
return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
let hiddenByRange = false
|
|
72
|
+
|
|
71
73
|
watchEffect(() => {
|
|
72
74
|
if (!el.value)
|
|
73
75
|
return
|
|
74
76
|
|
|
75
77
|
let rangeStr = props.ranges[index.value] ?? finallyRange.value
|
|
76
78
|
const hide = rangeStr === 'hide'
|
|
77
|
-
|
|
79
|
+
// Only toggle the class when a `hide` range set it, so a click directive on the same element keeps control
|
|
80
|
+
if (hide || hiddenByRange) {
|
|
81
|
+
el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)
|
|
82
|
+
hiddenByRange = hide
|
|
83
|
+
}
|
|
78
84
|
if (hide)
|
|
79
85
|
rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
|
|
80
86
|
|
package/builtin/Tweet.vue
CHANGED
|
@@ -4,14 +4,17 @@ A simple wrapper for embedded Tweet
|
|
|
4
4
|
Usage:
|
|
5
5
|
|
|
6
6
|
<Tweet id="20" />
|
|
7
|
+
<Tweet url="https://x.com/jack/status/20" />
|
|
7
8
|
-->
|
|
8
9
|
|
|
9
10
|
<script setup lang="ts">
|
|
10
11
|
import { onMounted, ref } from 'vue'
|
|
11
12
|
import { isDark } from '../logic/dark'
|
|
13
|
+
import { resolveTweetId } from '../logic/tweet'
|
|
12
14
|
|
|
13
15
|
const props = defineProps<{
|
|
14
|
-
id
|
|
16
|
+
id?: string | number
|
|
17
|
+
url?: string
|
|
15
18
|
scale?: string | number
|
|
16
19
|
conversation?: string
|
|
17
20
|
cards?: 'hidden' | 'visible'
|
|
@@ -23,6 +26,14 @@ const loaded = ref(false)
|
|
|
23
26
|
const tweetNotFound = ref(false)
|
|
24
27
|
|
|
25
28
|
async function create(retries = 10) {
|
|
29
|
+
const tweetId = resolveTweetId(props.id, props.url)
|
|
30
|
+
if (!tweetId) {
|
|
31
|
+
loaded.value = true
|
|
32
|
+
tweetNotFound.value = true
|
|
33
|
+
console.error('Tweet requires a valid id or X post URL.')
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
// @ts-expect-error global
|
|
27
38
|
if (!window.twttr?.widgets?.createTweet) {
|
|
28
39
|
if (retries <= 0)
|
|
@@ -32,7 +43,7 @@ async function create(retries = 10) {
|
|
|
32
43
|
}
|
|
33
44
|
// @ts-expect-error global
|
|
34
45
|
const element = await window.twttr.widgets.createTweet(
|
|
35
|
-
|
|
46
|
+
tweetId,
|
|
36
47
|
tweet.value,
|
|
37
48
|
{
|
|
38
49
|
theme: isDark.value ? 'dark' : 'light',
|
|
@@ -56,7 +67,7 @@ onMounted(() => {
|
|
|
56
67
|
<div v-if="!loaded || tweetNotFound" class="w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50">
|
|
57
68
|
<div class="m-auto animate-pulse text-4xl">
|
|
58
69
|
<div class="i-carbon:logo-twitter" />
|
|
59
|
-
<span v-if="tweetNotFound">Could not load tweet
|
|
70
|
+
<span v-if="tweetNotFound">Could not load tweet</span>
|
|
60
71
|
</div>
|
|
61
72
|
</div>
|
|
62
73
|
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { resolveTweetId } from './tweet'
|
|
3
|
+
|
|
4
|
+
describe('resolveTweetId', () => {
|
|
5
|
+
it('keeps the existing id input', () => {
|
|
6
|
+
expect(resolveTweetId(123, undefined)).toBe('123')
|
|
7
|
+
expect(resolveTweetId(' 456 ', undefined)).toBe('456')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('prefers id when both inputs are present', () => {
|
|
11
|
+
expect(resolveTweetId('123', 'https://x.com/slidevjs/status/456')).toBe('123')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it.each([
|
|
15
|
+
'https://x.com/slidevjs/status/123',
|
|
16
|
+
'https://www.x.com/slidevjs/status/123?s=20',
|
|
17
|
+
'https://twitter.com/slidevjs/status/123',
|
|
18
|
+
'https://mobile.twitter.com/slidevjs/status/123/photo/1',
|
|
19
|
+
'https://x.com/i/web/status/123',
|
|
20
|
+
])('extracts an id from %s', (url) => {
|
|
21
|
+
expect(resolveTweetId(undefined, url)).toBe('123')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it.each([
|
|
25
|
+
'https://example.com/slidevjs/status/123',
|
|
26
|
+
'https://x.com.example.com/slidevjs/status/123',
|
|
27
|
+
'https://x.com/slidevjs/status/not-a-number',
|
|
28
|
+
'javascript:alert(1)',
|
|
29
|
+
'not a URL',
|
|
30
|
+
])('rejects an unsupported source URL: %s', (url) => {
|
|
31
|
+
expect(resolveTweetId(undefined, url)).toBeUndefined()
|
|
32
|
+
})
|
|
33
|
+
})
|
package/logic/tweet.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const TWEET_HOSTS = new Set([
|
|
2
|
+
'mobile.twitter.com',
|
|
3
|
+
'mobile.x.com',
|
|
4
|
+
'twitter.com',
|
|
5
|
+
'www.twitter.com',
|
|
6
|
+
'www.x.com',
|
|
7
|
+
'x.com',
|
|
8
|
+
])
|
|
9
|
+
|
|
10
|
+
export function resolveTweetId(
|
|
11
|
+
id: string | number | undefined,
|
|
12
|
+
sourceUrl: string | undefined,
|
|
13
|
+
): string | undefined {
|
|
14
|
+
if (typeof id === 'number')
|
|
15
|
+
return id.toString()
|
|
16
|
+
|
|
17
|
+
if (id?.trim())
|
|
18
|
+
return id.trim()
|
|
19
|
+
|
|
20
|
+
if (!sourceUrl)
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(sourceUrl)
|
|
25
|
+
if (!['http:', 'https:'].includes(url.protocol) || !TWEET_HOSTS.has(url.hostname))
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
const segments = url.pathname.split('/').filter(Boolean)
|
|
29
|
+
const statusIndex = segments.lastIndexOf('status')
|
|
30
|
+
const tweetId = segments[statusIndex + 1]
|
|
31
|
+
if (statusIndex < 1 || !tweetId || !/^\d+$/.test(tweetId))
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
return tweetId
|
|
35
|
+
}
|
|
36
|
+
catch {}
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "52.19.
|
|
4
|
+
"version": "52.19.2",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -30,45 +30,45 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@antfu/utils": "^9.3.0",
|
|
32
32
|
"@fix-webm-duration/fix": "^1.0.1",
|
|
33
|
-
"@iconify-json/carbon": "^1.2.
|
|
33
|
+
"@iconify-json/carbon": "^1.2.27",
|
|
34
34
|
"@iconify-json/ph": "^1.2.2",
|
|
35
35
|
"@iconify-json/svg-spinners": "^1.2.4",
|
|
36
|
-
"@shikijs/engine-javascript": "^4.4.
|
|
37
|
-
"@shikijs/magic-move": "^4.4.
|
|
38
|
-
"@shikijs/monaco": "^4.4.
|
|
39
|
-
"@shikijs/vitepress-twoslash": "^4.4.
|
|
36
|
+
"@shikijs/engine-javascript": "^4.4.3",
|
|
37
|
+
"@shikijs/magic-move": "^4.4.3",
|
|
38
|
+
"@shikijs/monaco": "^4.4.3",
|
|
39
|
+
"@shikijs/vitepress-twoslash": "^4.4.3",
|
|
40
|
+
"@slidev/parser": "52.19.2",
|
|
40
41
|
"@slidev/rough-notation": "^0.1.0",
|
|
42
|
+
"@slidev/types": "52.19.2",
|
|
41
43
|
"@typescript/ata": "^0.9.8",
|
|
42
|
-
"@unhead/vue": "^3.
|
|
43
|
-
"@unocss/extractor-mdc": "^66.
|
|
44
|
-
"@unocss/preset-mini": "^66.
|
|
45
|
-
"@unocss/reset": "^66.
|
|
44
|
+
"@unhead/vue": "^3.4.1",
|
|
45
|
+
"@unocss/extractor-mdc": "^66.10.3",
|
|
46
|
+
"@unocss/preset-mini": "^66.10.3",
|
|
47
|
+
"@unocss/reset": "^66.10.3",
|
|
46
48
|
"@vueuse/core": "^14.4.0",
|
|
47
49
|
"@vueuse/math": "^14.4.0",
|
|
48
50
|
"@vueuse/motion": "^3.0.3",
|
|
49
|
-
"ansis": "^4.
|
|
51
|
+
"ansis": "^4.4.0",
|
|
50
52
|
"drauu": "^1.0.0",
|
|
51
53
|
"file-saver": "^2.0.5",
|
|
52
54
|
"fitty": "^2.4.2",
|
|
53
|
-
"floating-vue": "^5.
|
|
55
|
+
"floating-vue": "^5.4.0",
|
|
54
56
|
"fuse.js": "^7.5.0",
|
|
55
|
-
"katex": "^0.18.
|
|
57
|
+
"katex": "^0.18.7",
|
|
56
58
|
"lz-string": "^1.5.0",
|
|
57
|
-
"mermaid": "^11.16.
|
|
59
|
+
"mermaid": "^11.16.1",
|
|
58
60
|
"monaco-editor": "^0.56.0",
|
|
59
61
|
"nanotar": "^0.3.0",
|
|
60
62
|
"pptxgenjs": "^4.0.1",
|
|
61
63
|
"recordrtc": "^5.6.2",
|
|
62
|
-
"shiki": "^4.4.
|
|
64
|
+
"shiki": "^4.4.3",
|
|
63
65
|
"typescript": "^6.0.3",
|
|
64
|
-
"unocss": "^66.
|
|
65
|
-
"vue": "^3.5.
|
|
66
|
-
"vue-router": "^5.
|
|
67
|
-
"yaml": "^2.9.
|
|
68
|
-
"@slidev/parser": "52.19.0",
|
|
69
|
-
"@slidev/types": "52.19.0"
|
|
66
|
+
"unocss": "^66.10.3",
|
|
67
|
+
"vue": "^3.5.42",
|
|
68
|
+
"vue-router": "^5.3.1",
|
|
69
|
+
"yaml": "^2.9.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"vite": "
|
|
72
|
+
"vite": "~8.2.0"
|
|
73
73
|
}
|
|
74
74
|
}
|