From ae470e263882c0b57a39a194ff0f0e2c241cc4ef Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 9 Feb 2021 17:10:05 +0800 Subject: [PATCH] finish add/update event page --- src/database.py | 2 +- src/dt.py | 36 ++-- src/server.py | 3 +- src/static/i18n/strings_en-US.properties | 31 ++-- src/static/i18n/strings_zh-CN.properties | 7 +- src/static/js/api.js | 32 ++-- src/static/js/datetime.js | 77 ++++++++- src/static/js/datetimepicker.js | 17 +- src/static/js/page/calendar.js | 4 +- src/static/js/page/event.js | 202 +++++++++++++++++++++-- src/static/js/utils.js | 26 ++- 11 files changed, 347 insertions(+), 90 deletions(-) diff --git a/src/database.py b/src/database.py index 0d45e6b..a7aaed5 100644 --- a/src/database.py +++ b/src/database.py @@ -228,7 +228,7 @@ class CalendarDatabase(object): # get prev data self.cursor.execute('SELECT * FROM calendar WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (uuid, lastChange)) - analyseData = self.cursor.fetchone() + analyseData = list(self.cursor.fetchone()) # construct update data lastupdate = utils.GenerateUUID() diff --git a/src/dt.py b/src/dt.py index 52aac4e..59d239b 100644 --- a/src/dt.py +++ b/src/dt.py @@ -28,7 +28,7 @@ def ResolveLoopStr(strl, starttime, tzoffset): return int(cache.group(1)) # group 1 is datetime cache = precompiledLoopStopRules['times'].search(loopStopRules) if cache is not None: - loopTimes = cache # for follwing calc + loopTimes = int(cache.group(1)) # for follwing calc else: raise Exception('Invalid loopStopRules') # invalid rules @@ -41,7 +41,7 @@ def ResolveLoopStr(strl, starttime, tzoffset): def LoopHandle_Year(searchResult, starttime, times, tzoffset): - clientDate = datetime.datetime.fromtimestamp(starttime, UTCTimezone(tzoffset)) + clientDate = datetime.datetime.fromtimestamp(starttime * 60, UTCTimezone(tzoffset)) isStrict = searchResult.group(1) == 'S' yearSpan = int(searchResult.group(2)) @@ -77,13 +77,14 @@ def LoopHandle_Month(searchResult, starttime, times, tzoffset): # we should get original data in each method times -= 1 - clientDate = datetime.datetime.fromtimestamp(starttime, UTCTimezone(tzoffset)) + clientDate = datetime.datetime.fromtimestamp(starttime * 60, UTCTimezone(tzoffset)) newYear = clientYear = clientDate.year newMonth = clientMonth = clientDate.month newDay = clientDay = clientDate.day # data struct # dayStatistics = - # (dayForwards, dayBackwards, weeksForward, dayOfWeekForward, weeksBackwards, dayOfWeekBackward) + # (dayForwards || dayBackwards || weeksForward, dayOfWeek || weeksBackwards, dayOfWeek) + # ( A || B || C || D ) dayStatistics = GetDayInMonth(clientYear, clientMonth, clientDay) if isStrict: @@ -139,13 +140,16 @@ def LoopHandle_Week(searchResult, starttime, times, tzoffset): raise Exception('Invalid week format') weekSpan = int(searchResult.group(2)) - nowDayOfWeek = datetime.datetime.fromtimestamp(starttime, UTCTimezone(tzoffset)).weekday() + nowDayOfWeek = datetime.datetime.fromtimestamp(starttime * 60, UTCTimezone(tzoffset)).weekday() if not weekOccupied[nowDayOfWeek]: - times+=1 # if first event is not suit for week loop rules, add one more event to suit it. - fullWeek = times / weekEventCount + times-=1 # if first event is not suit for week loop rules, minus one more event to suit it. + fullWeek = int(times / weekEventCount) remainEvent = times % weekEventCount + print(fullWeek) + print(remainEvent) + print(nowDayOfWeek) - val = DAY7_SPAN * fullWeek * weekSpan + val = starttime + DAY7_SPAN * fullWeek * weekSpan if val > MAX_TIMESTAMP: return MAX_TIMESTAMP # return now, to reduce calc usage @@ -231,21 +235,15 @@ def DayOfWeek(year, month, day): def GetDayInMonth(year, month, day): days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0) firstDayOfWeek = DayOfWeek(year, month, 1) - lastDayOfWeek = (firstDayOfWeek + days - 1) % 7 dayOfWeek = (firstDayOfWeek + day - 1) % 7 dayForwards = day dayBackwards = days - day + 1 - weeksForward = (dayForwards - 1) / 7 - weeksBackwards = (dayBackwards - 1) / 7 + weeksForward = int((dayForwards - 1) / 7) + 1 + weeksBackwards = int((dayBackwards - 1) / 7) + 1 - dayOfWeekForward = (firstDayOfWeek + ((dayForwards - 1) % 7)) % 7 - # 7 don't change week - # # just keep this is the positive number and prevent pretential minus number calc problem - dayOfWeekBackward = (7 + lastDayOfWeek - ((dayBackwards - 1) % 7)) % 7 - - return (dayForwards, dayBackwards, weeksForward, dayOfWeekForward, weeksBackwards, dayOfWeekBackward) + return (dayForwards, dayBackwards, weeksForward, dayOfWeek, weeksBackwards, dayOfWeek) def GetMonthWeekStatistics(year, month): days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0) @@ -253,7 +251,7 @@ def GetMonthWeekStatistics(year, month): lastDayOfWeek = (firstDayOfWeek + days - 1) % 7 result = [4, 4, 4, 4, 4, 4, 4] - remain = (days - 1) % 7 + remain = days % 7 week = firstDayOfWeek while remain > 0: result[week % 7] += 1 @@ -273,4 +271,4 @@ class UTCTimezone(datetime.tzinfo): return 'UTC {}'.format(self._offset) def dst(self, dt): - return timedelta(0) \ No newline at end of file + return datetime.timedelta(0) \ No newline at end of file diff --git a/src/server.py b/src/server.py index 4271be3..b7ee68c 100644 --- a/src/server.py +++ b/src/server.py @@ -173,8 +173,7 @@ def api_calendar_addHandle(): ('eventDateTimeStart', int, False), ('eventDateTimeEnd', int, False), ('loopRules', str, False), - ('timezoneOffset', int, False), - ('lastChange', str, False))) + ('timezoneOffset', int, False))) @app.route('/api/calendar/delete', methods=['POST']) def api_calendar_deleteHandle(): diff --git a/src/static/i18n/strings_en-US.properties b/src/static/i18n/strings_en-US.properties index c9237cb..655fa16 100644 --- a/src/static/i18n/strings_en-US.properties +++ b/src/static/i18n/strings_en-US.properties @@ -27,18 +27,18 @@ ccn-i18n-universal-week-4=Thursday ccn-i18n-universal-week-5=Friday ccn-i18n-universal-week-6=Saturday ccn-i18n-universal-week-7=Sunday -ccn-i18n-universal-month-1=1月 -ccn-i18n-universal-month-2=2月 -ccn-i18n-universal-month-3=3月 -ccn-i18n-universal-month-4=4月 -ccn-i18n-universal-month-5=5月 -ccn-i18n-universal-month-6=6月 -ccn-i18n-universal-month-7=7月 -ccn-i18n-universal-month-8=8月 -ccn-i18n-universal-month-9=9月 -ccn-i18n-universal-month-10=10月 -ccn-i18n-universal-month-11=11月 -ccn-i18n-universal-month-12=12月 +ccn-i18n-universal-month-1=January +ccn-i18n-universal-month-2=February +ccn-i18n-universal-month-3=March +ccn-i18n-universal-month-4=April +ccn-i18n-universal-month-5=May +ccn-i18n-universal-month-6=June +ccn-i18n-universal-month-7=July +ccn-i18n-universal-month-8=August +ccn-i18n-universal-month-9=September +ccn-i18n-universal-month-10=October +ccn-i18n-universal-month-11=November +ccn-i18n-universal-month-12=December ccn-i18n-messagebox-confirm=OK ccn-i18n-messagebox-title=Notification @@ -50,6 +50,7 @@ ccn-i18n-js-fail-add=An add operation failed. It may caused by wrong arguments. ccn-i18n-js-fail-update=An update operation failed. It may caused by wrong arguments or lost target. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data. ccn-i18n-js-fail-delete=A delete operation failed. It may caused by no matched item. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data. ccn-i18n-js-success=Operation OK. +ccn-i18n-js-fail-form=Your filled event form is not fufilled or have error. Please check it and try again. ccn-i18n-home-desc=

