Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Commit 6ee07dd

Browse files
committed
✨ Implement admin mode 🔨 refactors
1 parent 4568bbd commit 6ee07dd

File tree

9 files changed

+211
-24
lines changed

9 files changed

+211
-24
lines changed

website/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"build": "node build/build.js"
1111
},
1212
"dependencies": {
13+
"clipboard-copy": "^2.0.0",
1314
"format-json": "^1.0.3",
1415
"front-matter": "^2.3.0",
1516
"fuzzaldrin-plus": "^0.6.0",
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<template>
2+
<div class="container-lg p-4">
3+
<div class="Subhead">
4+
<div class="Subhead-heading">Admin mode</div>
5+
</div>
6+
<div class="markdown-body">
7+
<p>When you activate <strong>admin mode</strong> you will see extra tools on the web app to make it easier.</p>
8+
<p>Admin mode status: <strong>{{ activated ? 'ON' : 'OFF' }}</strong></p>
9+
<p>
10+
<button
11+
class="btn btn-primary"
12+
@click="toggleAdminMode"
13+
>Toggle</button>
14+
</p>
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script>
20+
export default {
21+
computed: {
22+
activated() {
23+
return this.$store.state.admin
24+
}
25+
},
26+
methods: {
27+
toggleAdminMode() {
28+
this.$store.dispatch('toggleAdminMode')
29+
}
30+
}
31+
}
32+
</script>
33+
34+
<style>
35+
</style>

website/src/components/EventInfo.vue

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,14 @@
114114
<div class="markdown-body">
115115
<markdown :text="description"/>
116116
</div>
117-
<p class="text-right mt-4">
118-
<a
119-
:href="editLink"
120-
class="btn btn-secondary">Edit on GitHub</a>
121-
</p>
117+
<div class="d-flex flex-items-baseline">
118+
<p class="flex-auto" />
119+
<p class="text-right mt-4">
120+
<a
121+
:href="editLink"
122+
class="btn btn-secondary">Edit on GitHub</a>
123+
</p>
124+
</div>
122125
</div>
123126
</div>
124127
</div>
@@ -128,6 +131,7 @@
128131
import Octicon from 'vue-octicon/components/Octicon'
129132
import EventTags from './EventTags'
130133
import Markdown from './Markdown'
134+
import { MONTHS, DAYS } from '../utils'
131135
132136
export default {
133137
components: {
@@ -159,25 +163,11 @@ export default {
159163
},
160164
methods: {
161165
formatDate(d) {
162-
const MONTHS = [
163-
'January',
164-
'February',
165-
'March',
166-
'April',
167-
'May',
168-
'June',
169-
'July',
170-
'August',
171-
'September',
172-
'October',
173-
'November',
174-
'December'
175-
]
176166
return `${MONTHS[d.month - 1]} ${d.date}`
177167
},
178168
day(d) {
179169
const date = new Date(d.year, d.month - 1, d.date)
180-
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()]
170+
return DAYS[date.getDay()]
181171
},
182172
formatTime(t) {
183173
return `${t.hour}:${t.minute < 10 ? '0' : ''}${t.minute}`
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<template>
2+
<div>
3+
<div class="markdown-body mt-3">
4+
<h2>Share this event on Facebook page</h2>
5+
<p>Note: You must be a Facebook page admin.</p>
6+
<ol>
7+
<li>
8+
<p>
9+
<button
10+
class="btn btn-outline"
11+
type="button"
12+
@click="copyText()">Copy</button> the announcement text.
13+
</p>
14+
</li>
15+
<li>
16+
<p>
17+
<button
18+
class="btn btn-outline"
19+
type="button"
20+
@click="openShareDialog()">Share</button> the event on Facebook, pasting the copied text.
21+
</p>
22+
<ul>
23+
<li>Make sure to share on <strong>calendar.thaiprogrammer.org</strong> page.</li>
24+
</ul>
25+
</li>
26+
</ol>
27+
<h3>Announcement text</h3>
28+
<p>
29+
<textarea
30+
ref="textarea"
31+
:value="announcement"
32+
class="form-control input-monospace"
33+
style="width: 100%; height: 12em;"
34+
@dragover="$event.preventDefault()"
35+
@drop="handleDrop($event)"
36+
/>
37+
</p>
38+
</div>
39+
</div>
40+
</template>
41+
42+
<script>
43+
import { MONTHS, DAYS } from '../utils'
44+
45+
export default {
46+
props: {
47+
event: Object
48+
},
49+
computed: {
50+
announcement() {
51+
return getEventText(this.event)
52+
}
53+
},
54+
methods: {
55+
copyText() {
56+
this.$refs.textarea.focus()
57+
this.$refs.textarea.select()
58+
document.execCommand('copy')
59+
},
60+
openShareDialog() {
61+
const { event } = this
62+
const pageUrl = `https://calendar.thaiprogrammer.org/event/${event.id}`
63+
const fbEventLink = event.links.filter(link =>
64+
/https:\/\/(?:www|web)\.facebook\.com\/events\/\d+/.test(link.url)
65+
)[0]
66+
const fbEventUrl = fbEventLink && fbEventLink.url
67+
const link = fbEventUrl || pageUrl
68+
const shareQuery = [
69+
'app_id=1894360754189647',
70+
'display=popup',
71+
`href=${encodeURIComponent(link)}`
72+
].join('&')
73+
const shareUrl = `https://www.facebook.com/dialog/share?${shareQuery}`
74+
window.open(shareUrl, '_blank')
75+
}
76+
}
77+
}
78+
79+
function getEventText(event) {
80+
const url = `https://calendar.thaiprogrammer.org/event/${event.id}`
81+
const location = event.location && event.location.title
82+
const text = [
83+
`[Event] ${event.title}`,
84+
`${dates(event)}${location ? ` @ ${location}` : ''}`,
85+
'',
86+
`${event.summary}`,
87+
`${hashtags([...event.categories, ...event.topics])}`,
88+
url
89+
].join('\n')
90+
return text
91+
}
92+
93+
function hashtags(tags) {
94+
return tags.map(x => `#${x.replace(/[^a-zA-Z0-9_]/g, '')}`).join(' ')
95+
}
96+
97+
function dateOf({ year, month, date }) {
98+
return new Date(year, month - 1, date)
99+
}
100+
101+
function dates(event) {
102+
const formatDate = ({ year, month, date }) => {
103+
const day = dateOf({ year, month, date }).getDay()
104+
return `${MONTHS[month - 1]} ${date} (${DAYS[day]})`
105+
}
106+
const formatTime = t => `${t.hour}:${t.minute < 10 ? '0' : ''}${t.minute}`
107+
const start = formatDate(event.start)
108+
const end = formatDate(event.end)
109+
const time =
110+
event.time && event.time.length === 1
111+
? `${formatTime(event.time[0].from)} ~ ${formatTime(event.time[0].to)}`
112+
: ''
113+
return `${start}${end !== start ? ` ~ ${end}` : ''}${time ? `, ${time}` : ''}`
114+
}
115+
</script>
116+
117+
<style scoped>
118+
button.btn {
119+
vertical-align: baseline;
120+
}
121+
</style>

website/src/components/EventInfoPage.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
<p>Sorry, but the requested event cannot be found.</p>
1616
</div>
1717
<div v-if="!!event">
18-
<event-info :event="event"/>
18+
<EventInfo :event="event" />
19+
<details v-if="admin">
20+
<summary>for admin</summary>
21+
<EventInfoAdmin :event="event" />
22+
</details>
1923
</div>
2024
</div>
2125
<div
@@ -30,17 +34,19 @@
3034
import { mapState } from 'vuex'
3135
import Spinner from './Spinner'
3236
import EventInfo from './EventInfo'
37+
import EventInfoAdmin from './EventInfoAdmin'
3338
3439
export default {
3540
components: {
3641
Spinner,
37-
EventInfo
42+
EventInfo,
43+
EventInfoAdmin
3844
},
3945
computed: {
4046
id() {
4147
return this.$route.params.id
4248
},
43-
...mapState(['loading', 'error', 'events']),
49+
...mapState(['loading', 'error', 'events', 'admin']),
4450
event() {
4551
return this.events.filter(e => e.id === this.id)[0]
4652
}

website/src/router.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Vue from 'vue'
44
import EventInfoPage from './components/EventInfoPage'
55
import HomePage from './components/HomePage'
66
import ListPage from './components/ListPage'
7+
import AdminPage from './components/AdminPage'
78

89
Vue.use(Router)
910

@@ -32,6 +33,11 @@ export default new Router({
3233
component: () =>
3334
import(/* webpackChunkName: "data-generator" */ './components/DataEditorPage')
3435
},
36+
{
37+
path: '/admin',
38+
name: 'AdminPage',
39+
component: AdminPage
40+
},
3541
{
3642
path: '/event/:id',
3743
name: 'EventInfoPage',

website/src/store.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const store = new Vuex.Store({
77
state: {
88
events: [],
99
loading: true,
10-
error: null
10+
error: null,
11+
admin: !!localStorage.calendarAdmin
1112
},
1213
mutations: {
1314
eventsLoaded(state, { events }) {
@@ -17,6 +18,9 @@ const store = new Vuex.Store({
1718
eventsFailedToLoad(state, { error }) {
1819
state.error = error
1920
state.loading = false
21+
},
22+
adminModeSet(state, { admin }) {
23+
state.admin = admin
2024
}
2125
},
2226
actions: {
@@ -29,6 +33,11 @@ const store = new Vuex.Store({
2933
} catch (error) {
3034
commit('eventsFailedToLoad', { error })
3135
}
36+
},
37+
toggleAdminMode({ commit, state }) {
38+
const admin = !state.admin
39+
localStorage.calendarAdmin = admin ? '1' : ''
40+
commit('adminModeSet', { admin })
3241
}
3342
},
3443
getters: {

website/src/utils/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const MONTHS = [
2+
'January',
3+
'February',
4+
'March',
5+
'April',
6+
'May',
7+
'June',
8+
'July',
9+
'August',
10+
'September',
11+
'October',
12+
'November',
13+
'December'
14+
]
15+
export const DAYS = 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(',')

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,10 @@ cli-width@^2.0.0:
18121812
version "2.2.0"
18131813
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
18141814

1815+
clipboard-copy@^2.0.0:
1816+
version "2.0.0"
1817+
resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-2.0.0.tgz#663abcd8be9c641de6e92a2eb9afef6e0afa727e"
1818+
18151819
cliui@^2.1.0:
18161820
version "2.1.0"
18171821
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"

0 commit comments

Comments
 (0)