1
0

first commit

This commit is contained in:
2021-01-16 22:15:10 +08:00
commit f539b7e11f
30 changed files with 2381 additions and 0 deletions

54
src/coconut-leaf.py Normal file
View File

@@ -0,0 +1,54 @@
import os
import sys
import getopt
import server
import config
import utils
import database
def GetUsernamePassword():
print('What is the first username of this calendar system?')
cache = input()
while(not utils.IsValidUsername(cache)):
print('Sorry, invalid data. Please try again.')
cache = input()
username = cache
print("Input this user password:")
cache = input()
while(not utils.IsValidPassword(cache)):
print('Sorry, invalid data. Please try again.')
cache = input()
password = cache
return (username, password)
print('Coconut-leaf')
print('A self-host, multi-account calendar system.')
print('Project: https://github.com/yyc12345/coconut-leaf')
print('===================')
# process args
# preset init value
need_init = False
try:
opts, args = getopt.getopt(sys.argv[1:], "hi")
except getopt.GetoptError:
print('Wrong arguments!')
print('python coconut-leaf.py [-i] [-h]')
sys.exit(1)
for opt, arg in opts:
if opt == '-h':
print('python coconut-leaf.py [-i]')
sys.exit(0)
elif opt == '-i':
need_init = True
if need_init:
gotten_data = GetUsernamePassword()
calendar = database.CalendarDatabase()
calendar.init(*gotten_data)
calendar.close()
print('Staring server...')
server.run()

14
src/config.cfg.template Normal file
View File

@@ -0,0 +1,14 @@
{
"database-type": "sqlite",
"database-config": {
"user": "",
"password": "",
"db": "",
"url": "",
"port": 3306
},
"web": {
"port": 8888
},
"debug": true
}

7
src/config.py Normal file
View File

@@ -0,0 +1,7 @@
import json
CustomConfig = None
# read cfg
with open('config.cfg', 'r', encoding='utf-8') as f:
CustomConfig = json.load(f)

51
src/database.py Normal file
View File

@@ -0,0 +1,51 @@
import config
import sqlite3
import json
class CalendarDatabase(object):
def __init__(self):
self.db = None
def open(self):
if (self.is_database_valid()):
raise Exception('Databade is opened')
if config.CustomConfig['database-type'] == 'sqlite':
self.db = sqlite3.connect(config.CustomConfig['database-config']['url'])
elif config.CustomConfig['database-type'] == 'mysql':
raise Exception('Not implemented database')
else:
raise Exception('Unknow database type')
def init(self, username, password):
if (self.is_database_valid()):
raise Exception('Databade is opened')
self.open()
with open('sql/sqlite.sql', 'r', encoding='utf-8') as fsql:
cursor = self.db.cursor()
cursor.executescript(fsql.read())
cursor.close()
self.db.commit()
#todo: finish init
def close(self):
self.check_database()
self.db.close()
self.db = None
def check_database(self):
if (not self.is_database_valid()):
raise Exception('Databade is None')
def is_database_valid(self):
return not (self.db == None)
# operation function
def login_step1(self, username):
self.check_database()
def login_step2(self, username, password):
self.check_database()

215
src/server.py Normal file
View File

