diff --git a/src/database.py b/src/database.py index 2391ab0..a0a585d 100644 --- a/src/database.py +++ b/src/database.py @@ -114,8 +114,8 @@ class CalendarDatabase(object): )) return token else: - # return empty string to indicate fail to login - return '' + # throw a exception to indicate fail to login + raise Exception() @SafeDatabaseOperation def common_webLogin(self, username, password): @@ -130,23 +130,20 @@ class CalendarDatabase(object): )) return token else: - # return empty string to indicate fail to login - return '' + # throw a exception to indicate fail to login + raise Exception() @SafeDatabaseOperation def common_logout(self, token): username = self.get_username_from_token(token) self.cursor.execute('UPDATE user SET [ccn_tokenExpireOn] = 0 WHERE [ccn_name] = ?;', (username, )) - return True + return None @SafeDatabaseOperation def common_tokenValid(self, token): # get user name have check the validation, don't do anything more. - try: - self.get_username_from_token(token) - return True - except: - return False + self.get_username_from_token(token) + return None @SafeDatabaseOperation def common_isAdmin(self, token): @@ -194,13 +191,14 @@ class CalendarDatabase(object): username = self.get_username_from_token(token) newuuid = utils.GenerateUUID() lastupdate = utils.GenerateUUID() - self.cursor.execute('INSERT INTO todo VALUES (?, ?, ?, ?);', ( + returnedData = ( newuuid, username, '', lastupdate, - )) - return newuuid + ) + self.cursor.execute('INSERT INTO todo VALUES (?, ?, ?, ?);', returnedData) + return returnedData @SafeDatabaseOperation def todo_update(self, token, uuid, data, lastChange): @@ -212,14 +210,16 @@ class CalendarDatabase(object): lastChange )) if len(self.cursor.fetchall()) == 0: - return False + raise Exception() # update - self.cursor.execute('UPDATE todo SET [ccn_data] = ? WHERE [ccn_uuid] = ?;', ( + newLastChange = utils.GenerateUUID() + self.cursor.execute('UPDATE todo SET [ccn_data] = ?, [ccn_lastChange] = ? WHERE [ccn_uuid] = ?;', ( data, + newLastChange, uuid )) - return True + return newLastChange @SafeDatabaseOperation def todo_delete(self, token, uuid, lastChange): @@ -231,11 +231,11 @@ class CalendarDatabase(object): lastChange )) if len(self.cursor.fetchall()) == 0: - return False + raise Exception() # delete self.cursor.execute('DELETE FROM todo WHERE [ccn_uuid] = ?;', (uuid, )) - return True + return None # =============================== admin diff --git a/src/server.py b/src/server.py index 12a2b9a..c72bd06 100644 --- a/src/server.py +++ b/src/server.py @@ -192,27 +192,69 @@ def api_collection_getSharedHandle(): @app.route('/api/todo/getFull', methods=['POST']) def api_todo_getFullHandle(): - pass + result = (False, None) + if (CheckParameter(('token', ))): + db = get_database() + result = db.todo_getFull(request.form['token']) + + return ConstructResponseBody(result) @app.route('/api/todo/getList', methods=['POST']) def api_todo_getListHandle(): - pass + result = (False, None) + if (CheckParameter(('token', ))): + db = get_database() + result = db.todo_getList(request.form['token']) + + return ConstructResponseBody(result) @app.route('/api/todo/getDetail', methods=['POST']) def api_todo_getDetailHandle(): - pass + result = (False, None) + if (CheckParameter(('token', 'uuid'))): + db = get_database() + result = db.todo_getDetail( + request.form['token'], + request.form['uuid'] + ) + + return ConstructResponseBody(result) @app.route('/api/todo/add', methods=['POST']) def api_todo_addHandle(): - pass + result = (False, None) + if (CheckParameter(('token', ))): + db = get_database() + result = db.todo_add(request.form['token']) + + return ConstructResponseBody(result) @app.route('/api/todo/update', methods=['POST']) def api_todo_updateHandle(): - pass + result = (False, None) + if (CheckParameter(('token', 'uuid', 'data', 'lastChange'))): + db = get_database() + result = db.todo_update( + request.form['token'], + request.form['uuid'], + request.form['data'], + request.form['lastChange'] + ) + + return ConstructResponseBody(result) @app.route('/api/todo/delete', methods=['POST']) def api_todo_deleteHandle(): - pass + result = (False, None) + if (CheckParameter(('token', 'uuid', 'lastChange'))): + db = get_database() + result = db.todo_delete( + request.form['token'], + request.form['uuid'], + request.form['lastChange'] + ) + + return ConstructResponseBody(result) # ================================ admin diff --git a/src/static/i18n/strings_en-US.properties b/src/static/i18n/strings_en-US.properties index 096e09a..e084b92 100644 --- a/src/static/i18n/strings_en-US.properties +++ b/src/static/i18n/strings_en-US.properties @@ -14,6 +14,7 @@ ccn-header-language=Language ccn-js-failToLogin=Fail to login. Please check your username or password. ccn-js-failToLogout=Fail to logout due to unknow reason. Consider refreshing page to solve problem. +ccn-js-failToOperate=An operation failed. It may caused by your input wrong data, or system error. Refreshing page may fix system problem. Before refreshing page, please backup all your unsaved data. ccn-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.

