Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/pages/ProjectsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div class="max-w-[700px]">
<section>
<!-- Page title -->
<h1 class="h1 font-aspekta mb-12">Nice stuff I've built</h1>
<h1 id="projects-top" class="h1 font-aspekta mb-12">Nice stuff I've built</h1>

<!-- Page content -->
<div class="space-y-10">
Expand Down Expand Up @@ -58,6 +58,10 @@
</aside>
</div>

<div class="flex justify-end pt-10">
<BackToTopLink target="#projects-top" />
</div>

<FooterPartial />
</div>
</main>
Expand All @@ -76,6 +80,7 @@ import ProjectCardPartial from '@partials/ProjectCardPartial.vue';
import WidgetSkillsPartial from '@partials/WidgetSkillsPartial.vue';
import WidgetSponsorPartial from '@partials/WidgetSponsorPartial.vue';
import WidgetSkillsSkeletonPartial from '@partials/WidgetSkillsSkeletonPartial.vue';
import BackToTopLink from '@partials/BackToTopLink.vue';
import type { ProfileResponse, ProjectsResponse } from '@api/response/index.ts';
import ProjectCardSkeletonPartial from '@partials/ProjectCardSkeletonPartial.vue';
import { useSeo, SITE_NAME, ABOUT_IMAGE, siteUrlFor, buildKeywords, PERSON_JSON_LD } from '@/support/seo';
Expand Down
6 changes: 3 additions & 3 deletions src/pages/ResumePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
<span id="education" class="block h-0" aria-hidden="true"></span>
<EducationPartial v-if="education" :education="education" />
<span id="experience" class="block h-0" aria-hidden="true"></span>
<ExperiencePartial v-if="experience" :experience="experience" />
<ExperiencePartial v-if="experience" :experience="experience" back-to-top-target="#resume-top" />
<span id="recommendations" class="block h-0" aria-hidden="true"></span>
<RecommendationPartial v-if="recommendations" :recommendations="recommendations" />
<RecommendationPartial v-if="recommendations" :recommendations="recommendations" back-to-top-target="#resume-top" />
</template>
</div>
<div class="flex justify-end pt-10">
<BackToTopLink />
<BackToTopLink target="#resume-top" />
</div>
</section>
</div>
Expand Down
24 changes: 21 additions & 3 deletions src/partials/BackToTopLink.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
<template>
<a
class="inline-flex items-center gap-2 rounded-full border border-slate-200/70 px-4 py-2 text-sm font-medium text-slate-600 transition-colors hover:border-fuchsia-400/70 hover:text-slate-800 dark:border-slate-700/80 dark:text-slate-300 dark:hover:text-slate-100"
href="#resume-top"
:href="target"
>
<span aria-hidden="true"></span>
Back to top
<span aria-hidden="true">{{ icon }}</span>
{{ label }}
</a>
</template>

<script setup lang="ts">
import { toRefs } from 'vue';

const props = withDefaults(
defineProps<{
target: string;
label?: string;
icon?: string;
}>(),
{
label: 'Back to top',
icon: '↑',
},
);

const { target, label, icon } = toRefs(props);
</script>
6 changes: 5 additions & 1 deletion src/partials/ExperiencePartial.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<section class="space-y-8">
<div class="flex flex-wrap items-center justify-between gap-4">
<h2 class="h2 font-aspekta text-slate-700 dark:text-slate-300">Work Experience</h2>
<BackToTopLink />
<BackToTopLink :target="backToTopTarget" />
</div>
<ul class="space-y-8">
<!-- Item -->
Expand Down Expand Up @@ -43,10 +43,14 @@
</section>
</template>
<script setup lang="ts">
import { toRefs } from 'vue';
import BackToTopLink from '@partials/BackToTopLink.vue';
import type { ExperienceResponse } from '@api/response/index.ts';

const props = defineProps<{
experience: Array<ExperienceResponse>;
backToTopTarget: string;
}>();

