@commonpub/layer 0.4.11 → 0.4.13
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/package.json +5 -5
- package/pages/settings/profile.vue +82 -116
- package/pages/u/[username]/index.vue +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -51,14 +51,14 @@
|
|
|
51
51
|
"vue-router": "^4.3.0",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
53
|
"@commonpub/auth": "0.5.0",
|
|
54
|
-
"@commonpub/editor": "0.5.0",
|
|
55
54
|
"@commonpub/learning": "0.5.0",
|
|
56
|
-
"@commonpub/schema": "0.8.
|
|
55
|
+
"@commonpub/schema": "0.8.15",
|
|
56
|
+
"@commonpub/editor": "0.5.0",
|
|
57
57
|
"@commonpub/config": "0.8.0",
|
|
58
|
-
"@commonpub/explainer": "0.5.3",
|
|
59
58
|
"@commonpub/docs": "0.5.2",
|
|
59
|
+
"@commonpub/explainer": "0.5.3",
|
|
60
|
+
"@commonpub/server": "2.23.1",
|
|
60
61
|
"@commonpub/ui": "0.8.4",
|
|
61
|
-
"@commonpub/server": "2.23.0",
|
|
62
62
|
"@commonpub/protocol": "0.9.5"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
@@ -30,14 +30,18 @@ const form = ref({
|
|
|
30
30
|
bannerUrl: '',
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
const skills = ref<
|
|
33
|
+
const skills = ref<string[]>([]);
|
|
34
34
|
const socialLinks = ref({
|
|
35
35
|
github: '',
|
|
36
36
|
twitter: '',
|
|
37
37
|
linkedin: '',
|
|
38
|
-
|
|
38
|
+
youtube: '',
|
|
39
|
+
instagram: '',
|
|
40
|
+
mastodon: '',
|
|
41
|
+
discord: '',
|
|
39
42
|
});
|
|
40
|
-
const
|
|
43
|
+
const pronouns = ref('');
|
|
44
|
+
const experience = ref<Array<{ title: string; company: string; startDate: string; endDate: string; description: string }>>([]);
|
|
41
45
|
|
|
42
46
|
const emailNotifications = ref<{
|
|
43
47
|
digest: 'daily' | 'weekly' | 'none';
|
|
@@ -75,19 +79,28 @@ if (profile.value) {
|
|
|
75
79
|
form.value.bannerUrl = p.bannerUrl || '';
|
|
76
80
|
|
|
77
81
|
if (Array.isArray(p.skills)) {
|
|
78
|
-
skills.value = p.skills.
|
|
79
|
-
typeof s === 'string' ? { name: s, proficiency: 3 } : s,
|
|
80
|
-
);
|
|
82
|
+
skills.value = p.skills.filter((s): s is string => typeof s === 'string');
|
|
81
83
|
}
|
|
84
|
+
pronouns.value = p.pronouns || '';
|
|
82
85
|
if (p.socialLinks) {
|
|
83
|
-
|
|
84
|
-
socialLinks.value.
|
|
85
|
-
socialLinks.value.
|
|
86
|
-
socialLinks.value.
|
|
86
|
+
const sl = p.socialLinks as Record<string, string | undefined>;
|
|
87
|
+
socialLinks.value.github = sl.github || '';
|
|
88
|
+
socialLinks.value.twitter = sl.twitter || '';
|
|
89
|
+
socialLinks.value.linkedin = sl.linkedin || '';
|
|
90
|
+
socialLinks.value.youtube = sl.youtube || '';
|
|
91
|
+
socialLinks.value.instagram = sl.instagram || '';
|
|
92
|
+
socialLinks.value.mastodon = sl.mastodon || '';
|
|
93
|
+
socialLinks.value.discord = sl.discord || '';
|
|
87
94
|
}
|
|
88
95
|
const profileRecord = p as Record<string, unknown>;
|
|
89
96
|
if (Array.isArray(profileRecord.experience)) {
|
|
90
|
-
experience.value = (profileRecord.experience as Array<Record<string, unknown>>).map((e) => ({
|
|
97
|
+
experience.value = (profileRecord.experience as Array<Record<string, unknown>>).map((e) => ({
|
|
98
|
+
title: String(e.title || ''),
|
|
99
|
+
company: String(e.company || ''),
|
|
100
|
+
startDate: String(e.startDate || ''),
|
|
101
|
+
endDate: String(e.endDate || ''),
|
|
102
|
+
description: String(e.description || ''),
|
|
103
|
+
}));
|
|
91
104
|
}
|
|
92
105
|
if (profileRecord.emailNotifications && typeof profileRecord.emailNotifications === 'object') {
|
|
93
106
|
const en = profileRecord.emailNotifications as Record<string, unknown>;
|
|
@@ -104,25 +117,20 @@ if (profile.value) {
|
|
|
104
117
|
// Watch for form changes AFTER initial data is loaded (nextTick avoids false positive)
|
|
105
118
|
onMounted(() => {
|
|
106
119
|
nextTick(() => {
|
|
107
|
-
watch([form, skills, socialLinks, experience, emailNotifications], () => { isDirty.value = true; }, { deep: true });
|
|
120
|
+
watch([form, skills, pronouns, socialLinks, experience, emailNotifications], () => { isDirty.value = true; }, { deep: true });
|
|
108
121
|
});
|
|
109
122
|
});
|
|
110
123
|
|
|
111
124
|
function addSkill(): void {
|
|
112
|
-
skills.value.push(
|
|
125
|
+
skills.value.push('');
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
function removeSkill(index: number): void {
|
|
116
129
|
skills.value.splice(index, 1);
|
|
117
130
|
}
|
|
118
131
|
|
|
119
|
-
function generateId(): string {
|
|
120
|
-
return `exp-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
132
|
function addExperience(): void {
|
|
124
133
|
experience.value.push({
|
|
125
|
-
id: generateId(),
|
|
126
134
|
title: '',
|
|
127
135
|
company: '',
|
|
128
136
|
startDate: '',
|
|
@@ -170,8 +178,9 @@ async function handleSave(): Promise<void> {
|
|
|
170
178
|
method: 'PUT',
|
|
171
179
|
body: {
|
|
172
180
|
...form.value,
|
|
173
|
-
skills: skills.value.filter((s) => s.
|
|
181
|
+
skills: skills.value.filter((s) => s.trim()),
|
|
174
182
|
experience: experience.value.filter((e) => e.title.trim()),
|
|
183
|
+
pronouns: pronouns.value || undefined,
|
|
175
184
|
socialLinks: socialLinks.value,
|
|
176
185
|
...(emailNotificationsEnabled.value ? { emailNotifications: emailNotifications.value } : {}),
|
|
177
186
|
},
|
|
@@ -330,39 +339,42 @@ async function handleSave(): Promise<void> {
|
|
|
330
339
|
</div>
|
|
331
340
|
</div>
|
|
332
341
|
|
|
342
|
+
<!-- Pronouns -->
|
|
343
|
+
<div class="cpub-form-section">
|
|
344
|
+
<span class="cpub-form-section-label">Identity</span>
|
|
345
|
+
|
|
346
|
+
<div class="cpub-form-group">
|
|
347
|
+
<label for="pronouns" class="cpub-form-label">Pronouns</label>
|
|
348
|
+
<input
|
|
349
|
+
id="pronouns"
|
|
350
|
+
v-model="pronouns"
|
|
351
|
+
type="text"
|
|
352
|
+
class="cpub-input"
|
|
353
|
+
placeholder="e.g., they/them, she/her, he/him"
|
|
354
|
+
/>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
333
358
|
<!-- Skills -->
|
|
334
359
|
<div class="cpub-form-section">
|
|
335
360
|
<span class="cpub-form-section-label">Skills</span>
|
|
336
361
|
|
|
337
362
|
<div
|
|
338
|
-
v-for="(
|
|
363
|
+
v-for="(_skill, index) in skills"
|
|
339
364
|
:key="index"
|
|
340
365
|
class="cpub-skill-row"
|
|
341
366
|
>
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
/>
|
|
350
|
-
</div>
|
|
351
|
-
<div class="cpub-skill-slider">
|
|
352
|
-
<input
|
|
353
|
-
v-model.number="skill.proficiency"
|
|
354
|
-
type="range"
|
|
355
|
-
min="0"
|
|
356
|
-
max="100"
|
|
357
|
-
class="cpub-range"
|
|
358
|
-
:aria-label="`Skill ${index + 1} proficiency`"
|
|
359
|
-
/>
|
|
360
|
-
<span class="cpub-skill-value">{{ skill.proficiency }}%</span>
|
|
361
|
-
</div>
|
|
367
|
+
<input
|
|
368
|
+
v-model="skills[index]"
|
|
369
|
+
type="text"
|
|
370
|
+
class="cpub-input"
|
|
371
|
+
placeholder="Skill name"
|
|
372
|
+
:aria-label="`Skill ${index + 1}`"
|
|
373
|
+
/>
|
|
362
374
|
<button
|
|
363
375
|
type="button"
|
|
364
376
|
class="cpub-btn-icon cpub-btn-danger"
|
|
365
|
-
:aria-label="`Remove skill ${
|
|
377
|
+
:aria-label="`Remove skill ${skills[index] || index + 1}`"
|
|
366
378
|
@click="removeSkill(index)"
|
|
367
379
|
>
|
|
368
380
|
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
|
|
@@ -417,15 +429,25 @@ async function handleSave(): Promise<void> {
|
|
|
417
429
|
</div>
|
|
418
430
|
|
|
419
431
|
<div class="cpub-form-group">
|
|
420
|
-
<label for="social-
|
|
421
|
-
<input
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
432
|
+
<label for="social-youtube" class="cpub-form-label">YouTube</label>
|
|
433
|
+
<input id="social-youtube" v-model="socialLinks.youtube" type="url" class="cpub-input" placeholder="https://youtube.com/@channel" />
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<div class="cpub-form-group">
|
|
437
|
+
<label for="social-instagram" class="cpub-form-label">Instagram</label>
|
|
438
|
+
<input id="social-instagram" v-model="socialLinks.instagram" type="url" class="cpub-input" placeholder="https://instagram.com/username" />
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div class="cpub-form-group">
|
|
442
|
+
<label for="social-mastodon" class="cpub-form-label">Mastodon</label>
|
|
443
|
+
<input id="social-mastodon" v-model="socialLinks.mastodon" type="url" class="cpub-input" placeholder="https://mastodon.social/@username" />
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<div class="cpub-form-group">
|
|
447
|
+
<label for="social-discord" class="cpub-form-label">Discord</label>
|
|
448
|
+
<input id="social-discord" v-model="socialLinks.discord" type="url" class="cpub-input" placeholder="https://discord.gg/invite" />
|
|
428
449
|
</div>
|
|
450
|
+
|
|
429
451
|
</div>
|
|
430
452
|
|
|
431
453
|
<!-- Experience -->
|
|
@@ -434,7 +456,7 @@ async function handleSave(): Promise<void> {
|
|
|
434
456
|
|
|
435
457
|
<div
|
|
436
458
|
v-for="(entry, index) in experience"
|
|
437
|
-
:key="
|
|
459
|
+
:key="index"
|
|
438
460
|
class="cpub-experience-card"
|
|
439
461
|
>
|
|
440
462
|
<div class="cpub-experience-header">
|
|
@@ -451,9 +473,9 @@ async function handleSave(): Promise<void> {
|
|
|
451
473
|
|
|
452
474
|
<div class="cpub-experience-fields">
|
|
453
475
|
<div class="cpub-form-group">
|
|
454
|
-
<label :for="`exp-title-${
|
|
476
|
+
<label :for="`exp-title-${index}`" class="cpub-form-label">Title</label>
|
|
455
477
|
<input
|
|
456
|
-
:id="`exp-title-${
|
|
478
|
+
:id="`exp-title-${index}`"
|
|
457
479
|
v-model="entry.title"
|
|
458
480
|
type="text"
|
|
459
481
|
class="cpub-input"
|
|
@@ -462,9 +484,9 @@ async function handleSave(): Promise<void> {
|
|
|
462
484
|
</div>
|
|
463
485
|
|
|
464
486
|
<div class="cpub-form-group">
|
|
465
|
-
<label :for="`exp-company-${
|
|
487
|
+
<label :for="`exp-company-${index}`" class="cpub-form-label">Company</label>
|
|
466
488
|
<input
|
|
467
|
-
:id="`exp-company-${
|
|
489
|
+
:id="`exp-company-${index}`"
|
|
468
490
|
v-model="entry.company"
|
|
469
491
|
type="text"
|
|
470
492
|
class="cpub-input"
|
|
@@ -474,18 +496,18 @@ async function handleSave(): Promise<void> {
|
|
|
474
496
|
|
|
475
497
|
<div class="cpub-experience-dates">
|
|
476
498
|
<div class="cpub-form-group">
|
|
477
|
-
<label :for="`exp-start-${
|
|
499
|
+
<label :for="`exp-start-${index}`" class="cpub-form-label">Start Date</label>
|
|
478
500
|
<input
|
|
479
|
-
:id="`exp-start-${
|
|
501
|
+
:id="`exp-start-${index}`"
|
|
480
502
|
v-model="entry.startDate"
|
|
481
503
|
type="month"
|
|
482
504
|
class="cpub-input"
|
|
483
505
|
/>
|
|
484
506
|
</div>
|
|
485
507
|
<div class="cpub-form-group">
|
|
486
|
-
<label :for="`exp-end-${
|
|
508
|
+
<label :for="`exp-end-${index}`" class="cpub-form-label">End Date</label>
|
|
487
509
|
<input
|
|
488
|
-
:id="`exp-end-${
|
|
510
|
+
:id="`exp-end-${index}`"
|
|
489
511
|
v-model="entry.endDate"
|
|
490
512
|
type="month"
|
|
491
513
|
class="cpub-input"
|
|
@@ -495,9 +517,9 @@ async function handleSave(): Promise<void> {
|
|
|
495
517
|
</div>
|
|
496
518
|
|
|
497
519
|
<div class="cpub-form-group">
|
|
498
|
-
<label :for="`exp-desc-${
|
|
520
|
+
<label :for="`exp-desc-${index}`" class="cpub-form-label">Description</label>
|
|
499
521
|
<textarea
|
|
500
|
-
:id="`exp-desc-${
|
|
522
|
+
:id="`exp-desc-${index}`"
|
|
501
523
|
v-model="entry.description"
|
|
502
524
|
class="cpub-textarea"
|
|
503
525
|
rows="3"
|
|
@@ -749,58 +771,6 @@ async function handleSave(): Promise<void> {
|
|
|
749
771
|
margin-bottom: var(--space-3);
|
|
750
772
|
}
|
|
751
773
|
|
|
752
|
-
.cpub-skill-name {
|
|
753
|
-
flex: 1;
|
|
754
|
-
min-width: 0;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
.cpub-skill-slider {
|
|
758
|
-
display: flex;
|
|
759
|
-
align-items: center;
|
|
760
|
-
gap: var(--space-2);
|
|
761
|
-
width: 180px;
|
|
762
|
-
flex-shrink: 0;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
.cpub-range {
|
|
766
|
-
flex: 1;
|
|
767
|
-
appearance: none;
|
|
768
|
-
height: 4px;
|
|
769
|
-
background: var(--border2);
|
|
770
|
-
outline: none;
|
|
771
|
-
cursor: pointer;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
.cpub-range::-webkit-slider-thumb {
|
|
775
|
-
appearance: none;
|
|
776
|
-
width: 14px;
|
|
777
|
-
height: 14px;
|
|
778
|
-
background: var(--accent);
|
|
779
|
-
border: var(--border-width-default) solid var(--accent);
|
|
780
|
-
cursor: pointer;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
.cpub-range::-moz-range-thumb {
|
|
784
|
-
width: 14px;
|
|
785
|
-
height: 14px;
|
|
786
|
-
background: var(--accent);
|
|
787
|
-
border: var(--border-width-default) solid var(--accent);
|
|
788
|
-
cursor: pointer;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
.cpub-range:focus-visible::-webkit-slider-thumb {
|
|
792
|
-
outline: 2px solid var(--accent);
|
|
793
|
-
outline-offset: 2px;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
.cpub-skill-value {
|
|
797
|
-
font-family: var(--font-mono);
|
|
798
|
-
font-size: var(--text-xs);
|
|
799
|
-
color: var(--text-dim);
|
|
800
|
-
min-width: 36px;
|
|
801
|
-
text-align: right;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
774
|
/* ─── Buttons ─── */
|
|
805
775
|
.cpub-btn-icon {
|
|
806
776
|
width: 32px;
|
|
@@ -979,12 +949,8 @@ async function handleSave(): Promise<void> {
|
|
|
979
949
|
|
|
980
950
|
@media (max-width: 768px) {
|
|
981
951
|
.cpub-settings-form { padding: 0 var(--space-1); }
|
|
982
|
-
.cpub-skill-slider { width: 120px; }
|
|
983
952
|
.cpub-experience-dates { grid-template-columns: 1fr; }
|
|
984
953
|
.cpub-banner-upload { height: 100px; }
|
|
985
954
|
}
|
|
986
955
|
|
|
987
|
-
@media (max-width: 480px) {
|
|
988
|
-
.cpub-skill-slider { width: 80px; }
|
|
989
|
-
}
|
|
990
956
|
</style>
|
|
@@ -6,6 +6,8 @@ useSeoMeta({
|
|
|
6
6
|
title: `${username} — ${useSiteName()}`,
|
|
7
7
|
ogTitle: `${username} — ${useSiteName()}`,
|
|
8
8
|
ogImage: '/og-default.png',
|
|
9
|
+
ogType: 'profile',
|
|
10
|
+
twitterCard: 'summary',
|
|
9
11
|
});
|
|
10
12
|
|
|
11
13
|
const { explainers: explainersEnabled, learning: learningEnabled, federation: federationEnabled } = useFeatures();
|
|
@@ -370,6 +372,25 @@ async function handleReport(): Promise<void> {
|
|
|
370
372
|
<p class="cpub-empty-state-title">No skills listed yet</p>
|
|
371
373
|
</div>
|
|
372
374
|
</div>
|
|
375
|
+
|
|
376
|
+
<!-- Experience -->
|
|
377
|
+
<div v-if="p.experience?.length" class="cpub-experience-section">
|
|
378
|
+
<div class="cpub-sec-head">
|
|
379
|
+
<h2><i class="fa-solid fa-briefcase" style="color: var(--purple); margin-right: 6px"></i>Experience</h2>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="cpub-experience-list">
|
|
382
|
+
<div v-for="(exp, idx) in p.experience" :key="idx" class="cpub-experience-item">
|
|
383
|
+
<div class="cpub-exp-header">
|
|
384
|
+
<strong>{{ exp.title }}</strong>
|
|
385
|
+
<span v-if="exp.company" class="cpub-exp-company">{{ exp.company }}</span>
|
|
386
|
+
</div>
|
|
387
|
+
<div v-if="exp.startDate" class="cpub-exp-dates">
|
|
388
|
+
{{ exp.startDate }}{{ exp.endDate ? ` — ${exp.endDate}` : ' — Present' }}
|
|
389
|
+
</div>
|
|
390
|
+
<p v-if="exp.description" class="cpub-exp-desc">{{ exp.description }}</p>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
373
394
|
</div>
|
|
374
395
|
|
|
375
396
|
<!-- Sidebar -->
|
|
@@ -709,6 +730,20 @@ async function handleReport(): Promise<void> {
|
|
|
709
730
|
margin-bottom: 8px;
|
|
710
731
|
}
|
|
711
732
|
|
|
733
|
+
/* Experience */
|
|
734
|
+
.cpub-experience-section { margin-top: 24px; }
|
|
735
|
+
.cpub-experience-list { display: flex; flex-direction: column; gap: 16px; }
|
|
736
|
+
.cpub-experience-item {
|
|
737
|
+
padding: 12px 16px;
|
|
738
|
+
border: var(--border-width-default) solid var(--border2);
|
|
739
|
+
background: var(--surface);
|
|
740
|
+
}
|
|
741
|
+
.cpub-exp-header { display: flex; flex-direction: column; gap: 2px; }
|
|
742
|
+
.cpub-exp-header strong { font-size: var(--text-sm); }
|
|
743
|
+
.cpub-exp-company { font-size: var(--text-xs); color: var(--text-dim); }
|
|
744
|
+
.cpub-exp-dates { font-size: var(--text-xs); color: var(--text-faint); font-family: var(--font-mono); margin-top: 4px; }
|
|
745
|
+
.cpub-exp-desc { font-size: var(--text-sm); color: var(--text-dim); margin-top: 8px; line-height: var(--leading-normal); }
|
|
746
|
+
|
|
712
747
|
/* About grid */
|
|
713
748
|
.cpub-about-grid {
|
|
714
749
|
display: grid;
|