@@ -0,0 +1,215 @@
from flask import Flask
from flask import g
from flask import render_template
from flask import url_for
from flask import request
from flask import abort
from flask import redirect
from functools import reduce
import json
import os
import config
import database
app = Flask(__name__)
render_static_resources = None
# =============================================database
def get_database():
db = getattr(g, '_database', None)
if db is None:
db = database.CalendarDatabase()
db.open()
return db
@app.teardown_appcontext
def close_database(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
# ============================================= static page route
@app.route('/', methods=['GET'])
def nospecHandle():
return redirect(url_for('web_homeHandle'))
@app.route('/web/home', methods=['GET'])
def web_homeHandle():
UpdateStaticResources()
return render_template("home.html", **render_static_resources)
@app.route('/web/calendar', methods=['GET'])
def web_calendarHandle():
UpdateStaticResources()
return render_template("calendar.html", **render_static_resources)
@app.route('/web/todo', methods=['GET'])
def web_todoHandle():
UpdateStaticResources()
return render_template("todo.html", **render_static_resources)
@app.route('/web/admin', methods=['GET'])
def web_adminHandle():
UpdateStaticResources()
return render_template("admin.html", **render_static_resources)
# ============================================= query page route
# ================================ common
@app.route('/api/common/salt', methods=['POST'])
def api_common_saltHandle():
pass
@app.route('/api/common/login', methods=['POST'])
def api_common_loginHandle():
pass
@app.route('/api/common/logout', methods=['POST'])
def api_common_logoutHandle():
pass
@app.route('/api/common/tokenValid', methods=['POST'])
def api_common_tokenValidHandle():
pass
@app.route('/api/common/isAdmin', methods=['POST'])
def api_common_isAdminHandle():
pass
@app.route('/api/common/changePassword', methods=['POST'])
def api_common_changePasswordHandle():
pass
# ================================ calendar
@app.route('/api/calendar/getFull', methods=['POST'])
def api_calendar_getFullHandle():
pass
@app.route('/api/calendar/getList', methods=['POST'])
def api_calendar_getListHandle():
pass
@app.route('/api/calendar/getDetail', methods=['POST'])
def api_calendar_getDetailHandle():
pass
@app.route('/api/calendar/update', methods=['POST'])
def api_calendar_updateHandle():
pass
@app.route('/api/calendar/add', methods=['POST'])
def api_calendar_addHandle():
pass
@app.route('/api/calendar/delete', methods=['POST'])
def api_calendar_deleteHandle():
pass
# ================================ collection
@app.route('/api/collection/getOwn', methods=['POST'])
def api_collection_getOwnHandle():
pass
@app.route('/api/collection/addOwn', methods=['POST'])
def api_collection_addOwnHandle():
pass
@app.route('/api/collection/updateOwn', methods=['POST'])
def api_collection_updateOwnHandle():
pass
@app.route('/api/collection/deleteOwn', methods=['POST'])
def api_collection_deleteOwnHandle():
pass
@app.route('/api/collection/getSharing', methods=['POST'])
def api_collection_getSharingHandle():
pass
@app.route('/api/collection/deleteSharing', methods=['POST'])
def api_collection_deleteSharingHandle():
pass
@app.route('/api/collection/addSharing', methods=['POST'])
def api_collection_addSharingHandle():
pass
@app.route('/api/collection/getShared', methods=['POST'])
def api_collection_getSharedHandle():
pass
# ================================ todo
@app.route('/api/todo/getFull', methods=['POST'])
def api_todo_getFullHandle():
pass
@app.route('/api/todo/getList', methods=['POST'])
def api_todo_getListHandle():
pass
@app.route('/api/todo/getDetail', methods=['POST'])
def api_todo_getDetailHandle():
pass
@app.route('/api/todo/add', methods=['POST'])
def api_todo_addHandle():
pass
@app.route('/api/todo/update', methods=['POST'])
def api_todo_updateHandle():
pass
@app.route('/api/todo/delete', methods=['POST'])
def api_todo_deleteHandle():
pass
# ================================ admin
@app.route('/api/admin/get', methods=['POST'])
def api_admin_getHandle():
pass
@app.route('/api/admin/add', methods=['POST'])
def api_admin_addHandle():
pass
@app.route('/api/admin/update', methods=['POST'])
def api_admin_updateHandle():
pass
@app.route('/api/admin/delete', methods=['POST'])
def api_admin_deleteHandle():
pass
# =============================================main run
def UpdateStaticResources():
global render_static_resources
if render_static_resources is not None:
return
render_static_resources = {
'url_js_localStorageAssist': url_for('static', filename='js/localStorageAssist.js'),
'url_js_i18n': url_for('static', filename='js/i18n.js'),
'url_js_api': url_for('static', filename='js/api.js'),
'url_js_headerNav': url_for('static', filename='js/headerNav.js'),
'url_tmpl_headerNac': url_for('static', filename='tmpl/headerNav.tmpl'),
'url_js_pageHome': url_for('static', filename='js/page/home.js')
}
def run():
app.run(port=config.CustomConfig['web']['port'])

0
src/sql/mysql.sql Normal file
View File

61
src/sql/sqlite.sql Normal file
View File

@@ -0,0 +1,61 @@
PRAGMA encoding = "UTF-8";
PRAGMA foreign_keys = ON;
CREATE TABLE user(
[ccn_name] TEXT NOT NULL,
[ccn_password] TEXT NOT NULL,
[ccn_isAdmin] TINYINT NOT NULL CHECK(ccn_isAdmin = 1 OR ccn_isAdmin = 0),
[ccn_salt] INTEGER NOT NULL,
[ccn_token] TEXT UNIQUE NOT NULL,
[ccn_tokenExpireOn] BIGINT NOT NULL,
PRIMARY KEY (ccn_name)
);
CREATE TABLE collection(
[ccn_uuid] TEXT NOT NULL,
[ccn_name] TEXT NOT NULL,
[ccn_user] TEXT NOT NULL,
PRIMARY KEY (ccn_uuid),
FOREIGN KEY (ccn_user) REFERENCES user(ccn_name) ON DELETE CASCADE
);
CREATE TABLE share(
[ccn_uuid] TEXT NOT NULL,
[ccn_target] TEXT NOT NULL,
FOREIGN KEY (ccn_uuid) REFERENCES collection(ccn_uuid) ON DELETE CASCADE
FOREIGN KEY (ccn_target) REFERENCES user(ccn_name) ON DELETE CASCADE
);
CREATE TABLE calendar(
[ccn_uuid] TEXT NOT NULL,
[ccn_belongTo] TEXT NOT NULL,
[ccn_title] TEXT NOT NULL,
[ccn_description] TEXT NOT NULL,
[ccn_lastChange] BIGINT NOT NULL,
[ccn_eventDateTimeType] TINYINT NOT NULL,
[ccn_eventDateTimeStart] BIGINT NOT NULL,
[ccn_eventDateTimeEnd] BIGINT NOT NULL,
[ccn_loopRules] TEXT NOT NULL,
[ccn_loopDateTimeStart] BIGINT NOT NULL,
[ccn_loopDateTimeEnd] BIGINT NOT NULL,
PRIMARY KEY (ccn_uuid),
FOREIGN KEY (ccn_belongTo) REFERENCES collection(ccn_uuid) ON DELETE CASCADE
);
CREATE TABLE todo(
[ccn_uuid] TEXT NOT NULL,
[ccn_belongTo] TEXT NOT NULL,
[ccn_data] TEXT NOT NULL,
[ccn_lastChange] BIGINT NOT NULL,
PRIMARY KEY (ccn_uuid),
FOREIGN KEY (ccn_belongTo) REFERENCES user(ccn_name) ON DELETE CASCADE
);

37
src/static/css/admin.css Normal file
View File

@@ -0,0 +1,37 @@
div.user-item {
display: flex;
flex-flow: row;
align-items: flex-start;
padding: 1.25rem;
margin-bottom: 1.25rem;
}
div.user-item-words {
display: flex;
flex-flow: column;
align-items: flex-start;
flex-grow: 1;
flex-basis: 0;
word-break: break-all;
}
div.user-item-icon {
margin-left: 0.75rem;
}
div.control-list {
display: flex;
flex-flow: row;
flex-wrap: wrap;
}
div.control-list > * {
margin-right: 0.75rem;
margin-bottom: 0.75rem;
}

124
src/static/css/calendar.css Normal file
View File

@@ -0,0 +1,124 @@
#calendar-body div:nth-child(n+2) div {
border-top: 0 solid black;
border-left: 0 solid black;
border-right: 1px solid black;
border-bottom: 1px solid black;
padding: 0.75em;
display: flex;
flex-flow: column;
align-items: flex-start;
overflow: hidden;
}
#calendar-body div:nth-child(n+2) div:nth-child(1) {
border-left: 1px solid black;
}
#calendar-body div:nth-child(2) div {
border-top: 1px solid black;
}
#calendar-body div div {
flex-grow: 1;
flex-basis: 0;
flex-shrink: 0;
overflow: hidden;
}
#calendar-body div {
display: flex;
flex-flow: row;
}
div.schedule-day {
display: flex;
flex-flow: column;
}
div.schedule-day-words {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
div.schedule-event-list {
display: flex;
flex-flow: column;
}
div.schedule-event {
display: flex;
flex-flow: row;
align-items: flex-start;
padding: 1.25rem;
margin-bottom: 1.25rem;
}
div.schedule-event-words {
display: flex;
flex-flow: column;
align-items: flex-start;
flex-grow: 1;
flex-basis: 0;
word-break: break-all;
}
div.schedule-event-icon {
margin-left: 0.75rem;
}
#schedule-list div.schedule-day:nth-child(n+2) {
border-top: 1px solid rgba(219,219,219,.5);
}
div.collection-item {
display: flex;
flex-flow: row;
align-items: flex-start;
padding: 1.25rem;
margin-bottom: 1.25rem;
}
div.collection-item-words {
flex-grow: 1;
flex-basis: 0;
word-break: break-all;
}
div.collection-item-icon {
margin-left: 0.75rem;
}
div.control-list {
display: flex;
flex-flow: row;
flex-wrap: wrap;
}
div.control-list > * {
margin-right: 0.75rem;
margin-bottom: 0.75rem;
}

