Skip to content

Commit 16b772d

Browse files
authored
Add vue.js jobs widget (#1484)
1 parent 985ef3c commit 16b772d

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<script lang="ts">
2+
// shared data across instances so we load only once
3+
const base = `https://vuejobs.com/api/postings`
4+
let openings = $ref([])
5+
</script>
6+
7+
<script setup lang="ts">
8+
import { onMounted, onUnmounted } from 'vue'
9+
import { useData } from 'vitepress'
10+
const { frontmatter } = useData()
11+
12+
let vuejobs = $ref<HTMLElement>()
13+
let visible = $ref(false)
14+
15+
onMounted(async () => {
16+
// only render when entering view
17+
const observer = new IntersectionObserver(
18+
(entries) => {
19+
if (entries[0].isIntersecting) {
20+
visible = true
21+
observer.disconnect()
22+
}
23+
},
24+
{ rootMargin: '0px 0px 300px 0px' }
25+
)
26+
observer.observe(vuejobs)
27+
onUnmounted(() => observer.disconnect())
28+
29+
// load data
30+
if (!openings.length) {
31+
const items = await (await fetch(`${base}`)).json()
32+
// choose two random items
33+
if (items && items.length) {
34+
openings = items.sort(() => 0.5 - Math.random()).slice(0, 2)
35+
}
36+
}
37+
})
38+
</script>
39+
40+
<template>
41+
<div v-if="frontmatter.vuejobs !== false" ref="vuejobs">
42+
<div class="vuejobs-container" v-if="openings.length">
43+
<div class="vj-item" v-for="(job, n) in openings" :key="n">
44+
<a class="vj-job-title" :href="job.link" target="_blank">
45+
<p>
46+
{{ job.title }}
47+
<svg
48+
xmlns="http://www.w3.org/2000/svg"
49+
aria-hidden="true"
50+
focusable="false"
51+
height="24px"
52+
viewBox="0 0 24 24"
53+
width="24px"
54+
class="vt-link-icon"
55+
>
56+
<path d="M0 0h24v24H0V0z" fill="none"></path>
57+
<path
58+
d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z"
59+
></path>
60+
</svg>
61+
</p>
62+
63+
<p class="vj-job-info">
64+
{{ job.company }}
65+
<span v-if="job.salary">·</span>
66+
{{ job.salary }}
67+
<span>·</span>
68+
{{ job.location }}
69+
</p>
70+
</a>
71+
</div>
72+
</div>
73+
</div>
74+
</template>
75+
76+
<style scoped>
77+
.vuejobs-container {
78+
background-color: var(--vt-c-bg-soft);
79+
padding: 5px 15px;
80+
border-radius: 2px;
81+
}
82+
.vj-item {
83+
padding: 10px 0 10px 0;
84+
border-bottom: 1px solid var(--vt-c-divider-light);
85+
display: flex;
86+
flex-direction: column;
87+
}
88+
.vj-item:last-child {
89+
border-bottom: none;
90+
}
91+
.vuejobs-container p,
92+
.vuejobs-container a {
93+
line-height: 16px;
94+
transition: color 0.2s ease;
95+
display: inline-block;
96+
}
97+
.vuejobs-container a:hover {
98+
color: var(--vt-c-brand);
99+
}
100+
.vj-job-title {
101+
font-size: 12px;
102+
color: var(--vt-c-text-1);
103+
}
104+
.vj-job-info {
105+
font-size: 11px;
106+
color: var(--vt-c-text-2);
107+
margin-top: 2px;
108+
line-height: 12px;
109+
}
110+
</style>

.vitepress/theme/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import {
99
filterHeadersByPreference
1010
} from './components/preferences'
1111
import SponsorsAside from './components/SponsorsAside.vue'
12+
import VueJobs from './components/VueJobs.vue'
1213

1314
export default Object.assign({}, VPTheme, {
1415
Layout: () => {
1516
// @ts-ignore
1617
return h(VPTheme.Layout, null, {
1718
banner: () => h(Banner),
1819
'sidebar-top': () => h(PreferenceSwitch),
19-
'aside-mid': () => h(SponsorsAside)
20+
'aside-mid': () => h(SponsorsAside),
21+
'aside-bottom': () => h(VueJobs)
2022
})
2123
},
2224
enhanceApp({ app }: { app: App }) {

src/sponsor/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ sidebar: false
33
ads: false
44
editLink: false
55
sponsors: false
6+
vuejobs: false
67
---
78

89
<script setup>

0 commit comments

Comments
 (0)