Skip to content

Commit 5aa4418

Browse files
authored
Merge pull request #1167 from Jim-Zenn/schedule
加入 Google Calendar 支持,并完成了一个包含前端逻辑的日程页面
2 parents 263d30e + f49f08f commit 5aa4418

File tree

8 files changed

+330
-1
lines changed

8 files changed

+330
-1
lines changed

_config.yml

+19-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ menu:
3333
#about: /about
3434
archives: /archives
3535
tags: /tags
36+
schedule: /schedule
3637
#commonweal: /404.html
3738

3839

@@ -42,11 +43,12 @@ menu:
4243
# Key is the name of menu item and value is the name of FontAwsome icon. Key is case-senstive.
4344
# When an question mask icon presenting up means that the item has no mapping icon.
4445
menu_icons:
45-
enable: true
46+
enable: false
4647
#KeyMapsToMenuItemKey: NameOfTheIconFromFontAwesome
4748
home: home
4849
about: user
4950
categories: th
51+
schedule: calendar
5052
tags: tags
5153
archives: archive
5254
commonweal: heartbeat
@@ -333,6 +335,22 @@ busuanzi_count:
333335
# Enable baidu push so that the blog will push the url to baidu automatically which is very helpful for SEO
334336
baidu_push: false
335337

338+
# Google Calendar
339+
# Share your recent schedule to others via calendar page
340+
#
341+
# API Documentation:
342+
# https://developers.google.com/google-apps/calendar/v3/reference/events/list
343+
calendar:
344+
enable: false
345+
calendar_id: <required>
346+
api_key: <required>
347+
orderBy: startTime
348+
offsetMax: 24
349+
offsetMin: 4
350+
timeZone:
351+
showDeleted: false
352+
singleEvents: true
353+
maxResults: 250
336354

337355

338356
#! ---------------------------------------------------------------

languages/en.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
title:
22
archive: Archive
33
category: Category
4+
schedule: Schedule
45
tag: Tag
56

67
author: Author
@@ -9,6 +10,7 @@ menu:
910
home: Home
1011
archives: Archives
1112
categories: Categories
13+
schedule: Schedule
1214
tags: Tags
1315
about: About
1416
search: Search

languages/zh-Hans.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
title:
22
archive: 归档
33
category: 分类
4+
schedule: 日程
45
tag: 标签
56

67
author: 博主
@@ -9,6 +10,7 @@ menu:
910
home: 首页
1011
archives: 归档
1112
categories: 分类
13+
schedule: 日程
1214
tags: 标签
1315
about: 关于
1416
search: 搜索

layout/_layout.swig

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
{% include '_scripts/third-party/mathjax.swig' %}
7272
{% include '_scripts/third-party/lean-analytics.swig' %}
7373
{% include '_scripts/baidu-push.swig' %}
74+
{% include '_scripts/third-party/schedule.swig' %}
7475