32
src/static/css/todo.css Normal file
View File

@@ -0,0 +1,32 @@
div.todo-item {
display: flex;
flex-flow: row;
align-items: flex-start;
padding: 1.25rem;
margin-bottom: 1.25rem;
}
div.todo-item-words {
flex-grow: 1;
flex-basis: 0;
word-break: break-all;
}
div.todo-item-icon {
margin-left: 0.75rem;
}
div.control-list {
display: flex;
flex-flow: row;
flex-wrap: wrap;
}
div.control-list > * {
margin-right: 0.75rem;
margin-bottom: 0.75rem;
}

3
src/static/js/api.js Normal file
View File

@@ -0,0 +1,3 @@
function cnn_api_tokenValid() {
return true;
}

View File

@@ -0,0 +1,11 @@
function cnn_headerNav_Insert() {
$.ajax({
url: $("#jsrender-tmpl-headerNav").attr('src'),
type: "GET",
success: function (data) {
var tmpl = $.templates(data);
$('body').prepend(tmpl.render());
}
});
}

55
src/static/js/i18n.js Normal file
View File

@@ -0,0 +1,55 @@
var ccn_i18n_i18nSupported = ['en-US', 'zh-CN'];
var ccn_i18n_currentLanguage = 'en-US';
// judge current language
ccn_i18n_currentLanguage = ccn_localstorageAssist_Get('ccn-i18n', 'en-US');
if (ccn_i18n_i18nSupported.indexOf(ccn_i18n_currentLanguage) == -1){
ccn_localstorageAssist_Set('ccn-i18n', 'en-US');
ccn_i18n_currentLanguage = 'en-US';
}
function ccn_i18n_ChangeLanguage(newLang) {
if (ccn_i18n_i18nSupported.indexOf(newLang) == -1) return false;
ccn_i18n_currentLanguage = newLang;
ccn_localstorageAssist_Set('ccn-i18n', newLang);
return true;
}
function ccn_i18n_ApplyLanguage() {
$.i18n.properties({
name: 'strings_' + ccn_i18n_currentLanguage,
path: 'i18n/',
mode: 'map',
language: ccn_i18n_currentLanguage,
callback: function() {
//set usual block
var cache = $(".ccn-i18n");
cache.each(function() {
$(this).html($.i18n.prop($(this).attr('name')));
});
//set unusual block
//set title
switch(ccn_pages_currentPage) {
case ccn_pages_enumPages.home:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-home'))
break;
case ccn_pages_enumPages.user:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-user'))
break;
case ccn_pages_enumPages.userinfo:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-userinfo'))
break;
case ccn_pages_enumPages.map:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-map'))
break;
case ccn_pages_enumPages.mapinfo:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-mapinfo'))
break;
case ccn_pages_enumPages.about:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-about'))
break;
}
}
})
}