const { backToTopTarget } = toRefs(props);
</script>
11 changes: 7 additions & 4 deletions src/partials/RecommendationPartial.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<section class="space-y-8">
<div class="flex flex-wrap items-center justify-between gap-4">
<h2 class="h3 font-aspekta text-slate-800 dark:text-slate-100">Recommendations</h2>
<BackToTopLink />
<BackToTopLink :target="backToTopTarget" />
</div>
<ul class="space-y-8">
<!-- Item -->
Expand Down Expand Up @@ -30,19 +30,22 @@
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { computed, toRefs } from 'vue';
import DOMPurify from 'dompurify';
import BackToTopLink from '@partials/BackToTopLink.vue';
import { image, date } from '@/public.ts';
import type { RecommendationsResponse } from '@api/response/recommendations-response.ts';
import { renderMarkdown } from '@/support/markdown.ts';

const { recommendations } = defineProps<{
const props = defineProps<{
recommendations: Array<RecommendationsResponse>;
backToTopTarget: string;
}>();

const { recommendations, backToTopTarget } = toRefs(props);

const processedRecommendations = computed(() => {
return recommendations.map((item) => {
return recommendations.value.map((item) => {
const sanitisedHtml = DOMPurify.sanitize(renderMarkdown(item.text));

return {
Expand Down
12 changes: 6 additions & 6 deletions src/partials/WidgetSocialPartial.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"
/>
</svg>
<span class="text-xs text-slate-400 dark:text-slate-500">Social</span>
<span class="text-xs text-slate-400 dark:text-slate-500">Find Me Online</span>
</div>

<ul role="list" class="mt-5 space-y-2">
Expand Down Expand Up @@ -41,23 +41,23 @@ interface PlatformConfig {
const socialPlatforms: Record<string, PlatformConfig> = {
x: {
icon: 'M13.3174 10.7749L19.1457 4H17.7646L12.7039 9.88256L8.66193 4H4L10.1122 12.8955L4 20H5.38119L10.7254 13.7878L14.994 20H19.656L13.3171 10.7749H13.3174ZM11.4257 12.9738L10.8064 12.0881L5.87886 5.03974H8.00029L11.9769 10.728L12.5962 11.6137L17.7652 19.0075H15.6438L11.4257 12.9742V12.9738Z',
text: 'Follow me on X',
text: 'Join the conversation on X',
},
youtube: {
icon: 'M21.582,6.186c-0.23-0.86-0.908-1.538-1.768-1.768C18.254,4,12,4,12,4S5.746,4,4.186,4.418 c-0.86,0.23-1.538,0.908-1.768,1.768C2,7.746,2,12,2,12s0,4.254,0.418,5.814c0.23,0.86,0.908,1.538,1.768,1.768 C5.746,20,12,20,12,20s6.254,0,7.814-0.418c0.861-0.23,1.538-0.908,1.768-1.768C22,16.254,22,12,22,12S22,7.746,21.582,6.186z M10,15.464V8.536L16,12L10,15.464z',
text: 'Subscribe to my channel',
text: 'Watch my videos on YouTube',
},
instagram: {
icon: 'M7.5,2h9A5.5,5.5,0,0,1,22,7.5v9A5.5,5.5,0,0,1,16.5,22h-9A5.5,5.5,0,0,1,2,16.5v-9A5.5,5.5,0,0,1,7.5,2ZM12,16A4,4,0,1,0,12,8,4,4,0,0,0,12,16Z',
text: 'Follow me on Instagram',
text: 'See my photos on Instagram',
},
linkedin: {
icon: 'M19,3H5C3.89,3,3,3.89,3,5V19C3,20.1,3.89,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.89,20.1,3,19,3M8.5,18H5.5V10H8.5V18M6.94,8.5C6.16,8.5,5.5,7.83,5.5,7C5.5,6.17,6.16,5.5,6.94,5.5C7.72,5.5,8.38,6.17,8.38,7C8.38,7.83,7.72,8.5,6.94,8.5M18.5,18H15.5V14.25C15.5,13.17,14.67,12.25,13.5,12.25C12.5,12.25,11.5,13,11.5,14.25V18H8.5V10H11.5V11.25C12.06,10.25,13,9.5,14.25,9.5C16.5,9.5,18.5,11.25,18.5,14V18Z',
text: 'Follow me on LinkedIn',
text: 'Connect professionally on LinkedIn',
},
github: {
icon: 'M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 4.97,16.5 4.97,16.5C4.05,15.82 5.06,15.82 5.06,15.82C6.06,15.89 6.63,16.83 6.63,16.83C7.5,18.31 8.95,17.88 9.5,17.61C9.58,17.03 9.84,16.6 10.12,16.34C7.89,16.1 5.5,15.27 5.5,11.5C5.5,10.39 5.89,9.53 6.5,8.84C6.38,8.58 6.08,7.7 6.63,6.5C6.63,6.5 7.43,6.26 9.5,7.7C10.27,7.5 11.14,7.39 12,7.39C12.86,7.39 13.73,7.5 14.5,7.7C16.57,6.26 17.37,6.5 17.37,6.5C17.92,7.7 17.62,8.58 17.5,8.84C18.11,9.53 18.5,10.39 18.5,11.5C18.5,15.27 16.1,16.1 13.88,16.34C14.24,16.64 14.5,17.27 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z',
text: 'Follow me on GitHub',
text: 'Check out my code on GitHub',
},
};

Expand Down
3 changes: 3 additions & 0 deletions tests/pages/ProjectsPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
stubs: {
SideNavPartial: true,
HeaderPartial: true,
BackToTopLink: true,
WidgetSponsorPartial: true,
WidgetSkillsPartial: true,
FooterPartial: true,
Expand All @@ -82,6 +83,7 @@
stubs: {
SideNavPartial: true,
HeaderPartial: true,
BackToTopLink: true,
WidgetSponsorPartial: true,
WidgetSkillsPartial: true,
FooterPartial: true,
Expand All @@ -106,11 +108,12 @@
it('handles API errors', async () => {
const error = new Error('oops');
getProfile.mockRejectedValueOnce(error);
const wrapper = mount(ProjectsPage, {

Check warning on line 111 in tests/pages/ProjectsPage.test.ts

View workflow job for this annotation

GitHub Actions / format

'wrapper' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 111 in tests/pages/ProjectsPage.test.ts

View workflow job for this annotation

GitHub Actions / format

'wrapper' is assigned a value but never used. Allowed unused vars must match /^_/u
global: {
stubs: {
SideNavPartial: true,
HeaderPartial: true,
BackToTopLink: true,
WidgetSponsorPartial: true,
WidgetSkillsPartial: true,
FooterPartial: true,
Expand Down
2 changes: 1 addition & 1 deletion tests/partials/ExperiencePartial.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const experience: ExperienceResponse[] = [

describe('ExperiencePartial', () => {
it('renders each experience item', () => {
const wrapper = mount(ExperiencePartial, { props: { experience } });
const wrapper = mount(ExperiencePartial, { props: { experience, backToTopTarget: '#top' } });
const items = wrapper.findAll('li');
expect(items).toHaveLength(1);
expect(items[0].text()).toContain(experience[0].company);
Expand Down
4 changes: 3 additions & 1 deletion tests/partials/RecommendationPartial.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ describe('RecommendationPartial', () => {
];

it('sanitises and formats recommendation', () => {
const wrapper = mount(RecommendationPartial, { props: { recommendations: data } });
const wrapper = mount(RecommendationPartial, {
props: { recommendations: data, backToTopTarget: '#top' },
});
expect(renderMarkdown).toHaveBeenCalledWith('**great**');
expect(wrapper.html()).toContain('<strong>great</strong>');
expect(wrapper.text()).toContain('now');
Expand Down
2 changes: 1 addition & 1 deletion tests/partials/WidgetSocialPartial.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ describe('WidgetSocialPartial', () => {
const anchors = wrapper.findAll('a');
expect(anchors).toHaveLength(1);
expect(anchors[0].attributes('href')).toBe(social[0].url);
expect(anchors[0].text()).toContain('Follow me on GitHub');
expect(anchors[0].text()).toContain('Check out my code on GitHub');
});
});
Loading