7576
</body>
7677
</html>
+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
{% if theme.calendar.enable %}
2+
{% if page.type == 'schedule' %}
3+
4+
<script>
5+
6+
// Initialization
7+
var _n = function(arg) { if(arg) return arg; else return void 0;}
8+
9+
var cal_data = void 0;
10+
11+
var now = new Date();
12+
var timeMax = new Date();
13+
var timeMin = new Date();
14+
15+
// Read config form theme config file
16+
var calId = _n('{{ theme.calendar.calendar_id }}') ;
17+
var apiKey = _n('{{ theme.calendar.api_key }}') ;
18+
var orderBy = _n('{{ theme.calendar.ordarBy }}') || 'startTime';
19+
var showLocation = _n('{{ theme.calendar.showLocation }}') || 'false' ;
20+
var offsetMax = _n( {{ theme.calendar.offsetMax }} ) || 72 ;
21+
var offsetMin = _n( {{ theme.calendar.offsetMin }} ) || 4 ;
22+
var timeZone = _n( {{ theme.calendar.timeZone }} ) || void 0 ;
23+
var showDeleted = _n( {{ theme.calendar.showDeleted }} ) || 'false' ;
24+
var singleEvents = _n( {{ theme.calendar.singleEvents }} ) || 'true' ;
25+
var maxResults = _n( {{ theme.calendar.maxResults }} ) || '250' ;
26+
27+
timeMax.setHours(now.getHours() + offsetMax);
28+
timeMin.setHours(now.getHours() - offsetMin);
29+
30+
// Build URL
31+
BASE_URL = 'https://www.googleapis.com/calendar/v3/calendars/';
32+
FIELD_KEY = 'key';
33+
FIELD_ORDERBY = 'orderBy';
34+
FIELD_TIMEMAX = 'timeMax';
35+
FIELD_TIMEMIN = 'timeMin';
36+
FIELD_TIMEZONE = 'timeZone';
37+
FIELD_SHOWDELETED = 'showDeleted';
38+
FIELD_SINGLEEVENTS = 'singleEvents';
39+
FIELD_MAXRESULTS = 'maxResults';
40+
41+
timeMaxISO = timeMax.toISOString();
42+
timeMinISO = timeMin.toISOString();
43+
44+
request_url = BASE_URL + calId + '/events?' +
45+
FIELD_KEY + '=' + apiKey + '&' +
46+
FIELD_ORDERBY + '=' + orderBy + '&' +
47+
FIELD_TIMEMAX + '=' + timeMaxISO + '&' +
48+
FIELD_TIMEMIN + '=' + timeMinISO + '&' +
49+
FIELD_SHOWDELETED + '=' + showDeleted + '&' +
50+
FIELD_SINGLEEVENTS + '=' + singleEvents + '&' +
51+
FIELD_MAXRESULTS + '=' + maxResults;
52+
53+
if (timeZone) {
54+
request_url = request_url + '&' + FIELD_TIMEZONE + '=' + timeZone;
55+
}
56+
57+
fetchData();
58+
var queryLoop = setInterval(fetchData, 60000);
59+
60+
function fetchData() {
61+
$.ajax({
62+
dataType: 'json',
63+
url: request_url,
64+
success: function(data) {
65+
66+
$eventList = $('#schedule #event-list');
67+
68+
// clean the event list
69+
$eventList.html("");
70+
var prevEnd = 0; // used to decide where to insert an <hr>
71+
72+
data['items'].forEach((event) => {
73+
74+
// parse data
75+
var start = new Date(event.start.dateTime);
76+
var end = new Date(event.end.dateTime);
77+
78+
tense = judgeTense(now, start, end); // 0:now 1:future -1:past
79+
80+
if (tense == 1 && prevEnd < now) $eventList.append('<hr>');
81+
82+
eventDOM = buildEventDOM(tense, event);
83+
$eventList.append(eventDOM);
84+
85+
prevEnd = end;
86+
});
87+
}
88+
});
89+
}
90+
91+
function getRelativeTime(current, previous) {
92+
var msPerMinute = 60 * 1000;
93+
var msPerHour = msPerMinute * 60;
94+
var msPerDay = msPerHour * 24;
95+
var msPerMonth = msPerDay * 30;
96+
var msPerYear = msPerDay * 365;
97+
98+
var elapsed = current - previous;
99+
var tense = elapsed > 0 ? "ago" : "later";
100+
101+
elapsed = Math.abs(elapsed);
102+
103+
if ( elapsed < msPerHour ) {
104+
return Math.round(elapsed/msPerMinute) + ' minutes ' + tense;
105+
}
106+
else if ( elapsed < msPerDay ) {
107+
return Math.round(elapsed/msPerHour) + ' hours ' + tense;
108+
}
109+
else if ( elapsed < msPerMonth ) {
110+
return 'about ' + Math.round(elapsed/msPerDay) + ' days ' + tense;
111+
}
112+
else if ( elapsed < msPerYear ) {
113+
return 'about ' + Math.round(elapsed/msPerMonth) + ' months ' + tense;
114+
}
115+
else {
116+
return 'about' + Math.round(elapsed/msPerYear) + ' years' + tense;
117+
}
118+
}
119+
120+
function judgeTense(now, eventStart, eventEnd) {
121+
if (eventEnd < now) { return -1; }
122+
else if (eventStart > now) { return 1; }
123+
else { return 0; }
124+
}
125+
126+
function buildEventDOM(tense, event) {
127+
var tenseClass = "";
128+
var start = new Date(event.start.dateTime);
129+
var end = new Date(event.end.dateTime);
130+
switch(tense) {
131+
case 0 : // now
132+
tenseClass = "event-now";
133+
break;
134+
case 1 : // future
135+
tenseClass = "event-future";
136+
break;
137+
case -1: // past
138+
tenseClass = "event-past";
139+
break;
140+
default:
141+
throw "Time data error";
142+
}
143+
durationFormat = {
144+
weekday: 'short',
145+
hour: '2-digit',
146+
minute:'2-digit'
147+
};
148+
relativeTimeStr = (tense == 0) ? "NOW" : getRelativeTime(now, start);
149+
durationStr = start.toLocaleTimeString([], durationFormat) + " - " +
150+
end.toLocaleTimeString([], durationFormat);
151+
152+
liOpen = `<li class="event ${tenseClass}">`;
153+
liClose = `</li>`;
154+
h2Open = `<h2 class="event-summary">`;
155+
h2Close = `</h2>`;
156+
157+
locationDOM = "";
158+
if (showLocation && event['location']) {
159+
locationDOM = `<span class="event-location event-details">
160+
${event['location']}
161+
</span>`;
162+
}
163+
relativeTimeDOM = `<span class="event-relative-time">
164+
${relativeTimeStr}
165+
</span>`;
166+
durationDOM = `<span class="event-duration event-details">
167+
${durationStr}
168+
</span>`;
169+
170+
eventContent =
171+
liOpen +
172+
h2Open +
173+
event['summary'] +
174+
relativeTimeDOM+
175+
h2Close +
176+
locationDOM +
177+
durationDOM +
178+
liClose;
179+
return eventContent;
180+
}
181+
182+
</script>
183+
184+
{% endif %}
185+
{% endif %}