View File

@@ -0,0 +1,11 @@
function ccn_localstorageAssist_Get(index, defaultValue) {
var cache = localStorage.getItem(index);
if (cache == null) {
ccn_localstorageAssist_Set(index, defaultValue);
return defaultValue;
} else return cache;
}
function ccn_localstorageAssist_Set(index, value) {
localStorage.setItem(index, value);
}

View File

View File

View File

@@ -0,0 +1,4 @@
$(document).ready(function() {
// insert nav first
cnn_headerNav_Insert();
});

View File

View File

View File

@@ -0,0 +1,41 @@
<nav class="navbar has-shadow is-spaced bd-navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="home">
<img src="icon.png"><b style="margin:0 0 0 14px;">coconut-leaf</b>
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="home">Home</a>
<a class="navbar-item" href="calendar">Calendar</a>
<a class="navbar-item" href="todo">Todo</a>
<a class="navbar-item" href="admin">Admin</a>
</div>
<div class="navbar-end">
<p class="control">
<a class="button is-primary">Login</a>
</p>
<p class="control">
<a class="button is-primary">Logout</a>
</p>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Language</a>
<div class="navbar-dropdown">
<a class="navbar-item">English</a>
<a class="navbar-item">简体中文</a>
</div>
</div>
</div>
</div>
</nav>

123
src/templates/admin.html Normal file
View File