coconut-leaf

A light, self-host calendar system.

Originally, this app is served for yyc12345 personal use.


Pull request / issue / translation are welcomed.

Submit them in our GitHub project.

This project source code is licensed AGPL v3.

@@ -88,15 +89,15 @@ ccn-i18n-event-loopMonth-span=Month span ccn-i18n-event-loopWeek-option=Month mode ccn-i18n-event-loopWeek-optionA=Day {0} in month ccn-i18n-event-loopWeek-optionB=Day {0} from the end of the month -ccn-i18n-event-loopWeek-optionC=Week {0}, day {1} in month -ccn-i18n-event-loopWeek-optionD=Week {0}, day {1} from the end of the month +ccn-i18n-event-loopWeek-optionC=Day {1} in week {0} +ccn-i18n-event-loopWeek-optionD=Day {1} in week {0} from the end of the month ccn-i18n-event-loopYear-span=Year span ccn-i18n-event-loopStop=Event Loop Stop ccn-i18n-event-loopStop-forever=Forever ccn-i18n-event-loopStop-datetime=Date Time ccn-i18n-event-loopStop-times=Times ccn-i18n-event-timezone-title=Timezone -ccn-i18n-event-timezone-warning=The timezone of this event is not corresponding with your current timezone. You can choose a timezone option in follwing content. If you are not familar with this, please pick keep timezone. +ccn-i18n-event-timezone-warning=The timezone of this event is not corresponding with your current timezone. All of date and time in this page are shown as the original timezone of this event. You can choose a timezone option in follwing content. If you are not familar with this, please pick keep timezone. ccn-i18n-event-timezone-keep=Keep timezone ccn-i18n-event-timezone-replace=Use my timezone ccn-i18n-event-strictMode-title=Strict Mode in Event Loop diff --git a/src/static/i18n/strings_zh-CN.properties b/src/static/i18n/strings_zh-CN.properties index 3017f07..575aded 100644 --- a/src/static/i18n/strings_zh-CN.properties +++ b/src/static/i18n/strings_zh-CN.properties @@ -50,6 +50,7 @@ ccn-i18n-js-fail-add=一个添加操作失败了,可能是您输入的参数 ccn-i18n-js-fail-update=一个更新操作失败了,可能是没有找到匹配的条目或者您的参数输入错误。刷新页面可能会解决问题。请在刷新页面前备份好自己的数据。 ccn-i18n-js-fail-delete=一个删除操作失败了,可能是没有找到对应条目。刷新页面可能会解决问题。请在刷新页面前备份好自己的数据。 ccn-i18n-js-success=操作成功 +ccn-i18n-js-fail-form=您所填写的事件内容存在缺漏或有错误字段,请检查后再提交。 ccn-i18n-home-desc=