@@ -21,3 +22,19 @@ ccn-login-form-username=Username ccn-login-form-password=Password ccn-login-form-login=Login +ccn-todo-todoList=Todo list + +ccn-calendar-jump=Jump +ccn-calendar-today=Today +ccn-calendar-add=Add... +ccn-calendar-scheduleList=Schedule +ccn-calendar-tabcontrol-tabCalendar=Calendar +ccn-calendar-tabcontrol-tabShared=Shared +ccn-calendar-tabcontrol-tabSharing=Sharing +ccn-calendar-week-monday=Monday +ccn-calendar-week-tuesday=Tuesday +ccn-calendar-week-wednesday=Wednesday +ccn-calendar-week-thursday=Thursday +ccn-calendar-week-friday=Friday +ccn-calendar-week-saturday=Saturday +ccn-calendar-week-sunday=Sunday diff --git a/src/static/i18n/strings_zh-CN.properties b/src/static/i18n/strings_zh-CN.properties index fd3a6ee..25b2217 100644 --- a/src/static/i18n/strings_zh-CN.properties +++ b/src/static/i18n/strings_zh-CN.properties @@ -14,6 +14,7 @@ ccn-header-language=语言 ccn-js-failToLogin=登陆失败,请检查您的用户名和密码。 ccn-js-failToLogout=由于未知原因,登出失败,请考虑刷新页面解决问题。 +ccn-js-failToOperate=一个操作失败了,可能是您输入了错误的参数,又或者是系统错误。刷新页面可能会解决系统错误问题。请在刷新前备份好自己的数据。 ccn-home-desc=

coconut-leaf

一个轻量的自建日历系统

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


欢迎提出Pull request / issue / 翻译

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

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