@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>coconut-leaf Admin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.12.1/js/all.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.js"></script>
<link rel="stylesheet" href="admin.css">
</head>
<body>
<nav class="navbar has-shadow is-spaced bd-navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="home">
<img src="icon.png"><b style="margin:0 0 0 14px;">coconut-leaf</b>
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="home">Calendar</a>
<a class="navbar-item" href="home">Todo</a>
<a class="navbar-item" href="about">About</a>
</div>
<div class="navbar-end">
<p class="control">
<a class="button is-primary" href="javascript:void(0);">Logout</a>
</p>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Language</a>
<div class="navbar-dropdown">
<a href="javascript:void(0);" class="navbar-item">English</a>
<a href="javascript:void(0);" class="navbar-item">简体中文</a>
</div>
</div>
</div>
</div>
</nav>
<div class="container" style="display: flex; flex-flow: column; margin-top: 1.25rem;">
<h1 class="title">My account</h1>
<div class="field">
<label class="label">Change password</label>
<div class="field has-addons">
<div class="control">
<input class="input" type="password" placeholder="New password">
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-key"></i></span>
</a>
</div>
</div>
</div>
<h1 class="title">User list</h1>
<div class="control-list">
<div class="field has-addons">
<div class="control">
<input class="input" type="text">
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-plus"></i></span>
</a>
</div>
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-sync"></i></span>
</a>
</div>
</div>
<div style="display: flex; flex-flow: column; margin-top: 1.25rem;">
<div class="user-item card">
<div class="user-item-words">
<p><b>this is a name</b></p>
</div>
<div class="user-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-pen"></i></span></a>
</div>
<div class="user-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-trash"></i></span></a>
</div>
</div>
<div class="user-item card">
<div class="user-item-words">
<p><b>this is a name</b></p>
<div class="control">
<input class="input" type="password" placeholder="New password"></input>
</div>
</div>
<div class="user-item-icon control">
<a class="button" href="javascript:void(0);"><span class="icon is-small"><i class="fas fa-check"></i></span></a>
</div>
<div class="user-item-icon control">
<a class="button" href="javascript:void(0);"><span class="icon is-small"><i class="fas fa-times"></i></span></a>
</div>
</div>
</div>
</div>
</body>
</html>

597
src/templates/calendar.html Normal file
View File

