Skip to content

Commit bdcf861

Browse files
committed
Create the components of the page
1 parent 05e6692 commit bdcf861

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

webpack/js/components.js

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import VueSelect from 'vue-select';
2+
3+
import {octokit} from './octokit';
4+
5+
export const IssueLabel = {
6+
template: `
7+
<span class="gh-label" :class="className">
8+
{{ name }}
9+
</span>`,
10+
data() {
11+
return {
12+
labels: window.labels
13+
}
14+
},
15+
props: {
16+
name: {
17+
type: String,
18+
required: true
19+
}
20+
},
21+
computed: {
22+
/**
23+
* Get the name of the class to apply to the label based on the group to
24+
* which it belongs. Falls back to miscellaneous if the label does not
25+
* belong to a group or a class cannot be identified.
26+
*
27+
* @returns {string} the name of the class to apply to the label
28+
*/
29+
className() {
30+
return window.className[this.name] || 'miscellaneous'
31+
}
32+
}
33+
}
34+
35+
export const IssueCard = {
36+
template: `
37+
<div class="card entry-post vertical margin-top-normal padding-normal">
38+
<h4 class="card-title b-header margin-bottom-small">{{ issue.title }}</h4>
39+
<a :href="issue.html_url" class="button is-text tiny site-link" target="_blank">
40+
<span class="has-color-forest-green">
41+
{{ issue.repository.name }}#{{ issue.number }}
42+
</span>
43+
<i class="icon external-link has-color-forest-green"></i>
44+
</a>
45+
<div class="labels margin-top-small">
46+
<IssueLabel
47+
v-for="(name, index) in issue.labelNames"
48+
:key="index"
49+
:name="name"/>
50+
</div>
51+
</div>`,
52+
components: {
53+
IssueLabel
54+
},
55+
props: {
56+
issue: {
57+
type: Object,
58+
required: true
59+
},
60+
}
61+
}
62+
63+
export const App = {
64+
el: '#vue-app',
65+
template: `
66+
<div class="find-issues">
67+
<div class="columns">
68+
<div class="column is-one-quarter">
69+
<form id="filters">
70+
<label for="skills">
71+
<strong>Skill set</strong><br>
72+
Choose up to three skills that you would like to see issues for.
73+
Leave blank if you don't have a preference.
74+
</label>
75+
<VueSelect
76+
v-model="filters.skills"
77+
id="skills"
78+
name="skills"
79+
:options="options.skills"
80+
:reduce="skill => skill.toLocaleLowerCase()"
81+
:selectable="() => filters.skills.length < 3"
82+
multiple/>
83+
<br/>
84+
<label for="experience">
85+
<strong>Experience</strong><br>
86+
Is this your first time contributing to CC?
87+
</label>
88+
<VueSelect
89+
v-model="filters.experience"
90+
id="experience"
91+
name="experience"
92+
:options="options.experiences"
93+
label="name"
94+
:reduce="experience => experience.code"
95+
:clearable="false"/>
96+
97+
<button
98+
class="button small is-success margin-top-small"
99+
@click.prevent="search">
100+
Search
101+
</button>
102+
</form>
103+
</div>
104+
<div class="column">
105+
<template v-if="issues.length">
106+
<issue-card
107+
v-for="issue in filteredIssues"
108+
:key="issue.id"
109+
:issue="issue"/>
110+
</template>
111+
<p
112+
v-else
113+
class="margin-top-normal">
114+
No results.
115+
</p>
116+
</div>
117+
</div>
118+
</div>`,
119+
components: {
120+
VueSelect,
121+
IssueCard
122+
},
123+
data() {
124+
return {
125+
options: {
126+
skills: window.skillSet,
127+
experiences: [
128+
{name: 'Yes, it is', code: 'beginner'},
129+
{name: 'No, it isn\'t', code: 'experienced'}
130+
]
131+
},
132+
filters: {
133+
skills: [],
134+
experience: 'experienced'
135+
},
136+
issues: []
137+
}
138+
},
139+
computed: {
140+
/**
141+
* Get a nested list of all the labels to search for. This is only based on
142+
* the experience filter. The skills filter must be applied on the client
143+
* side due to limitations of the GitHub API.
144+
*
145+
* This returns a list of lists. Each nested list corresponds to a single
146+
* API query and multiple queries need to be run to get the combined set of
147+
* valid issues.
148+
*
149+
* @returns {array} the array of array of labels
150+
*/
151+
labelsList() {
152+
const labelsList = []
153+
if (this.filters.experience === 'experienced') {
154+
labelsList.push('help wanted')
155+
} else {
156+
labelsList.push('good first issue')
157+
}
158+
return labelsList
159+
},
160+
/**
161+
* Get a filtered list of issues matching the chosen skill labels.
162+
*
163+
* @returns {array} the array of filtered issues
164+
*/
165+
filteredIssues() {
166+
let filtered = this.issues
167+
if (this.filters.skills.length) {
168+
filtered = filtered.filter(issue => {
169+
const joinedLabels = issue.labelNames.join(',')
170+
return this.filters.skills.some(skill => joinedLabels.includes(skill))
171+
})
172+
}
173+
return filtered
174+
}
175+
},
176+
methods: {
177+
/**
178+
* Get a Promise for the list of issues pertaining to a given labels.
179+
*
180+
* @param {Array} labels - list of labels to search for
181+
* @returns {Promise} a promise that resolves into a list of issues
182+
*/
183+
promise(labels = []) {
184+
const params = {
185+
org: 'creativecommons',
186+
state: 'open',
187+
filter: 'all',
188+
per_page: 200
189+
}
190+
if (labels) {
191+
params.labels = labels.join(',')
192+
}
193+
return octokit
194+
.issues
195+
.listForOrg(params)
196+
.then(response => response.data)
197+
},
198+
/**
199+
* Run the search based on the data submitted via the form and load all
200+
* results into the `issues` attribute.
201+
*/
202+
search() {
203+
this.promise(this.labelsList)
204+
.then(issueLists => {
205+
const issues = issueLists.flat()
206+
issues.forEach(issue => {
207+
issue.labelNames = issue.labels.map(label => label.name)
208+
})
209+
this.issues = issues
210+
})
211+
.catch(err => console.error(err))
212+
}
213+
},
214+
mounted() {
215+
this.search()
216+
}
217+
}

0 commit comments

Comments
 (0)