@@ -21,4 +22,19 @@ ccn-login-form-username=用户名 ccn-login-form-password=密码 ccn-login-form-login=登录 +ccn-todo-todoList=待办列表 +ccn-calendar-jump=转到 +ccn-calendar-today=今天 +ccn-calendar-add=添加... +ccn-calendar-scheduleList=日程安排 +ccn-calendar-tabcontrol-tabCalendar=日历 +ccn-calendar-tabcontrol-tabShared=被共享的 +ccn-calendar-tabcontrol-tabSharing=共享给其他人 +ccn-calendar-week-monday=星期一 +ccn-calendar-week-tuesday=星期二 +ccn-calendar-week-wednesday=星期三 +ccn-calendar-week-thursday=星期四 +ccn-calendar-week-friday=星期五 +ccn-calendar-week-saturday=星期六 +ccn-calendar-week-sunday=星期日 diff --git a/src/static/js/api.js b/src/static/js/api.js index 1b73236..4af7885 100644 --- a/src/static/js/api.js +++ b/src/static/js/api.js @@ -49,6 +49,8 @@ function cnn_api_common_login(_username, password) { } */ +// ====================================================== common + function cnn_api_common_webLogin(_username, password) { var gotten_data = undefined; $.ajax({ @@ -63,7 +65,7 @@ function cnn_api_common_webLogin(_username, password) { gotten_data = data; } }); - if (IsResponseOK(gotten_data) && gotten_data['data'] != '') { + if (IsResponseOK(gotten_data)) { SetApiToken(gotten_data['data']); return true; } else return false; @@ -84,7 +86,7 @@ function cnn_api_common_logout() { } }); - if (IsResponseOK(gotten_data) && gotten_data['data']) { + if (IsResponseOK(gotten_data)) { SetApiToken(''); return true; } return false; @@ -109,9 +111,106 @@ function cnn_api_common_tokenValid() { } }); - if (IsResponseOK(gotten_data) && gotten_data['data']) return true; + if (IsResponseOK(gotten_data)) return true; else { SetApiToken(''); return false; } -} \ No newline at end of file +} + +// ====================================================== calendar + + + + + +// ====================================================== collection + + + +// ====================================================== todo + +function cnn_api_todo_getFull() { + // return data or undefined + var gotten_data = undefined; + $.ajax({ + url: '/api/todo/getFull', + type: "POST", + async: false, + data: { + token: GetApiToken() + }, + success: function (data) { + gotten_data = data; + } + }); + + if (IsResponseOK(gotten_data)) return gotten_data['data']; + else return undefined; +} + +function cnn_api_todo_add() { + // return data or undefined + var gotten_data = undefined; + $.ajax({ + url: '/api/todo/add', + type: "POST", + async: false, + data: { + token: GetApiToken() + }, + success: function (data) { + gotten_data = data; + } + }); + + if (IsResponseOK(gotten_data)) return gotten_data['data']; + else return undefined; +} + +function cnn_api_todo_update(_uuid, _data, _lastChange) { + // return data or undefined + var gotten_data = undefined; + $.ajax({ + url: '/api/todo/update', + type: "POST", + async: false, + data: { + token: GetApiToken(), + uuid: _uuid, + data: _data, + lastChange: _lastChange + }, + success: function (data) { + gotten_data = data; + } + }); + + if (IsResponseOK(gotten_data)) return gotten_data['data']; + else return undefined; +} + +function cnn_api_todo_delete(_uuid, _lastChange) { + // return true or false + var gotten_data = undefined; + $.ajax({ + url: '/api/todo/delete', + type: "POST", + async: false, + data: { + token: GetApiToken(), + uuid: _uuid, + lastChange: _lastChange + }, + success: function (data) { + gotten_data = data; + } + }); + + return IsResponseOK(gotten_data); +} + +// ====================================================== admin + + + diff --git a/src/static/js/i18n.js b/src/static/js/i18n.js index 211bbd9..85b8831 100644 --- a/src/static/js/i18n.js +++ b/src/static/js/i18n.js @@ -34,7 +34,7 @@ function ccn_i18n_ApplyLanguage() { language: ccn_i18n_currentLanguage, callback: function() { //set usual block - var cache = $(".ccn-i18n"); + var cache = $("[i18n-name]"); cache.each(function() { $(this).html($.i18n.prop($(this).attr('i18n-name'))); }); diff --git a/src/static/js/page/calendar.js b/src/static/js/page/calendar.js index 83af4a5..609a86d 100644 --- a/src/static/js/page/calendar.js +++ b/src/static/js/page/calendar.js @@ -8,6 +8,18 @@ $(document).ready(function() { // process calendar it self ccn_calendar_LoadCalendarBody(); + // bind tab control switcher and set current tab + $("#tabcontrol-tab-1-1").click(function(){ + ccn_tabcontrol_SwitchTab(1, 1); + }); + $("#tabcontrol-tab-1-2").click(function(){ + ccn_tabcontrol_SwitchTab(1, 2); + }); + $("#tabcontrol-tab-1-3").click(function(){ + ccn_tabcontrol_SwitchTab(1, 3); + }); + ccn_tabcontrol_SwitchTab(1, 1); + // apply i18n ccn_i18n_ApplyLanguage(); }); diff --git a/src/static/js/page/login.js b/src/static/js/page/login.js index 7289ef8..99e2921 100644 --- a/src/static/js/page/login.js +++ b/src/static/js/page/login.js @@ -6,13 +6,13 @@ $(document).ready(function() { cnn_headerNav_LoggedRefresh(); // bind login event - $("#ccn-login-form-login").click(StartLogin); + $("#ccn-login-form-login").click(ccn_login_startLogin); // apply i18n ccn_i18n_ApplyLanguage(); }); -function StartLogin() { +function ccn_login_startLogin() { // disable all ui first $("#ccn-login-form-login").attr("disabled",true); $("#ccn-login-form-username").attr("disabled",true); diff --git a/src/static/js/page/todo.js b/src/static/js/page/todo.js index e69de29..1bd64c5 100644 --- a/src/static/js/page/todo.js +++ b/src/static/js/page/todo.js @@ -0,0 +1,202 @@ +var ccn_todo_todoListCache = []; + +$(document).ready(function() { + // nav process + ccn_pages_currentPage = ccn_pages_enumPages.login; + cnn_headerNav_Insert(); + cnn_headerNav_BindEvents(); + cnn_headerNav_LoggedRefresh(); + + // refresh once + ccn_todo_Refresh(); + + // bind event + $("#ccn-todo-btnAdd").click(ccn_todo_Add); + $("#ccn-todo-btnRefresh").click(ccn_todo_Refresh); + + // apply i18n + ccn_i18n_ApplyLanguage(); +}); + +function ccn_todo_RefreshCacheList() { + // clean list cache first + ccn_todo_todoListCache = new Array(); + + var result = cnn_api_todo_getFull(); + if(typeof(result) != 'undefined') { + for(var index in result) { + ccn_todo_todoListCache[result[index][0]] = result[index]; + } + } + +} + +function ccn_todo_RenderCacheList() { + // clean list first + $("#ccn-todo-todoList").empty(); + + var renderdata = { + uuid: undefined, + data: undefined + }; + + var templates = undefined; + $.ajax({ + url: $("#jsrender-tmpl-todoItem").attr('src'), + type: "GET", + async: false, + success: function (data) { + templates = $.templates(data); + } + }); + + var listDOM = $("#ccn-todo-todoList"); + for(var index in ccn_todo_todoListCache) { + // update render data + var item = ccn_todo_todoListCache[index]; + renderdata.uuid = item[0]; + renderdata.data = item[2]; + + // render + listDOM.append(templates.render(renderdata)); + + // set mode + var uuid = renderdata.uuid; + ccn_todo_ChangeDisplayMode(uuid, false); + + // bind event + $("#ccn-todo-todoItem-btnEdit-" + uuid).click(ccn_todo_ItemEdit); + $("#ccn-todo-todoItem-btnDelete-" + uuid).click(ccn_todo_ItemDelete); + $("#ccn-todo-todoItem-btnUpdate-" + uuid).click(ccn_todo_ItemUpdate); + $("#ccn-todo-todoItem-btnCancelUpdate-" + uuid).click(ccn_todo_ItemCancelUpdate); + } +} + +function ccn_todo_ChangeDisplayMode(uuid, isEdit) { + if(isEdit) { + // 4 buttons + $("#ccn-todo-todoItem-btnEdit-" + uuid).hide(); + $("#ccn-todo-todoItem-btnDelete-" + uuid).hide(); + $("#ccn-todo-todoItem-btnUpdate-" + uuid).show(); + $("#ccn-todo-todoItem-btnCancelUpdate-" + uuid).show(); + + // 2 elements + $("#ccn-todo-todoItem-p-" + uuid).hide(); + $("#ccn-todo-todoItem-textarea-" + uuid).show(); + } else { + $("#ccn-todo-todoItem-btnEdit-" + uuid).show(); + $("#ccn-todo-todoItem-btnDelete-" + uuid).show(); + $("#ccn-todo-todoItem-btnUpdate-" + uuid).hide(); + $("#ccn-todo-todoItem-btnCancelUpdate-" + uuid).hide(); + + $("#ccn-todo-todoItem-p-" + uuid).show(); + $("#ccn-todo-todoItem-textarea-" + uuid).hide(); + } + +} + +function ccn_todo_Refresh() { + // refresh and render once + ccn_todo_RefreshCacheList(); + ccn_todo_RenderCacheList(); +} + +function ccn_todo_Add() { + var result = cnn_api_todo_add(); + if (typeof(result) == 'undefined') { + // fail + alert($.i18n.prop("ccn-js-failToOperate")); + } else { + // add into cache + ccn_todo_todoListCache[result[0]] = result; + + // render + var templates = undefined; + $.ajax({ + url: $("#jsrender-tmpl-todoItem").attr('src'), + type: "GET", + async: false, + success: function (data) { + templates = $.templates(data); + } + }); + + // render + var listDOM = $("#ccn-todo-todoList"); + listDOM.append(templates.render({ + uuid: result[0], + data: result[2] + })); + + // set mode + var uuid = result[0]; + ccn_todo_ChangeDisplayMode(uuid, false); + + // bind event + $("#ccn-todo-todoItem-btnEdit-" + uuid).click(ccn_todo_ItemEdit); + $("#ccn-todo-todoItem-btnDelete-" + uuid).click(ccn_todo_ItemDelete); + $("#ccn-todo-todoItem-btnUpdate-" + uuid).click(ccn_todo_ItemUpdate); + $("#ccn-todo-todoItem-btnCancelUpdate-" + uuid).click(ccn_todo_ItemCancelUpdate); + } +} + +function ccn_todo_ItemEdit() { + var uuid = $(this).attr("uuid"); + + // copy current data to textarea + $("#ccn-todo-todoItem-textarea-" + uuid).val( + $("#ccn-todo-todoItem-p-" + uuid).text() + ); + + // switch to edit mode + ccn_todo_ChangeDisplayMode(uuid, true); +} + +function ccn_todo_ItemDelete() { + var uuid = $(this).attr("uuid"); + + var result = cnn_api_todo_delete( + uuid, + ccn_todo_todoListCache[uuid][3] + ); + + if(typeof(result) == 'undefined') { + // fail + alert($.i18n.prop("ccn-js-failToOperate")); + } else { + // remove body + $("#ccn-todo-todoItem-" + uuid).remove(); + } +} + +function ccn_todo_ItemUpdate() { + var uuid = $(this).attr("uuid"); + + var newData = $("#ccn-todo-todoItem-textarea-" + uuid).val(); + var result = cnn_api_todo_update( + uuid, + newData, + ccn_todo_todoListCache[uuid][3] + ); + + if (typeof(result) == 'undefined') { + // fail + alert($.i18n.prop("ccn-js-failToOperate")); + } else { + // safely update data & lastChanged and control + ccn_todo_todoListCache[uuid][2] = newData; + ccn_todo_todoListCache[uuid][3] = result; + $("#ccn-todo-todoItem-p-" + uuid).text(newData); + + // switch to normal mode + ccn_todo_ChangeDisplayMode(uuid, false); + } +} + +function ccn_todo_ItemCancelUpdate() { + var uuid = $(this).attr("uuid"); + // clean data + $("#ccn-todo-todoItem-textarea-" + uuid).val(""); + // switch to normal mode + ccn_todo_ChangeDisplayMode(uuid, false); +} diff --git a/src/static/js/tabcontrol.js b/src/static/js/tabcontrol.js new file mode 100644 index 0000000..f7b5228 --- /dev/null +++ b/src/static/js/tabcontrol.js @@ -0,0 +1,10 @@ +// all args are based on 1 +function ccn_tabcontrol_SwitchTab(tabcontrolGroup, targetTabIndex) { + // close all panel and tab + $(".tabcontrol-tab-" + tabcontrolGroup).removeClass("is-active"); + $(".tabcontrol-panel-" + tabcontrolGroup).hide(); + + // show specific + $("#tabcontrol-tab-" + tabcontrolGroup + "-" + targetTabIndex).addClass("is-active"); + $("#tabcontrol-panel-" + tabcontrolGroup + "-" + targetTabIndex).show(); +} \ No newline at end of file diff --git a/src/static/js/utils.js b/src/static/js/utils.js index 7f82efc..1d7f233 100644 --- a/src/static/js/utils.js +++ b/src/static/js/utils.js @@ -1,3 +1,4 @@ +/* function ComputPasswordWithSalt(password, salt) { return ComputeSHA256(ComputeSHA256(password) + salt.toString()); } @@ -13,6 +14,7 @@ function ComputeSHA256(strl) { var hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join(''); return hashHex.toLowerCase(); } +*/ function IsResponseOK(data) { if (typeof(data) == 'undefined') { diff --git a/src/static/tmpl/headerNav.tmpl b/src/static/tmpl/headerNav.tmpl index 0a90789..a3ff069 100644 --- a/src/static/tmpl/headerNav.tmpl +++ b/src/static/tmpl/headerNav.tmpl @@ -14,22 +14,22 @@