@@ -0,0 +1,597 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>coconut-leaf Calendar</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.12.1/js/all.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.js"></script>
<link rel="stylesheet" href="calendar.css">
</head>
<body>
<nav class="navbar has-shadow is-spaced bd-navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="home">
<img src="icon.png"><b style="margin:0 0 0 14px;">coconut-leaf</b>
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="home">Calendar</a>
<a class="navbar-item" href="home">Todo</a>
<a class="navbar-item" href="about">About</a>
</div>
<div class="navbar-end">
<p class="control">
<a class="button is-primary" href="javascript:void(0);">Logout</a>
</p>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Language</a>
<div class="navbar-dropdown">
<a href="javascript:void(0);" class="navbar-item">English</a>
<a href="javascript:void(0);" class="navbar-item">简体中文</a>
</div>
</div>
</div>
</div>
</nav>
<div class="container" style="margin-top: 20px;">
<div class="tabs">
<ul>
<li class="is-active"><a>Calendar</a></li>
<li><a>Shared</a></li>
<li><a>Sharing</a></li>
</ul>
</div>
</div>
<div id="tabcontrol-1-1" class="container" style="margin-top: 20px; display: none;">
<nav class="level">
<div class="level-item">
<div class="field has-addons">
<div class="control">
<input class="input" type="date">
</div>
<div class="control">
<button class="button is-info">Jump</button>
</div>
</div>
</div>
<div class="level-item control">
<a class="button is-info">Today</a>
</div>
<div class="level-item control">
<a class="button is-primary">Add...</a>
</div>
</nav>
<div id="calendar-body" class="card" style="padding: 1.25rem; display: flex; flex-flow: column;">
<div style="margin: 0 0 0.75em 0;">
<div><b>Monday</b></div>
<div><b>Tuesday</b></div>
<div><b>Wednesday</b></div>
<div><b>Thursday</b></div>
<div><b>Friday</b></div>
<div><b style="color: red;">Saturday</b></div>
<div><b style="color: red;">Sunday</b></div>
</div>
<div>
<div>
<p><b>1</b></p>
<p><small>春分</small></p>
<p><span class="icon is-small"><i class="fas fa-tasks"></i></span>114514</p>
</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</div>
<div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</div>
<div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</div>
<div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</div>
<div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</div>
<div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</div>
</div>
<div class="container" style="padding: 1.25rem; display: flex; flex-flow: column; margin-top: 1.25rem;">
<h1 class="title">Schedule</h1>
<div id="schedule-list">
<div class="schedule-day container">
<div class="schedule-day-words">
<b>13</b>
<b>Friday</b>
</div>
<div class="schedule-event-list">
<div class="schedule-event card">
<div class="schedule-event-words">
<p class="level-item"><b>This is
titleewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww</b>
</p>
<p class="level-item">this is
subtitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
</p>
</div>
<div class="schedule-event-icon">
<span class="icon is-small"><i class="fas fa-lock"></i></span>
</div>
</div>
<div class="schedule-event card">
<div class="schedule-event-words">
<p class="level-item"><b>This is
titleewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww</b>
</p>
<p class="level-item">this is
subtitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
</p>
</div>
<div class="schedule-event-icon">
<span class="icon is-small"><i class="fas fa-lock"></i></span>
</div>
</div>
</div>
</div>
<div class="schedule-day container">
<div class="schedule-day-words">
<b>13</b>
<b>Friday</b>
</div>
<div class="schedule-event-list">
</div>
</div>
<div class="schedule-day container">
<div class="schedule-day-words">
<b>13</b>
<b>Friday</b>
</div>
<div class="schedule-event-list">
<div class="schedule-event card">
<div class="schedule-event-words">
<p class="level-item"><b>This is
titleewwwwwwwwwwwwwwwwww</b>
</p>
<p class="level-item">this is
subtitleeeeeeeeeewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeee
</p>
</div>
<div class="schedule-event-icon">
<span class="icon is-small"><i class="fas fa-lock"></i></span>
</div>
</div>
<div class="schedule-event card">
<div class="schedule-event-words">
<p class="level-item"><b>This is
titleeww</b>
</p>
<p class="level-item">this is
subtitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
</p>
</div>
<div class="schedule-event-icon">
<span class="icon is-small"><i class="fas fa-lock"></i></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="tabcontrol-1-2" class="container" style="margin-top: 20px; display: none;">
<div class="container" style="display: flex; flex-flow: column;">
<h1 class="title">Shared</h1>
<div class="field">
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-sync"></i></span>
</a>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div class="collection-item card">
<div class="collection-item-words">
<b>this is a name</b>
<p><span>Shared by: </span><span>yyc12345</span></p>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-eye"></i></span></a>
</div>
</div>
<div class="collection-item card">
<div class="collection-item-words">
<b>this is a
namewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww</b>
<p><span>Shared by: </span><span>Diablo</span></p>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-eye-slash"></i></span></a>
</div>
</div>
</div>
</div>
</div>
<div id="tabcontrol-1-3" class="container" style="margin-top: 20px; display: none;">
<div class="container" style="display: flex; flex-flow: column;">
<h1 class="title">Owned</h1>
<div class="control-list">
<div class="field has-addons">
<div class="control">
<input class="input" type="text">
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-plus"></i></span>
</a>
</div>
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-sync"></i></span>
</a>
</div>
</div>
<div style="display: flex; flex-flow: column; margin-top: 1.25rem;">
<div class="collection-item card">
<div class="collection-item-words">
<p>this is a name</p>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-pen"></i></span></a>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-trash"></i></span></a>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-eye"></i></span></a>
</div>
</div>
<div class="collection-item card">
<div class="collection-item-words">
<p>this is a
namewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
</p>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-pen"></i></span></a>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-trash"></i></a>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-eye-slash"></i></span></a>
</div>
</div>
</div>
</div>
<div class="container" style="display: flex; flex-flow: column;">
<h1 class="title">Sharing target</h1>
<label class="label"><span>Editing: </span><span>xxx</span></label>
<div class="control-list">
<div class="field has-addons">
<div class="control">
<input class="input" type="text">
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-plus"></i></span>
</a>
</div>
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-sync"></i></span>
</a>
</div>
</div>
<div style="display: flex; flex-flow: column; margin-top: 1.25rem;">
<div class="collection-item card">
<div class="collection-item-words">
<p>yyc12345</p>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-trash"></i></span></a>
</div>
</div>
<div class="collection-item card">
<div class="collection-item-words">
<p>boluo</p>
</div>
<div class="collection-item-icon control">
<a class="button"><span class="icon is-small"><i class="fas fa-trash"></i></span></a>
</div>
</div>
</div>
</div>
</div>
<!-- add is-active in class to show this-->
<div class="modal" style="float: left; position: fixed; top: 0; bottom: 0; left: 0; right: 0;">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Modal title</p>
<button class="delete" aria-label="close"></button>
</header>
<div class="modal-card-body" style="word-break: break-all;">
<div class="field">
<label class="label">Title</label>
<div class="control">
<input class="input" type="text">
</div>
</div>
<div class="field">
<label class="label">Description</label>
<div class="control">
<textarea class="textarea"></textarea>
</div>
</div>
<div class="field">
<label class="label">Collection</label>
<div class="control">
<div class="select">
<select>
<option>Select dropdown</option>
<option>With options</option>
</select>
</div>
</div>
</div>
<label class="label">Start date</label>
<div class="control-list">
<div class="control">
<input class="input" type="date">
</div>
<div class="control">
<input class="input" type="time">
</div>
</div>
<label class="label">End date</label>
<div class="control-list">
<div class="control">
<button class="button is-link">Spot</button>
</div>
<div class="control">
<button class="button is-link">Full day</button>
</div>
</div>
<div class="control-list">
<div class="control">
<input class="input" type="date">
</div>
<div class="control">
<input class="input" type="time">
</div>
</div>
<label class="label">Loop</label>
<label class="label">Loop method</label>
<div class="control-list">
<label class="radio">
<input type="radio" name="loop-method">Never
</label>
<label class="radio">
<input type="radio" name="loop-method">Day
</label>
<label class="radio">
<input type="radio" name="loop-method">Week
</label>
<label class="radio">
<input type="radio" name="loop-method">Month
</label>
<label class="radio">
<input type="radio" name="loop-method">Year
</label>
</div>
<div>
<div class="field">
<label class="label">Day loop span</label>
<div class="control">
<input class="input" type="text">
</div>
</div>
</div>
<div>
<div class="field">
<label class="label">Week loop span</label>
<div class="control">
<input class="input" type="text">
</div>
</div>
<div class="field">
<label class="label">Week option</label>
<div class="control-list">
<label class="checkbox">
<input type="checkbox">Monday
</label>
<label class="checkbox">
<input type="checkbox">Tuesday
</label>
<label class="checkbox">
<input type="checkbox">Wednesdayday
</label>
<label class="checkbox">
<input type="checkbox">Thursday
</label>
<label class="checkbox">
<input type="checkbox">Firday
</label>
<label class="checkbox">
<input type="checkbox">Saturday
</label>
<label class="checkbox">
<input type="checkbox">Sunday
</label>
</div>
</div>
</div>
<div>
<div class="field">
<label class="label">Month loop span</label>
<div class="control">
<input class="input" type="text">
</div>
</div>
<div class="field">
<label class="label">Month option</label>
<div class="control-list">
<label class="radio">
<input type="radio" name="month-loop-method">每月第X天
</label>
<label class="radio">
<input type="radio" name="month-loop-method">每月倒数第X天
</label>
<label class="radio">
<input type="radio" name="month-loop-method">每月第X个星期第Y天
</label>
<label class="radio">
<input type="radio" name="month-loop-method">每月倒数第X个星期第Y天
</label>
</div>
</div>
</div>
<div>
<div class="field">
<label class="label">Year loop span</label>
<div class="control">
<input class="input" type="text">
</div>
</div>
</div>
<div class="field">
<label class="label">Loop end with</label>
<div class="control-list">
<label class="radio">
<input type="radio" name="loop-end">Forever
</label>
<label class="radio">
<input type="radio" name="loop-end">A date
</label>
<label class="radio">
<input type="radio" name="loop-end">Times
</label>
</div>
</div>
<div class="field">
<div class="control">
<input class="input" type="date">
</div>
</div>
<div class="field">
<div class="control">
<input class="input" type="text">
</div>
</div>
</div>
<footer class="modal-card-foot">
<button class="button is-success">Submit</button>
<button class="button">Cancel</button>
</footer>
</div>
</div>
</body>
</html>