coconut-leaf

一个轻量的自建日历系统

原本是出于yyc12345的个人使用而制作的。


欢迎提出Pull request / issue / 翻译

将他们提交到我们的GitHub项目.

本工程代码使用AGPL v3授权。

@@ -94,15 +95,15 @@ ccn-i18n-event-loopMonth-span=间隔月数 ccn-i18n-event-loopWeek-option=月份模式 ccn-i18n-event-loopWeek-optionA=第{0}天 ccn-i18n-event-loopWeek-optionB=倒数第{0}天 -ccn-i18n-event-loopWeek-optionC=第{0}个星期第{1}天 -ccn-i18n-event-loopWeek-optionD=倒数第{0}个星期第{1}天 +ccn-i18n-event-loopWeek-optionC=第{0}个星期{1} +ccn-i18n-event-loopWeek-optionD=倒数第{0}个星期{1} ccn-i18n-event-loopYear-span=间隔年数 ccn-i18n-event-loopStop=事件循环停止方式 ccn-i18n-event-loopStop-forever=永不停止 ccn-i18n-event-loopStop-datetime=指定时间 ccn-i18n-event-loopStop-times=指定次数 ccn-i18n-event-timezone-title=时区设定 -ccn-i18n-event-timezone-warning=您当前设置的事件的时区与您的时区不匹配,您可以在下面修改您对于此事件的时区选择,如果您不熟悉时区,请选择保持原有时区。 +ccn-i18n-event-timezone-warning=您当前设置的事件的时区与您的时区不匹配,本页面以事件的原时区进行时间显示。您可以在下面修改您对于此事件的时区选择,如果您不熟悉时区,请选择保持原有时区。 ccn-i18n-event-timezone-keep=保持原有时区 ccn-i18n-event-timezone-replace=使用我现在的时区 ccn-i18n-event-strictMode-title=循环的严格与宽松 diff --git a/src/static/js/api.js b/src/static/js/api.js index cce4400..a64f455 100644 --- a/src/static/js/api.js +++ b/src/static/js/api.js @@ -200,20 +200,28 @@ function ccn_api_calendar_getDetail(_uuid) { } function ccn_api_calendar_update(_uuid, _belongTo, _title, _description, _eventDateTimeStart, _eventDateTimeEnd, _loopRules, _timezoneOffset, _lastChange) { + var data = {}; + if (typeof(_belongTo) != 'undefined') + data.belongTo = _belongTo; + if (typeof(_title) != 'undefined') + data.title = _title; + if (typeof(_description) != 'undefined') + data.description = _description; + if (typeof(_eventDateTimeStart) != 'undefined') + data.eventDateTimeStart = _eventDateTimeStart; + if (typeof(_eventDateTimeEnd) != 'undefined') + data.eventDateTimeEnd = _eventDateTimeEnd; + if (typeof(_loopRules) != 'undefined') + data.loopRules = _loopRules; + if (typeof(_timezoneOffset) != 'undefined') + data.timezoneOffset = _timezoneOffset; + + data.token = GetApiToken(); + data.uuid = _uuid; + data.lastChange = _lastChange; return ccn_api_dataTemplate( '/api/calendar/update', - { - token: GetApiToken(), - uuid: _uuid, - belongTo: _belongTo, - title: _title, - description: _description, - eventDateTimeStart: _eventDateTimeStart, - eventDateTimeEnd: _eventDateTimeEnd, - loopRules: _loopRules, - timezoneOffset: _timezoneOffset, - lastChange: _lastChange - } + data ); } diff --git a/src/static/js/datetime.js b/src/static/js/datetime.js index ebabf4e..ba0909c 100644 --- a/src/static/js/datetime.js +++ b/src/static/js/datetime.js @@ -1,3 +1,4 @@ +// NOTE: this file is sync with dt.py. if this file or dt.py have bugs, all code should be changed ccn_datetime_monthDayCount = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; ccn_datetime_MIN_YEAR = 1950; @@ -47,15 +48,16 @@ function ccn_datetime_ResolveLoopRules4UI(strl) { var loopStopRules = undefined; if (ccn_datetime_precompiledLoopRules.year.test(sp[0])) { - loopRules = [1, RegExp.$1 == 'S', parseInt(RegExp.$2)]; + loopRules = [0, RegExp.$1 == 'S', parseInt(RegExp.$2)]; } else if (ccn_datetime_precompiledLoopRules.month.test(sp[0])) { - loopRules = [2, RegExp.$1 == 'S', RegExp.$2, parseInt(RedExp.$3)]; + loopRules = [1, RegExp.$1 == 'S', RegExp.$2, parseInt(RedExp.$3)]; } else if (ccn_datetime_precompiledLoopRules.week.test(sp[0])) { - loopRules = [3]; - for (index in RegExp.$1) loopRules.push(RegExp.$1[index] == 'T'); + loopRules = [2]; + for (var i = 0; i < 7; i++) + loopRules.push(RegExp.$1[i] == 'T'); loopRules.push(parseInt(RegExp.$2)); } else if (ccn_datetime_precompiledLoopRules.day.test(sp[0])) { - loopRules = [4, parseInt(RegExp.$1)]; + loopRules = [3, parseInt(RegExp.$1)]; } else return undefined; @@ -74,6 +76,71 @@ function ccn_datetime_ResolveLoopRules4Event(strl) { return undefined; } +function ccn_datetime_LeapYearCountEx(endYear, includeThis, baseYear, includeBase) { + if (!includeThis) endYear--; + if (includeBase) baseYear--; + + var endly = Math.floor(endYear / 4); + endly -= Math.floor(endYear / 100); + endly += Math.floor(endYear / 400); + + var basely = Math.floor(baseYear / 4); + basely -= Math.floor(baseYear / 100); + basely += Math.floor(baseYear / 400); + + return (endly - basely); +} + +function ccn_datetime_DaysCount(year, month, day) { + var ly = ccn_datetime_LeapYearCountEx(year, false, 1, true); + var days = 365 * (year - 1); + days += ly; + + for(var index = 1; index < month; index++) + days += ccn_datetime_monthDayCount[index - 1]; + + if (month > 2 && ccn_datetime_IsLeapYear(year)) + days += 1; + + days += day - 1; + return days; +} + +function ccn_datetime_DayOfWeek(year, month, day) { + return ccn_datetime_DaysCount(year, month, day) % 7; +} + +function ccn_datetime_GetDayInMonth(year, month, day) { + var days = ccn_datetime_monthDayCount[month - 1] + ((month == '2' && ccn_datetime_IsLeapYear(year)) ? 1 : 0); + var firstDayOfWeek = ccn_datetime_DayOfWeek(year, month, 1); + var dayOfWeek = (firstDayOfWeek + day - 1) % 7; + + var dayForwards = day; + var dayBackwards = days - day + 1; + + var weeksForward = Math.floor((dayForwards - 1) / 7) + 1; + var weeksBackwards = Math.floor((dayBackwards - 1) / 7) + 1; + + return [dayForwards, dayBackwards, weeksForward, dayOfWeek, weeksBackwards, dayOfWeek]; +} + +function ccn_datetime_GetMonthWeekStatistics(year, month) { + var days = ccn_datetime_monthDayCount[month - 1] + ((month == '2' && ccn_datetime_IsLeapYear(year)) ? 1 : 0); + var firstDayOfWeek = ccn_datetime_DayOfWeek(year, month, 1); + var lastDayOfWeek = (firstDayOfWeek + days - 1) % 7; + + var result = [4, 4, 4, 4, 4, 4, 4]; + var remain = days % 7; + var week = firstDayOfWeek; + while (remain > 0) { + result[week % 7] += 1; + week++; + remain--; + } + + return result; +} + function ccn_datetime_IsLeapYear(year) { var isLeap = false; if (year % 4 == 0) isLeap = true; diff --git a/src/static/js/datetimepicker.js b/src/static/js/datetimepicker.js index 466ac83..5535aa3 100644 --- a/src/static/js/datetimepicker.js +++ b/src/static/js/datetimepicker.js @@ -50,15 +50,15 @@ function ccn_datetimepicker_SyncEx(pickerIndex) { } } -function ccn_datetimepicker_Set(pickerIndex, dt) { - $('.datetimepicker-year[datetimepicker=' + pickerIndex + ']').val(dt.getFullYear()); - $('.datetimepicker-month[datetimepicker=' + pickerIndex + ']').val(dt.getMonth() + 1); - $('.datetimepicker-day[datetimepicker=' + pickerIndex + ']').val(dt.getDate()); - $('.datetimepicker-hour[datetimepicker=' + pickerIndex + ']').val(dt.getHours()); - $('.datetimepicker-minute[datetimepicker=' + pickerIndex + ']').val(dt.getMinutes()); +function ccn_datetimepicker_Set(pickerIndex, dt, isUTC) { + $('.datetimepicker-year[datetimepicker=' + pickerIndex + ']').val(isUTC ? dt.getUTCFullYear() : dt.getFullYear()); + $('.datetimepicker-month[datetimepicker=' + pickerIndex + ']').val((isUTC ? dt.getUTCMonth() : dt.getMonth()) + 1); + $('.datetimepicker-day[datetimepicker=' + pickerIndex + ']').val(isUTC ? dt.getUTCDate() : dt.getDate()); + $('.datetimepicker-hour[datetimepicker=' + pickerIndex + ']').val(isUTC ? dt.getUTCHours() : dt.getHours()); + $('.datetimepicker-minute[datetimepicker=' + pickerIndex + ']').val(isUTC ? dt.getUTCMinutes() : dt.getMinutes()); } -function ccn_datetimepicker_Get(pickerIndex) { +function ccn_datetimepicker_Get(pickerIndex, isUTC) { year = $('.datetimepicker-year[datetimepicker=' + pickerIndex + ']').val(); month = $('.datetimepicker-month[datetimepicker=' + pickerIndex + ']').val(); day = $('.datetimepicker-day[datetimepicker=' + pickerIndex + ']').val(); @@ -70,5 +70,6 @@ function ccn_datetimepicker_Get(pickerIndex) { if (IsUndefinedOrEmpty(hour)) hour = 0; if (IsUndefinedOrEmpty(minute)) minute = 0; - return new Date(year, parseInt(month) - 1, day, hour, minute, 0, 0); + if (isUTC) return new Date(Date.UTC(year, parseInt(month) - 1, day, hour, minute, 0, 0)); + else return new Date(year, parseInt(month) - 1, day, hour, minute, 0, 0); } diff --git a/src/static/js/page/calendar.js b/src/static/js/page/calendar.js index 360889c..ebbcfe9 100644 --- a/src/static/js/page/calendar.js +++ b/src/static/js/page/calendar.js @@ -66,7 +66,7 @@ function ccn_calendar_calendar_LoadCalendarBody() { } function ccn_calendar_calendar_Refresh() { - gottenDateTime = ccn_datetimepicker_Get(1); + gottenDateTime = ccn_datetimepicker_Get(1, false); gottenYear = gottenDateTime.getFullYear(); gottenMonth = gottenDateTime.getMonth() + 1; } @@ -81,7 +81,7 @@ function ccn_calendar_calendar_AnalyseEvent() { function ccn_calendar_calendar_Today() { var nowtime = new Date(); - ccn_datetimepicker_Set(1, nowtime); + ccn_datetimepicker_Set(1, nowtime, false); ccn_calendar_calendar_Refresh(); } diff --git a/src/static/js/page/event.js b/src/static/js/page/event.js index eb148e5..789f6ce 100644 --- a/src/static/js/page/event.js +++ b/src/static/js/page/event.js @@ -25,6 +25,15 @@ $(document).ready(function() { // bind event $('input[type=radio][name=loop-method]').click(ccn_event_RefreshRadioDiaplay); $('input[type=radio][name=loop-end]').click(ccn_event_RefreshRadioDiaplay); + $('.datetimepicker-year[datetimepicker=1],.datetimepicker-month[datetimepicker=1],.datetimepicker-day[datetimepicker=1]').bind( + 'input propertychange', + ccn_event_RefreshLoopMonthType + ); + + $('#ccn-event-btnSubmit').click(ccn_event_btnSubmit); + $('#ccn-event-btnCancel').click(ccn_event_btnCancel); + $('#ccn-event-btnSpot').click(ccn_event_btnSpot); + $('#ccn-event-btnFullDay').click(ccn_event_btnFullDay); // init form ccn_event_Init(); @@ -91,7 +100,7 @@ function ccn_event_Init() { } // in add mode, set as -1, otherwise try to match original data // indexOf will return -1 if no matched item - collectionDOM.val(isAdd ? '' : ccn_calendar_eventModal_editing[1]); + collectionDOM.val(isAdd ? '' : ccn_event_editingEvent[1]); // init start and end datetime if (isAdd) { @@ -100,18 +109,18 @@ function ccn_event_Init() { currentDateTime.setMilliseconds(0); currentDateTime.setSeconds(0); currentDateTime.setMinutes(0); - ccn_datetimepicker_Set(1, currentDateTime); + ccn_datetimepicker_Set(1, currentDateTime, false); // time span is 2 hours currentDateTime.setHours(currentDateTime.getHours() + 2); - ccn_datetimepicker_Set(2, currentDateTime); + ccn_datetimepicker_Set(2, currentDateTime, false); } else { // in update mode, match it with original data - var originalDateTime = new Date(ccn_event_editingEvent[5] * 60000); - ccn_datetimepicker_Set(1, originalDateTime); + var originalDateTime = new Date((ccn_event_editingEvent[5] + ccn_event_editingEvent[7]) * 60000); + ccn_datetimepicker_Set(1, originalDateTime, true); - originalDateTime = new Date(ccn_event_editingEvent[6] * 60000); - ccn_datetimepicker_Set(2, originalDateTime); + originalDateTime = new Date((ccn_event_editingEvent[6] + ccn_event_editingEvent[7]) * 60000); + ccn_datetimepicker_Set(2, originalDateTime, true); } // setup timezone here @@ -120,7 +129,7 @@ function ccn_event_Init() { $('#ccn-event-timezone-radioKeep').prop('checked', true); // give a default value var nowtime = new Date(); SmarterShowHide( - (!isAdd) && nowtime.getTimezoneOffset() != ccn_event_editingEvent[7], + (!isAdd) && (-nowtime.getTimezoneOffset()) != ccn_event_editingEvent[7], $('#ccn-event-boxTimezone') ); @@ -133,23 +142,26 @@ function ccn_event_Init() { // give some value with a default value $('#ccn-event-loopMonth-radioA').prop('checked', true); - var weekDate = undefined; - if (isAdd) weekDate = nowtime; - else weekDate = new Date(ccn_event_editingEvent[5] * 60000); - $('#ccn-event-loopWeek-check' + (weekDate.getDay() + 1)).prop('checked', true); + $('#ccn-event-loopWeek-check' + (nowtime.getWeekday() + 1)).prop('checked', true); + $('#ccn-event-strictMode-radioStrict').prop('checked', true); // real process - if (isAdd) $('#ccn-event-radioLoopNever').prop('checked', true); - else { + if (isAdd) { + $('#ccn-event-radioLoopNever').prop('checked', true); + } else { switch(data[0][0]) { case 0: $('#ccn-event-radioLoopYear').prop('checked', true); $('#ccn-event-loopYear-inputSpan').val(data[0][2]); + if (data[0][1]) $('#ccn-event-strictMode-radioStrict').prop('checked', true); + else $('#ccn-event-strictMode-radioRough').prop('checked', true); break; case 1: $('#ccn-event-radioLoopMonth').prop('checked', true); $('#ccn-event-loopMonth-inputSpan').val(data[0][3]); $('#ccn-event-loopMonth-radio' + data[0][2]).prop('checked', true); + if (data[0][1]) $('#ccn-event-strictMode-radioStrict').prop('checked', true); + else $('#ccn-event-strictMode-radioRough').prop('checked', true); break; case 2: $('#ccn-event-radioLoopWeek').prop('checked', true); @@ -165,9 +177,11 @@ function ccn_event_Init() { } } + // give some item a default value + ccn_datetimepicker_Set(3, nowtime, false); + if (isAdd) { $('#ccn-event-loopStop-radioForever').prop('checked', true); - ccn_datetimepicker_Set(3, nowtime); } else { switch(data[1][0]) { case 0: @@ -175,8 +189,8 @@ function ccn_event_Init() { break; case 1: $('#ccn-event-loopStop-radioDateTime').prop('checked', true); - var stopDatetime = new Date(data[1][1] * 60000); - ccn_datetimepicker_Set(3, stopDatetime); + var stopDatetime = new Date((data[1][1] + ccn_event_editingEvent[7]) * 60000); + ccn_datetimepicker_Set(3, stopDatetime, true); break; case 2: $('#ccn-event-loopStop-radioTimes').prop('checked', true); @@ -212,11 +226,163 @@ function ccn_event_RefreshRadioDiaplay() { } function ccn_event_RefreshLoopMonthType() { + var picker = ccn_datetimepicker_Get(1, false); + var data = ccn_datetime_GetDayInMonth(picker.getFullYear(), picker.getMonth() + 1, picker.getDate()); + $('#ccn-event-loopMonth-textA').text($.i18n.prop('ccn-i18n-event-loopWeek-optionA').format(data[0])); + $('#ccn-event-loopMonth-textB').text($.i18n.prop('ccn-i18n-event-loopWeek-optionB').format(data[1])); + $('#ccn-event-loopMonth-textC').text($.i18n.prop('ccn-i18n-event-loopWeek-optionC').format(data[2], data[3] + 1)); + $('#ccn-event-loopMonth-textD').text($.i18n.prop('ccn-i18n-event-loopWeek-optionD').format(data[4], data[5] + 1)); } // return undefined to indicate an error -function ccn_event_Get() { +// or +// [belongTo, title, description, eventDateTimeStart, eventDateTimeEnd, timezoneOffset, loopRules] +function ccn_event_GetForm() { + // basic + var title = $('#ccn-event-inputTitle').val(); + if (title == '') return undefined; + var description = $('#ccn-event-inputDescription').val(); + if (description == '') return undefined; + var belongTo = $('#ccn-event-inputCollection').val(); + if (belongTo == null) return undefined; // if no selected item, val return null, not undefined + var isAdd = typeof(ccn_event_editingEvent) == 'undefined'; + var keepTimezone = $('#ccn-event-timezone-radioKeep').prop('checked'); + var isStrict = $('#ccn-event-strictMode-radioStrict').prop('checked'); + + // time + var eventDateTimeStart = undefined; + var eventDateTimeEnd = undefined; + var timezoneOffset = undefined; + if ((!isAdd) && (!keepTimezone)) { + // get datetime as utc, then minus original timezone to get unix timestamp + timezoneOffset = ccn_event_editingEvent[7]; // keep timezone + eventDateTimeStart = Math.floor(ccn_datetimepicker_Get(1, true).getTime() / 60000) - timezoneOffset; + eventDateTimeEnd = Math.floor(ccn_datetimepicker_Get(2, true).getTime() / 60000) - timezoneOffset; + } else { + // use my timezone, resolve presented data as my local time + var cache = ccn_datetimepicker_Get(1, false); + timezoneOffset = -cache.getTimezoneOffset(); + eventDateTimeStart = Math.floor(cache.getTime() / 60000); + eventDateTimeEnd = Math.floor(ccn_datetimepicker_Get(2, false).getTime() / 60000); + } + + // loopRules + var loopRules = undefined; + if ($('#ccn-event-radioLoopNever').prop('checked')) { + loopRules = ""; + } else if ($('#ccn-event-radioLoopDay').prop('checked')) { + loopRules = "D{0}".format($('#ccn-event-loopDay-inputSpan').val()); + } else if ($('#ccn-event-radioLoopWeek').prop('checked')) { + var cache = "" + for(var i = 1; i < 8; i++) + cache += $('#ccn-event-loopWeek-check' + i).prop('checked') ? 'T' : 'F'; + loopRules = 'W{0}{1}'.format( + cache, + $('#ccn-event-loopWeek-inputSpan').val() + ); + } else if ($('#ccn-event-radioLoopMonth').prop('checked')) { + var cache = undefined; + if ($('#ccn-event-loopMonth-radioA').prop('checked')) cache='A'; + else if ($('#ccn-event-loopMonth-radioB').prop('checked')) cache='B'; + else if ($('#ccn-event-loopMonth-radioC').prop('checked')) cache='C'; + else if ($('#ccn-event-loopMonth-radioD').prop('checked')) cache='D'; + else return undefined; + + loopRules = "M{0}{1}{2}".format( + isStrict ? "S" : "R", + cache, + $('#ccn-event-loopMonth-inputSpan').val() + ); + } else if ($('#ccn-event-radioLoopYear').prop('checked')) { + loopRules = "Y{0}{1}".format( + isStrict ? "S" : "R", + $('#ccn-event-loopDay-inputSpan').val() + ); + } + + // no need to process stop if this is not a loop event + if (loopRules != "") { + loopRules += '-'; + if ($('#ccn-event-loopStop-radioForever').prop('checked')) { + loopRules += 'F'; + } else if ($('#ccn-event-loopStop-radioDateTime').prop('checked')) { + var timestamp = undefined; + if ((!isAdd) && (!keepTimezone)) { + // keep timezone + var cache = ccn_datetimepicker_Get(3, true); + cache.setUTCHours(23); + cache.setUTCMinutes(59); + timestamp = Math.floor(cache.getTime() / 60000) - timezoneOffset; + } else { + // use my timezone + timestamp = Math.floor(ccn_datetimepicker_Get(3, false).getTime() / 60000); + } + + loopRules += 'D{0}'.format(timestamp); + } else if ($('#ccn-event-loopStop-radioTimes').prop('checked')) { + loopRules += 'T{0}'.format($('#ccn-event-loopStop-inputTimes').val()); + } + } + + return [belongTo, title, description, eventDateTimeStart, eventDateTimeEnd, timezoneOffset, loopRules]; +} + +function ccn_event_btnSpot() { + var datetime = ccn_datetimepicker_Get(1, false); + datetime.setMinutes(datetime.getMinutes() + 1); + ccn_datetimepicker_Set(2, datetime, false); +} + +function ccn_event_btnFullDay() { + var datetime = ccn_datetimepicker_Get(1, false); + datetime.setMinutes(0); + datetime.setHours(0); + ccn_datetimepicker_Set(1, datetime, false); + datetime.setMinutes(59); + datetime.setHours(23); + ccn_datetimepicker_Set(2, datetime, false); +} + +function ccn_event_btnCancel() { + window.location.href = '/web/calendar'; +} + +function ccn_event_btnSubmit() { + var submitData = ccn_event_GetForm(); + if (typeof(submitData) == 'undefined') { + ccn_messagebox_Show($.i18n.prop("ccn-i18n-js-fail-form")); + return; + } + + var isAdd = typeof(ccn_event_editingEvent) == 'undefined'; + if (isAdd) { + var result = ccn_api_calendar_add( + submitData[0], + submitData[1], + submitData[2], + submitData[3], + submitData[4], + submitData[6], + submitData[5] + ); + if (typeof(result) == 'undefined') ccn_messagebox_Show($.i18n.prop("ccn-i18n-js-fail-add")); + else window.location.href = '/web/calendar'; + } else { + var result = ccn_api_calendar_update( + ccn_event_editingEvent[0], + ccn_event_editingEvent[1] == submitData[0] ? undefined : submitData[0], + ccn_event_editingEvent[2] == submitData[1] ? undefined : submitData[1], + ccn_event_editingEvent[3] == submitData[2] ? undefined : submitData[2], + ccn_event_editingEvent[5] == submitData[3] ? undefined : submitData[3], + ccn_event_editingEvent[6] == submitData[4] ? undefined : submitData[4], + ccn_event_editingEvent[8] == submitData[6] ? undefined : submitData[6], + ccn_event_editingEvent[7] == submitData[5] ? undefined : submitData[5], + ccn_event_editingEvent[4] + ); + if (typeof(result) == 'undefined') ccn_messagebox_Show($.i18n.prop("ccn-i18n-js-fail-update")); + else window.location.href = '/web/calendar'; + } } diff --git a/src/static/js/utils.js b/src/static/js/utils.js index fbd2dc5..7b7b7c8 100644 --- a/src/static/js/utils.js +++ b/src/static/js/utils.js @@ -17,7 +17,7 @@ function ComputeSHA256(strl) { */ function IsResponseOK(data) { - if (typeof(data) == 'undefined') { + if (typeof (data) == 'undefined') { console.log("Fail to execute an api!"); return false; } @@ -38,15 +38,31 @@ function SetApiToken(value) { } function LineBreaker2Br(strl) { - return $('
').text(strl).html().replace(/\n/g,'
'); + return $('
').text(strl).html().replace(/\n/g, '
'); } function IsUndefinedOrEmpty(data) { - return (typeof(data) == 'undefined' || data == ""); + return (typeof (data) == 'undefined' || data == ""); } function SmarterShowHide(boolean, element) { - if (typeof(element) == 'undefined') return; + if (typeof (element) == 'undefined') return; if (boolean) element.show(); else element.hide(); -} \ No newline at end of file +} + +String.prototype.format = function() { + var e = arguments; + return !!this && this.replace( + /\{(\d+)\}/g, + function (t, n) { + return e[n].toString() ? e[n].toString() : t; + } + ); +}; + +Date.prototype.getWeekday = function() { + var temp = this.getDay(); + if (temp == 0) return 6; + else return temp - 1; +}; \ No newline at end of file