@commonpub/layer 0.37.0 → 0.39.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/components/contest/ContestHero.vue +1 -12
- package/components/contest/ContestRules.vue +0 -4
- package/components/homepage/ContestsSection.vue +9 -2
- package/components/homepage/HeroSection.vue +9 -0
- package/package.json +6 -6
- package/pages/contests/index.vue +1 -12
- package/utils/markdownExcerpt.ts +26 -0
|
@@ -61,18 +61,7 @@ const isEnded = computed(() => c.value?.status === 'completed' || c.value?.statu
|
|
|
61
61
|
const tagline = computed<string>(() => {
|
|
62
62
|
const sub = (c.value?.subheading ?? '').trim();
|
|
63
63
|
if (sub) return sub;
|
|
64
|
-
|
|
65
|
-
if (!d) return 'No description available.';
|
|
66
|
-
return d
|
|
67
|
-
.replace(/```[\s\S]*?```/g, ' ')
|
|
68
|
-
.replace(/`([^`]*)`/g, '$1')
|
|
69
|
-
.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
|
|
70
|
-
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
71
|
-
.replace(/^#{1,6}\s+/gm, '')
|
|
72
|
-
.replace(/^\s*[-*+>]\s+/gm, '')
|
|
73
|
-
.replace(/(\*\*|__|~~|\*|_)/g, '')
|
|
74
|
-
.replace(/\s+/g, ' ')
|
|
75
|
-
.trim();
|
|
64
|
+
return markdownToExcerpt(c.value?.description) || 'No description available.';
|
|
76
65
|
});
|
|
77
66
|
</script>
|
|
78
67
|
|
|
@@ -27,8 +27,4 @@ defineProps<{
|
|
|
27
27
|
.cpub-sec-head h2 { font-size: 15px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
|
28
28
|
|
|
29
29
|
.cpub-rules-card { background: var(--surface); border: var(--border-width-default) solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; box-shadow: var(--shadow-md); }
|
|
30
|
-
.cpub-rules-list { padding-left: 20px; margin: 0; }
|
|
31
|
-
.cpub-rule-item { font-size: 12px; color: var(--text-dim); line-height: 1.7; margin-bottom: 6px; }
|
|
32
|
-
.cpub-rule-item:last-child { margin-bottom: 0; }
|
|
33
|
-
.cpub-rules-text { font-size: 12px; color: var(--text-dim); line-height: 1.7; white-space: pre-line; }
|
|
34
30
|
</style>
|
|
@@ -10,12 +10,19 @@ const { data: contests } = await useFetch('/api/contests', {
|
|
|
10
10
|
query: computed(() => ({ limit: limit.value, status: 'active' })),
|
|
11
11
|
lazy: true,
|
|
12
12
|
});
|
|
13
|
+
|
|
14
|
+
// Dedupe against the hero: if the hero is already showing an active contest as a
|
|
15
|
+
// full callout, don't repeat it here. Other active contests still list.
|
|
16
|
+
const heroContestId = useState<string | null>('cpub:hero-contest-id', () => null);
|
|
17
|
+
const visibleContests = computed(() =>
|
|
18
|
+
(contests.value?.items ?? []).filter((c: { id: string }) => c.id !== heroContestId.value),
|
|
19
|
+
);
|
|
13
20
|
</script>
|
|
14
21
|
|
|
15
22
|
<template>
|
|
16
|
-
<div v-if="
|
|
23
|
+
<div v-if="visibleContests.length" class="cpub-sb-card">
|
|
17
24
|
<div class="cpub-sb-head">Active Contests <NuxtLink to="/contests">View all</NuxtLink></div>
|
|
18
|
-
<div v-for="c in
|
|
25
|
+
<div v-for="c in visibleContests" :key="c.id" class="cpub-contest-item">
|
|
19
26
|
<NuxtLink :to="`/contests/${c.slug}`" class="cpub-contest-name">{{ c.title }}</NuxtLink>
|
|
20
27
|
<div class="cpub-contest-row">
|
|
21
28
|
<span class="cpub-contest-entries">{{ c.entryCount ?? 0 }} entries</span>
|
|
@@ -85,6 +85,15 @@ const heroDismissed = useState('cpub:hero-dismissed', () => false);
|
|
|
85
85
|
function dismissHero(): void {
|
|
86
86
|
heroDismissed.value = true;
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
// Publish the contest the hero is currently showing so the sidebar "Active
|
|
90
|
+
// Contests" widget can DEDUPE it (no point showing the same contest twice — the
|
|
91
|
+
// hero is already a full callout). Null when the hero isn't showing one.
|
|
92
|
+
const heroContestId = useState<string | null>('cpub:hero-contest-id', () => null);
|
|
93
|
+
watchEffect(() => {
|
|
94
|
+
const shown = contestsEnabled.value && !!activeContest.value && !heroDismissed.value;
|
|
95
|
+
heroContestId.value = shown ? ((activeContest.value as { id?: string }).id ?? null) : null;
|
|
96
|
+
});
|
|
88
97
|
</script>
|
|
89
98
|
|
|
90
99
|
<template>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -54,15 +54,15 @@
|
|
|
54
54
|
"vue-router": "^4.3.0",
|
|
55
55
|
"zod": "^4.3.6",
|
|
56
56
|
"@commonpub/auth": "0.7.0",
|
|
57
|
-
"@commonpub/explainer": "0.7.15",
|
|
58
57
|
"@commonpub/docs": "0.6.3",
|
|
59
|
-
"@commonpub/editor": "0.7.11",
|
|
60
58
|
"@commonpub/config": "0.16.0",
|
|
61
|
-
"@commonpub/
|
|
59
|
+
"@commonpub/editor": "0.7.11",
|
|
62
60
|
"@commonpub/learning": "0.5.2",
|
|
61
|
+
"@commonpub/protocol": "0.12.0",
|
|
62
|
+
"@commonpub/explainer": "0.7.15",
|
|
63
|
+
"@commonpub/schema": "0.24.0",
|
|
63
64
|
"@commonpub/ui": "0.9.1",
|
|
64
|
-
"@commonpub/server": "2.67.0"
|
|
65
|
-
"@commonpub/protocol": "0.12.0"
|
|
65
|
+
"@commonpub/server": "2.67.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@testing-library/jest-dom": "^6.9.1",
|
package/pages/contests/index.vue
CHANGED
|
@@ -9,18 +9,7 @@ const { isAuthenticated, isAdmin, user } = useAuth();
|
|
|
9
9
|
// cards never dump a raw `## ...` wall.
|
|
10
10
|
function cardBlurb(c: { subheading?: string | null; description?: string | null }): string {
|
|
11
11
|
if (c.subheading?.trim()) return c.subheading.trim();
|
|
12
|
-
|
|
13
|
-
if (!d) return '';
|
|
14
|
-
return d
|
|
15
|
-
.replace(/```[\s\S]*?```/g, ' ')
|
|
16
|
-
.replace(/`([^`]*)`/g, '$1')
|
|
17
|
-
.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
|
|
18
|
-
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
19
|
-
.replace(/^#{1,6}\s+/gm, '')
|
|
20
|
-
.replace(/^\s*[-*+>]\s+/gm, '')
|
|
21
|
-
.replace(/(\*\*|__|~~|\*|_)/g, '')
|
|
22
|
-
.replace(/\s+/g, ' ')
|
|
23
|
-
.trim();
|
|
12
|
+
return markdownToExcerpt(c.description);
|
|
24
13
|
}
|
|
25
14
|
|
|
26
15
|
const config = useRuntimeConfig();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip Markdown (and the inline-HTML-ish bits we author) down to clean,
|
|
3
|
+
* single-line plain text suitable for a CSS-clamped excerpt.
|
|
4
|
+
*
|
|
5
|
+
* Used wherever we want a short tagline/blurb from a (possibly long) Markdown
|
|
6
|
+
* description without dumping a raw `## ...` / fenced-code wall into the UI —
|
|
7
|
+
* the contest hero tagline and the contest listing card blurb both share this.
|
|
8
|
+
* The full formatted description still renders through the Markdown pipeline
|
|
9
|
+
* elsewhere; this is purely for the truncated preview.
|
|
10
|
+
*
|
|
11
|
+
* Returns `''` for empty/whitespace input.
|
|
12
|
+
*/
|
|
13
|
+
export function markdownToExcerpt(raw: string | null | undefined): string {
|
|
14
|
+
const d = (raw ?? '').trim();
|
|
15
|
+
if (!d) return '';
|
|
16
|
+
return d
|
|
17
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
18
|
+
.replace(/`([^`]*)`/g, '$1')
|
|
19
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
|
|
20
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
21
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
22
|
+
.replace(/^\s*[-*+>]\s+/gm, '')
|
|
23
|
+
.replace(/(\*\*|__|~~|\*|_)/g, '')
|
|
24
|
+
.replace(/\s+/g, ' ')
|
|
25
|
+
.trim();
|
|
26
|
+
}
|