38
src/templates/home.html Normal file
View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>coconut-leaf Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<script src="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.12.1/js/all.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery-i18n-properties@1.2.7/jquery.i18n.properties.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jsrender@1.0.10/jsrender.min.js"></script>
<script type="text/x-jsrender" id="jsrender-tmpl-headerNav" src="{{url_tmpl_headerNac}}"></script>
<script type="text/javascript" src="{{url_js_localStorageAssist}}"></script>
<script type="text/javascript" src="{{url_js_i18n}}"></script>
<script type="text/javascript" src="{{url_js_api}}"></script>
<script type="text/javascript" src="{{url_js_headerNav}}"></script>
<script type="text/javascript" src="{{url_js_pageHome}}"></script>
</head>
<body>
<div class="container" style="margin-top: 1.25rem;">
<article>
<h1 class="title">coconut-leaf</h1>
<p>A light, self-host calendar system.</p>
<p>Originally, this app is served for yyc12345's personal use.</p>
<br />
<p>Pull request / issue / translation are welcomed.</p>
<p>Submit them in our <a href="https://github.com/yyc12345/coconut-leaf">GitHub project</a>.</p>
</article>
</div>
</body>
</html>

79
src/templates/login.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html style="height: 100%; overflow: hidden;">
<head>
<meta charset="utf-8">
<title>coconut-leaf Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.12.1/js/all.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.js"></script>
</head>
<body style="height: 100%; display: flex; flex-flow: column;">
<nav class="navbar has-shadow is-spaced bd-navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="home">
<img src="icon.png"><b style="margin:0 0 0 14px;">coconut-leaf</b>
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="home">Home</a>
<a class="navbar-item" href="about">About</a>
</div>
<div class="navbar-end">
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Language</a>
<div class="navbar-dropdown">
<a class="navbar-item">English</a>
<a class="navbar-item">简体中文</a>
</div>
</div>
</div>
</div>
</nav>
<div style="height: 100%; width: 100%; display: flex; justify-content: center; align-items: center;">
<div class="card" style="padding: 1.25rem;">
<div class="field">
<label class="label">Username</label>
<div class="control has-icons-left has-icons-right">
<input class="input" type="text">
<span class="icon is-small is-left">
<i class="fas fa-user"></i>
</span>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<p class="control has-icons-left">
<input class="input" type="password">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="control">
<button class="button is-primary">Login</button>
</div>
</div>
</div>
</body>
</html>