layout/schedule.swig

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{% extends '_layout.swig' %}
2+
{% import '_macro/sidebar.swig' as sidebar_template %}
3+
4+
{% block title %} {{ __('title.schedule') }} | {{ config.title }} {% endblock %}
5+
6+
{% block page_calendar %}page-post-detail{% endblock %}
7+
8+
{% block content %}
9+
10+
<section id="schedule">
11+
<ul id="event-list">
12+
</ul>
13+
</section>
14+
15+
{% endblock %}
16+
17+
{% block sidebar %}
18+
{{ sidebar_template.render(false) }}
19+
{% endblock %}

source/css/_common/components/pages/pages.styl

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
@import "archive";
44
@import "categories";
5+
@import "schedule";
56
@import "post-detail";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
@keyframes dot-flash {
2+
from {opacity: 1; transform:scale(1.1);}
3+
to {opacity: 0; transform:scale(1);}
4+
}
5+
6+
#schedule {
7+
ul#event-list {
8+
padding-left: 30px
9+
hr {
10+
margin: 20px 0 45px 0!important
11+
background: #222
12+
&:after {
13+
display: inline-block
14+
content: 'NOW'
15+
background: #222
16+
color: #FFF
17+
font-weight:bold
18+
text-align: right
19+
padding: 0 5px
20+
}
21+
}
22+
li.event {
23+
margin: 20px 0px
24+
background: #F9F9F9
25+
padding-left: 10px
26+
min-height: 40px
27+
h2.event-summary {
28+
margin: 0
29+
padding-bottom: 3px
30+
&:before {
31+
display: inline-block
32+
font-family: FontAwesome
33+
font-size: 8px
34+
content: '\f111'
35+
vertical-align: middle
36+
margin-right: 25px
37+
color: #bbb
38+
}
39+
}
40+
span.event-relative-time {
41+
display: inline-block
42+
font-size: 12px
43+
font-weight: 400
44+
padding-left: 12px
45+
color: #bbb
46+
}
47+
span.event-details {
48+
display: block
49+
color: #bbb
50+
margin-left: 56px
51+
padding-top: 3px
52+
padding-bottom: 6px
53+
text-indent: -24px
54+
line-height: 18px
55+
&:before {
56+
text-indent: 0
57+
display: inline-block
58+
width: 14px
59+
font-family: FontAwesome
60+
text-align: center
61+
margin-right: 9px
62+
color: #bbb
63+
}
64+
&.event-location:before {
65+
content: '\f041'
66+
}
67+
&.event-duration:before {
68+
content: '\f017'
69+
}
70+
}
71+
}
72+
li.event-past {
73+
background: #FCFCFC
74+
& > * {
75+
opacity: .6
76+
}
77+
h2.event-summary {
78+
color: #bbb
79+
&:before {
80+
color: #DFDFDF
81+
}
82+
}
83+
}
84+
li.event-now {
85+
background: #222
86+
color: #FFF
87+
padding: 15px 0 15px 10px
88+
h2.event-summary {
89+
&:before {
90+
transform: scale(1.2)
91+
color: #FFF
92+
animation: dot-flash 1s alternate infinite ease-in-out;
93+
}
94+
}
95+
* {
96+
color: #FFF!important
97+
}
98+
}
99+
}
100+
}
101+

0 commit comments

Comments
 (0)