113
src/templates/todo.html Normal file
View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>coconut-leaf Calendar</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.12.1/js/all.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.js"></script>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<nav class="navbar has-shadow is-spaced bd-navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="home">
<img src="icon.png"><b style="margin:0 0 0 14px;">coconut-leaf</b>
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="home">Calendar</a>
<a class="navbar-item" href="home">Todo</a>
<a class="navbar-item" href="about">About</a>
</div>
<div class="navbar-end">
<p class="control">
<a class="button is-primary" href="javascript:void(0);">Logout</a>
</p>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Language</a>
<div class="navbar-dropdown">
<a href="javascript:void(0);" class="navbar-item">English</a>
<a href="javascript:void(0);" class="navbar-item">简体中文</a>
</div>
</div>
</div>
</div>
</nav>
<div class="container" style="display: flex; flex-flow: column; margin-top: 1.25rem;">
<h1 class="title">Todo list</h1>
<div class="control-list">
<div class="control">
<a class="button is-primary"><span class="icon is-small"><i class="fas fa-plus"></i></span></a>
</div>
<div class="control">
<a class="button is-primary">
<span class="icon is-small"><i class="fas fa-sync"></i></span>
</a>
</div>
</div>
<div style="display: flex; flex-flow: column; margin-top: 1.25rem;">
<div class="todo-item card">
<div class="todo-item-words">
<p>this is a name</p>
</div>
<div class="todo-item-icon control">
<button class="button"><span class="icon is-small"><i class="fas fa-pen"></i></span></button>
</div>
<div class="todo-item-icon control">
<button class="button"><span class="icon is-small"><i class="fas fa-trash"></i></span></button>
</div>
</div>
<div class="todo-item card">
<div class="todo-item-words">
<p>this is a
namewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
</p>
</div>
<div class="todo-item-icon control">
<button class="button"><span class="icon is-small"><i class="fas fa-pen"></i></span></button>
</div>
<div class="todo-item-icon control">
<button class="button"><span class="icon is-small"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="todo-item card">
<div class="todo-item-words control">
<textarea class="textarea"></textarea>
</div>
<div class="todo-item-icon control">
<button class="button"><span class="icon is-small"><i class="fas fa-check"></i></span></button>
</div>
<div class="todo-item-icon control">
<button class="button"><span class="icon is-small"><i class="fas fa-times"></i></button>
</div>
</div>
</div>
</div>
</body>
</html>

34
src/utils.py Normal file
View File

@@ -0,0 +1,34 @@
import hashlib
import random
import uuid
ValidUsername = set(map(lambda x:chr(x), range(48, 58, 1))) | set(map(lambda x:chr(x), range(65, 91, 1))) | set(map(lambda x:chr(x), range(97, 123, 1)))
ValidPassword = set(map(lambda x:chr(x), range(33, 127, 1)))
def IsValidUsername(strl):
return (len(set(strl) - ValidUsername) == 0)
def IsValidPassword(strl):
return (len(set(strl) - ValidPassword) == 0)
def ComputePasswordHash(password):
s = hashlib.sha256()
s.update(password)
return s.hexdigest()
def GenerateUUID():
return str(uuid.uuid1())
def GenerateToken(username):
s = hashlib.sha256()
s.update(username)
s.update(str(GenerateSalt()))
return s.hexdigest()
def GenerateSalt():
return random.randint(0, 6172748)
def ComputePasswordHashWithSalt(passwordHashed, salt):
s = hashlib.sha256()
s.update(passwordHashed + str(salt))
return s.hexdigest()