Compare commits
11 Commits
1980f61242
...
v1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| a0e3385670 | |||
| 8e72e75a15 | |||
| 1277d36a42 | |||
| a9d06af3ae | |||
| bf441a6891 | |||
| 8323a9c1d8 | |||
| 6bf624a67f | |||
| bf3dc67754 | |||
| 1c7ddfc8a9 | |||
| cd411f8066 | |||
| 46a18fae99 |
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,16 +1,3 @@
|
||||
# ignore sqlite db
|
||||
*.db
|
||||
|
||||
# ignore my debug setting
|
||||
*.cfg
|
||||
|
||||
# ignore py cache
|
||||
src/__pycache__
|
||||
|
||||
# ignore any image first
|
||||
*.png
|
||||
*.jpg
|
||||
*.gif
|
||||
|
||||
# elimate vscode
|
||||
.vscode
|
||||
## ======== Personal ========
|
||||
# Ignore VSCode
|
||||
.vscode/
|
||||
|
||||
21
README.md
21
README.md
@@ -1,3 +1,24 @@
|
||||
# coconut-leaf
|
||||
|
||||
A self-host, multi-account calendar system.
|
||||
|
||||
## Warning
|
||||
|
||||
This project still work in progress. Because this project need a massive refactor now.
|
||||
If you want to check out the first version which can fufill basic usage, please switch to `v1-maintain` branch. In `main` branch, I am refactoring v1 and it will be updated to v2 in future.
|
||||
The first version of this project have too much C-style JavaScript. It is too complicated to maintain and cannot add any other new features. Therefore, it needs to be fully refactored using ES6 and some modern JavaScript tools. It will come soon.
|
||||
|
||||
## Features & shortcomings
|
||||
|
||||
### Features
|
||||
|
||||
* Basic calendar(valid range from 1970 to 2200)
|
||||
* Simple event system(including summary, color and etc)
|
||||
* Simple account system and share system
|
||||
* An looping event system.
|
||||
|
||||
### Shortcomings
|
||||
|
||||
* No extra properties for event(including location, busy status and etc. All of them can be written in summary property and extirely useless for myself. There are no plan to implement these in future.)
|
||||
* No alarm system(should be implemented in frontend in future?)
|
||||
|
||||
|
||||
19
backend/.gitignore
vendored
Normal file
19
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
## ======== Personal ========
|
||||
# Database file
|
||||
*.db
|
||||
|
||||
# Ignore setting file
|
||||
coconut-leaf.toml
|
||||
|
||||
## ======== Python ========
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
1
backend/.python-version
Normal file
1
backend/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.11
|
||||
76
backend/coconut-leaf.py
Normal file
76
backend/coconut-leaf.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import sys
|
||||
import logging
|
||||
from argparse import ArgumentParser
|
||||
from typing import cast
|
||||
from pathlib import Path
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Coconut-leaf")
|
||||
print("A self-host, multi-account calendar system.")
|
||||
print("Project: https://github.com/yyc12345/coconut-leaf")
|
||||
print("===================")
|
||||
|
||||
# Receive arguments
|
||||
parser = ArgumentParser(description="Coconut-leaf")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
required=True,
|
||||
type=Path,
|
||||
action="store",
|
||||
metavar="CONFIG_TOML",
|
||||
dest="config",
|
||||
help="The configuration file for coconut-leaf",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--init",
|
||||
action="store_true",
|
||||
dest="init",
|
||||
help="Set for initialize the calendar system",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load config file
|
||||
try:
|
||||
config.setup_config(cast(Path, args.config))
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Setup logging level
|
||||
logging_level = logging.DEBUG if config.get_config().others.debug else logging.INFO
|
||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging_level)
|
||||
|
||||
# Initialize the calendar system if needed
|
||||
if cast(bool, args.init):
|
||||
gotten_data = GetUsernamePassword()
|
||||
calendar = database.CalendarDatabase()
|
||||
calendar.init(*gotten_data)
|
||||
calendar.close()
|
||||
|
||||
logging.info("Staring server...")
|
||||
server.run()
|
||||
22
backend/coconut-leaf.template.toml
Normal file
22
backend/coconut-leaf.template.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[database]
|
||||
driver = "sqlite"
|
||||
|
||||
[database.config]
|
||||
path = "coconut-leaf.db"
|
||||
|
||||
# [database]
|
||||
# driver = "mysql"
|
||||
#
|
||||
# [database.config]
|
||||
# host = "localhost"
|
||||
# port = 3306
|
||||
# user = "root"
|
||||
# password = "password"
|
||||
# database = "coconut_leaf"
|
||||
|
||||
[web]
|
||||
port = 8888
|
||||
|
||||
[others]
|
||||
auto-token-clean-duration = 86400
|
||||
debug = true
|
||||
130
backend/config.py
Normal file
130
backend/config.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import tomllib
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class DatabaseDriver(StrEnum):
|
||||
SQLITE = "sqlite"
|
||||
MYSQL = "mysql"
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
return DatabaseDriver(raw["driver"])
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SqliteDatabaseConfig:
|
||||
path: str
|
||||
"""Database path"""
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
return SqliteDatabaseConfig(raw["path"])
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MysqlDatabaseConfig:
|
||||
host: str
|
||||
"""Database host"""
|
||||
port: int
|
||||
"""Database port"""
|
||||
user: str
|
||||
"""Database user"""
|
||||
password: str
|
||||
"""Database password"""
|
||||
database: str
|
||||
"""Database name"""
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
return MysqlDatabaseConfig(
|
||||
raw["host"], raw["port"], raw["user"], raw["password"], raw["database"]
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DatabaseConfig:
|
||||
driver: DatabaseDriver
|
||||
"""Database driver"""
|
||||
config: SqliteDatabaseConfig | MysqlDatabaseConfig
|
||||
"""Database config"""
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
if raw["driver"] == DatabaseDriver.SQLITE:
|
||||
return DatabaseConfig(
|
||||
DatabaseDriver.SQLITE, SqliteDatabaseConfig.from_raw(raw["config"])
|
||||
)
|
||||
elif raw["driver"] == DatabaseDriver.MYSQL:
|
||||
return DatabaseConfig(
|
||||
DatabaseDriver.MYSQL, MysqlDatabaseConfig.from_raw(raw["config"])
|
||||
)
|
||||
else:
|
||||
raise ValueError("Invalid database driver")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WebConfig:
|
||||
port: int
|
||||
"""Web server port"""
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
return WebConfig(raw["port"])
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OthersConfig:
|
||||
debug: bool
|
||||
"""Whether enable debug mode"""
|
||||
auto_token_clean_duration: int
|
||||
"""Auto token clean duration"""
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
return OthersConfig(raw["debug"], raw["auto-token-clean-duration"])
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Config:
|
||||
database: DatabaseConfig
|
||||
web: WebConfig
|
||||
others: OthersConfig
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw: dict):
|
||||
return Config(
|
||||
database=DatabaseConfig.from_raw(raw["database"]),
|
||||
web=WebConfig.from_raw(raw["web"]),
|
||||
others=OthersConfig.from_raw(raw["others"]),
|
||||
)
|
||||
|
||||
|
||||
_CONFIG: Optional[Config] = None
|
||||
|
||||
|
||||
def setup_config(p: Path) -> None:
|
||||
"""
|
||||
Setup config by given path.
|
||||
|
||||
Raise exception if config file is invalid.
|
||||
"""
|
||||
with open(p, "rb") as f:
|
||||
raw = tomllib.load(f)
|
||||
|
||||
global _CONFIG
|
||||
_CONFIG = Config.from_raw(raw)
|
||||
|
||||
|
||||
def get_config() -> Config:
|
||||
"""
|
||||
Get config instance.
|
||||
|
||||
Raises RuntimeError if config is not loaded.
|
||||
"""
|
||||
if _CONFIG is None:
|
||||
raise RuntimeError("Config is not loaded. Call setup_config() first.")
|
||||
else:
|
||||
return _CONFIG
|
||||
@@ -1,13 +1,16 @@
|
||||
import config
|
||||
import sqlite3
|
||||
import json
|
||||
import utils
|
||||
import threading
|
||||
import logging
|
||||
import dt
|
||||
from typing import cast
|
||||
from pathlib import Path
|
||||
|
||||
def SafeDatabaseOperation(func):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
def wrapper(self: 'CalendarDatabase', *args, **kwargs):
|
||||
cfg = config.get_config()
|
||||
|
||||
with self.mutex:
|
||||
# check database and acquire cursor
|
||||
try:
|
||||
@@ -15,16 +18,16 @@ def SafeDatabaseOperation(func):
|
||||
self.cursor = self.db.cursor()
|
||||
except Exception as e:
|
||||
self.cursor = None
|
||||
if config.CustomConfig['debug']:
|
||||
if cfg.others.debug:
|
||||
logging.exception(e)
|
||||
return (False, str(e), None)
|
||||
|
||||
# do real data work
|
||||
try:
|
||||
currentTime = utils.GetCurrentTimestamp()
|
||||
if currentTime - self.latestClean > config.CustomConfig['auto-token-clean-duration']:
|
||||
if currentTime - self.latestClean > cfg.others.auto_token_clean_duration:
|
||||
self.latestClean = currentTime
|
||||
print('Cleaning outdated token...')
|
||||
logging.info('Cleaning outdated token...')
|
||||
self.tokenOper_clean()
|
||||
|
||||
result = (True, '', func(self, *args, **kwargs))
|
||||
@@ -36,13 +39,19 @@ def SafeDatabaseOperation(func):
|
||||
self.cursor.close()
|
||||
self.cursor = None
|
||||
self.db.rollback()
|
||||
if config.CustomConfig['debug']:
|
||||
if cfg.others.debug:
|
||||
logging.exception(e)
|
||||
return (False, str(e), None)
|
||||
|
||||
return wrapper
|
||||
|
||||
class CalendarDatabase(object):
|
||||
class CalendarDatabase:
|
||||
|
||||
db: sqlite3.Connection
|
||||
cursor: sqlite3.Cursor
|
||||
mutex: threading.Lock
|
||||
latestClean: int
|
||||
|
||||
def __init__(self):
|
||||
self.db = None
|
||||
self.cursor = None
|
||||
@@ -53,23 +62,36 @@ class CalendarDatabase(object):
|
||||
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'], check_same_thread = False)
|
||||
self.db.execute('PRAGMA encoding = "UTF-8";')
|
||||
self.db.execute('PRAGMA foreign_keys = ON;')
|
||||
elif config.CustomConfig['database-type'] == 'mysql':
|
||||
raise Exception('Not implemented database')
|
||||
else:
|
||||
raise Exception('Unknow database type')
|
||||
cfg = config.get_config()
|
||||
match cfg.database.driver:
|
||||
case config.DatabaseDriver.SQLITE:
|
||||
self.db = sqlite3.connect(cast(config.SqliteDatabaseConfig, cfg.database.config).path, check_same_thread = False)
|
||||
self.db.execute('PRAGMA encoding = "UTF-8";')
|
||||
self.db.execute('PRAGMA foreign_keys = ON;')
|
||||
case config.DatabaseDriver.MYSQL:
|
||||
raise Exception('Not implemented database')
|
||||
case _:
|
||||
raise Exception('Unknow database type')
|
||||
|
||||
def init(self, username, password):
|
||||
if (self.is_database_valid()):
|
||||
raise Exception('Database is opened')
|
||||
|
||||
# establish tables
|
||||
cfg = config.get_config()
|
||||
backend_path = Path(__file__).resolve().parent
|
||||
backend_sql_path = backend_path / 'sql'
|
||||
match cfg.database.driver:
|
||||
case config.DatabaseDriver.SQLITE:
|
||||
sql_file = backend_sql_path / 'sqlite.sql'
|
||||
case config.DatabaseDriver.MYSQL:
|
||||
raise Exception('Not implemented database')
|
||||
case _:
|
||||
raise Exception('Unknow database type')
|
||||
|
||||
self.open()
|
||||
cursor = self.db.cursor()
|
||||
with open('sql/sqlite.sql', 'r', encoding='utf-8') as fsql:
|
||||
with open(sql_file, 'r', encoding='utf-8') as fsql:
|
||||
cursor.executescript(fsql.read())
|
||||
|
||||
# finish init
|
||||
@@ -272,11 +294,11 @@ class CalendarDatabase(object):
|
||||
sqlList.append('[ccn_loopDateTimeStart] = ?')
|
||||
argumentsList.append(analyseData[5])
|
||||
sqlList.append('[ccn_loopDateTimeEnd] = ?')
|
||||
argumentsList.append(dt.ResolveLoopStr(
|
||||
argumentsList.append(str(dt.ResolveLoopStr(
|
||||
analyseData[8],
|
||||
analyseData[5],
|
||||
analyseData[7]
|
||||
))
|
||||
)))
|
||||
|
||||
# execute
|
||||
argumentsList.append(uuid)
|
||||
@@ -531,8 +553,8 @@ class CalendarDatabase(object):
|
||||
argumentsList.append(_username)
|
||||
self.cursor.execute('UPDATE user SET {} WHERE [ccn_name] = ?;'.format(', '.join(sqlList)),
|
||||
tuple(argumentsList))
|
||||
print(cache)
|
||||
print(tuple(argumentsList))
|
||||
logging.debug(cache)
|
||||
logging.debug(tuple(argumentsList))
|
||||
if self.cursor.rowcount != 1:
|
||||
raise Exception('Fail to update due to no matched rows or too much rows.')
|
||||
return True
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
import time
|
||||
import re
|
||||
import logging
|
||||
import typing
|
||||
from functools import reduce
|
||||
import utils
|
||||
|
||||
@@ -13,7 +14,9 @@ MAX_TIMESTAMP = int(MAX_DATETIME.timestamp() / 60)
|
||||
DAY1_SPAN = 60 * 24
|
||||
DAY7_SPAN = 7 * DAY1_SPAN
|
||||
|
||||
def ResolveLoopStr(strl, starttime, tzoffset):
|
||||
LoopHandle = typing.Callable[[re.Match, int, int, int], int]
|
||||
|
||||
def ResolveLoopStr(strl: str, starttime: int, tzoffset: int) -> int:
|
||||
# check no loop
|
||||
if strl == '':
|
||||
return starttime
|
||||
@@ -40,7 +43,7 @@ def ResolveLoopStr(strl, starttime, tzoffset):
|
||||
raise Exception('Invalid loopRules')
|
||||
|
||||
|
||||
def LoopHandle_Year(searchResult, starttime, times, tzoffset):
|
||||
def LoopHandle_Year(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||
clientDate = datetime.datetime.fromtimestamp(starttime * 60, UTCTimezone(tzoffset))
|
||||
isStrict = searchResult.group(1) == 'S'
|
||||
yearSpan = int(searchResult.group(2))
|
||||
@@ -52,7 +55,7 @@ def LoopHandle_Year(searchResult, starttime, times, tzoffset):
|
||||
if clientMonth == 2 and clientDay == 29:
|
||||
if isStrict:
|
||||
realSpan = utils.LCM(yearSpan, 4)
|
||||
print(realSpan)
|
||||
logging.debug(realSpan)
|
||||
valCache = starttime
|
||||
while valCache < MAX_TIMESTAMP and times > 0:
|
||||
newYear += realSpan
|
||||
@@ -71,7 +74,7 @@ def LoopHandle_Year(searchResult, starttime, times, tzoffset):
|
||||
val = starttime + DAY1_SPAN * (DaysCount(newYear, newMonth, newDay) - DaysCount(clientYear, clientMonth, clientDay))
|
||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||
|
||||
def LoopHandle_Month(searchResult, starttime, times, tzoffset):
|
||||
def LoopHandle_Month(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||
isStrict = searchResult.group(1) == 'S'
|
||||
loopType = searchResult.group(2)
|
||||
monthSpan = int(searchResult.group(3))
|
||||
@@ -144,7 +147,7 @@ def LoopHandle_Month(searchResult, starttime, times, tzoffset):
|
||||
val = starttime + DAY1_SPAN * (DaysCount(newYear, newMonth, newDay) - DaysCount(clientYear, clientMonth, clientDay))
|
||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||
|
||||
def LoopHandle_Week(searchResult, starttime, times, tzoffset):
|
||||
def LoopHandle_Week(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||
weekOccupied = tuple(map(lambda x: x == 'T', searchResult.group(1)))
|
||||
weekEventCount = reduce(lambda x, y: x + (1 if y else 0), weekOccupied, 0)
|
||||
if weekEventCount == 0:
|
||||
@@ -170,25 +173,25 @@ def LoopHandle_Week(searchResult, starttime, times, tzoffset):
|
||||
val -= 1
|
||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||
|
||||
def LoopHandle_Day(searchResult, starttime, times, tzoffset):
|
||||
def LoopHandle_Day(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||
val = starttime + DAY1_SPAN * times * int(searchResult.group(1))
|
||||
val -= 1
|
||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||
|
||||
precompiledLoopRules = (
|
||||
precompiledLoopRules: tuple[tuple[re.Pattern, LoopHandle], ...] = (
|
||||
(re.compile(r'^Y([SR]{1})([1-9]\d*)$'), LoopHandle_Year),
|
||||
(re.compile(r'^M([SR]{1})([ABCD]{1})([1-9]\d*)$'), LoopHandle_Month),
|
||||
(re.compile(r'^W([TF]{7})([1-9]\d*)$'), LoopHandle_Week),
|
||||
(re.compile(r'^D([1-9]\d*)$'), LoopHandle_Day)
|
||||
)
|
||||
|
||||
precompiledLoopStopRules = {
|
||||
precompiledLoopStopRules: dict[str, re.Pattern] = {
|
||||
'infinity': re.compile(r'^F$'),
|
||||
'datetime': re.compile(r'^D([1-9]\d*|0)$'),
|
||||
'times': re.compile(r'^T([1-9]\d*)$')
|
||||
}
|
||||
|
||||
def LeapYearCountEx(endYear, includeThis = False, baseYear = 1, includeBase = True):
|
||||
def LeapYearCountEx(endYear: int, includeThis: bool = False, baseYear: int = 1, includeBase: bool = True):
|
||||
if not includeThis:
|
||||
endYear -= 1
|
||||
if includeBase:
|
||||
@@ -204,10 +207,10 @@ def LeapYearCountEx(endYear, includeThis = False, baseYear = 1, includeBase = Tr
|
||||
|
||||
return (endly - basely)
|
||||
|
||||
def LeapYearCount(year):
|
||||
def LeapYearCount(year: int):
|
||||
return LeapYearCountEx(year, False, 1, True)
|
||||
|
||||
def IsLeapYear(year):
|
||||
def IsLeapYear(year: int):
|
||||
isLeap = False
|
||||
if year % 4 == 0:
|
||||
isLeap = True
|
||||
@@ -217,7 +220,7 @@ def IsLeapYear(year):
|
||||
isLeap = True
|
||||
return isLeap
|
||||
|
||||
def DaysCount(year, month, day):
|
||||
def DaysCount(year: int, month: int, day: int):
|
||||
ly = LeapYearCountEx(year, False, 1, True)
|
||||
days = 365 * (year - 1)
|
||||
days += ly
|
||||
@@ -231,7 +234,7 @@ def DaysCount(year, month, day):
|
||||
days += day - 1
|
||||
return days
|
||||
|
||||
def DayOfWeek(year, month, day):
|
||||
def DayOfWeek(year: int, month: int, day: int):
|
||||
# as we know, 1/1/1900 is Monday.
|
||||
# via this method, we can got 1/1/1 is Monday
|
||||
# compute day span
|
||||
@@ -240,7 +243,7 @@ def DayOfWeek(year, month, day):
|
||||
# return day of week (from 0 - 6, corresponding with python)
|
||||
return days % 7
|
||||
|
||||
def GetDayInMonth(year, month, day):
|
||||
def GetDayInMonth(year: int, month: int, day: int):
|
||||
days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0)
|
||||
firstDayOfWeek = DayOfWeek(year, month, 1)
|
||||
dayOfWeek = (firstDayOfWeek + day - 1) % 7
|
||||
@@ -253,7 +256,7 @@ def GetDayInMonth(year, month, day):
|
||||
|
||||
return (dayForwards, dayBackwards, weeksForward, dayOfWeek, weeksBackwards, dayOfWeek)
|
||||
|
||||
def GetMonthWeekStatistics(year, month):
|
||||
def GetMonthWeekStatistics(year: int, month: int):
|
||||
days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0)
|
||||
firstDayOfWeek = DayOfWeek(year, month, 1)
|
||||
lastDayOfWeek = (firstDayOfWeek + days - 1) % 7
|
||||
@@ -269,14 +272,18 @@ def GetMonthWeekStatistics(year, month):
|
||||
return tuple(result)
|
||||
|
||||
class UTCTimezone(datetime.tzinfo):
|
||||
def __init__(self, offset = 0):
|
||||
self._offset = offset
|
||||
|
||||
__offset: int
|
||||
|
||||
def __init__(self, offset: int = 0):
|
||||
self.__offset = offset
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return datetime.timedelta(minutes=self._offset)
|
||||
return datetime.timedelta(minutes=self.__offset)
|
||||
|
||||
def tzname(self, dt):
|
||||
return 'UTC {}'.format(self._offset)
|
||||
return 'UTC {}'.format(self.__offset)
|
||||
|
||||
def dst(self, dt):
|
||||
return datetime.timedelta(0)
|
||||
|
||||
14
backend/pyproject.toml
Normal file
14
backend/pyproject.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "coleaf-backend"
|
||||
version = "1.1.0"
|
||||
description = "The backend of coconut-leaf."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"flask==2.2.3",
|
||||
]
|
||||
[tool.uv]
|
||||
constraint-dependencies = [
|
||||
"Werkzeug==2.2.2",
|
||||
"MarkupSafe==2.1.5"
|
||||
]
|
||||
@@ -1,39 +1,45 @@
|
||||
from flask import Flask
|
||||
from flask import g
|
||||
# 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 abort
|
||||
from flask import redirect
|
||||
|
||||
from functools import reduce
|
||||
import json
|
||||
import os
|
||||
# from functools import reduce
|
||||
# import json
|
||||
# import os
|
||||
|
||||
import config
|
||||
import database
|
||||
import utils
|
||||
from pathlib import Path
|
||||
|
||||
app = Flask(__name__)
|
||||
_FRONTEND_PATH = Path(__file__).resolve().parent.parent / "frontend"
|
||||
app = Flask(
|
||||
__name__,
|
||||
static_folder=_FRONTEND_PATH / "static",
|
||||
template_folder=_FRONTEND_PATH / "templates",
|
||||
)
|
||||
calendar_db = database.CalendarDatabase()
|
||||
|
||||
# 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()
|
||||
'''
|
||||
# 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
|
||||
|
||||
@@ -216,7 +222,7 @@ def api_collection_getFullOwnHandle():
|
||||
|
||||
@app.route('/api/collection/getListOwn', methods=['POST'])
|
||||
def api_collection_getListOwnHandle():
|
||||
return SmartDbCaller(calendar_db.collection_getListlOwn,
|
||||
return SmartDbCaller(calendar_db.collection_getListOwn,
|
||||
(('token', str, False), ),
|
||||
None)
|
||||
|
||||
@@ -446,6 +452,6 @@ def ConstructResponseBody(returnedTuple):
|
||||
|
||||
def run():
|
||||
calendar_db.open()
|
||||
app.run(port=config.CustomConfig['web']['port'])
|
||||
app.run(port=config.get_config().web.port)
|
||||
calendar_db.close()
|
||||
|
||||
0
backend/sql/mysql.sql
Normal file
0
backend/sql/mysql.sql
Normal file
117
backend/uv.lock
generated
Normal file
117
backend/uv.lock
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[manifest]
|
||||
constraints = [
|
||||
{ name = "markupsafe", specifier = "==2.1.5" },
|
||||
{ name = "werkzeug", specifier = "==2.2.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coleaf-backend"
|
||||
version = "1.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "flask", specifier = "==2.2.3" }]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "2.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/5c/ff9047989bd995b1098d14b03013f160225db2282925b517bb4a967752ee/Flask-2.2.3.tar.gz", hash = "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d", size = 697599, upload-time = "2023-02-15T22:43:57.265Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/9c/a3542594ce4973786236a1b7b702b8ca81dbf40ea270f0f96284f0c27348/Flask-2.2.3-py3-none-any.whl", hash = "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d", size = 101839, upload-time = "2023-02-15T22:43:55.501Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/c1/1c8e539f040acd80f844c69a5ef8e2fccdf8b442dabb969e497b55d544e1/Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", size = 844378, upload-time = "2022-08-08T21:44:15.376Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/27/be6ddbcf60115305205de79c29004a0c6bc53cec814f733467b1bb89386d/Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5", size = 232700, upload-time = "2022-08-08T21:44:13.251Z" },
|
||||
]
|
||||
@@ -29,6 +29,9 @@ div.perfectTable > div > div {
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
div.perfectTable > div {
|
||||
@@ -36,6 +39,11 @@ div.perfectTable > div {
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
div.perfectTable > div > div[picked=true] {
|
||||
background: hsl(171, 100%, 41%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -64,6 +72,8 @@ div.pickerContainer > svg {
|
||||
div.pickerContainer > svg > text {
|
||||
dominant-baseline: middle;
|
||||
text-anchor: middle;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
div.pickerContainer > svg > circle[type=background] {
|
||||
@@ -90,7 +100,7 @@ header.pickerHeader {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-grow: 0;
|
||||
flex-basis: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -63,7 +63,6 @@ ccn-i18n-login-form-login=Login
|
||||
|
||||
ccn-i18n-todo-todoList=Todo list
|
||||
|
||||
ccn-i18n-calendar-calendar-jump=Jump
|
||||
ccn-i18n-calendar-calendar-today=Today
|
||||
ccn-i18n-calendar-calendar-add=Add...
|
||||
ccn-i18n-calendar-calendar-stripedEvents={0} items
|
||||
@@ -63,7 +63,6 @@ ccn-i18n-login-form-login=登录
|
||||
|
||||
ccn-i18n-todo-todoList=待办列表
|
||||
|
||||
ccn-i18n-calendar-calendar-jump=转到
|
||||
ccn-i18n-calendar-calendar-today=今天
|
||||
ccn-i18n-calendar-calendar-add=添加...
|
||||
ccn-i18n-calendar-calendar-stripedEvents=共{0}项
|
||||
@@ -139,3 +138,16 @@ ccn-i18n-tokenItem-ua=UA:
|
||||
ccn-i18n-tokenItem-ip=IP:
|
||||
ccn-i18n-tokenItem-expireOn=过期时间:
|
||||
ccn-i18n-tokenItem-isMe=这是你当前使用的登录凭据
|
||||
|
||||
ccn-i18n-datetime-loopStopRuleText-infinity=永远循环。
|
||||
ccn-i18n-datetime-loopStopRuleText-datetime=到{0}停止循环。
|
||||
ccn-i18n-datetime-loopStopRuleText-times=循环{0}次。
|
||||
ccn-i18n-datetime-loopRuleText-modeStrict=严格模式。
|
||||
ccn-i18n-datetime-loopRuleText-modeRough=宽松模式。
|
||||
ccn-i18n-datetime-loopRuleText-year=每{0}年于{1}循环一次。
|
||||
ccn-i18n-datetime-loopRuleText-monthA=每{0}月的第{1}日循环一次。
|
||||
ccn-i18n-datetime-loopRuleText-monthB=每{0}月的倒数第{1}日循环一次。
|
||||
ccn-i18n-datetime-loopRuleText-monthC=每{0}月的第{1}个星期{2}循环一次。
|
||||
ccn-i18n-datetime-loopRuleText-monthD=每{0}月的倒数第{1}个星期{2}循环一次。
|
||||
ccn-i18n-datetime-loopRuleText-week=每{0}周的{1}循环一次。
|
||||
ccn-i18n-datetime-loopRuleText-day=每{0}天循环一次。
|
||||
@@ -2,9 +2,9 @@
|
||||
var ccn_datetime_monthDayCount = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
var ccn_datetime_MIN_YEAR = 1950;
|
||||
var ccn_datetime_MAX_YEAR = 2199;
|
||||
var ccn_datetime_MIN_DATETIME = new Date(Date.UTC(1950, 1, 1, 0, 0, 0, 0));
|
||||
var ccn_datetime_MAX_DATETIME = new Date(Date.UTC(2200, 1, 1, 0, 0, 0, 0));
|
||||
var ccn_datetime_MAX_YEAR = 2200;
|
||||
var ccn_datetime_MIN_DATETIME = new Date(Date.UTC(ccn_datetime_MIN_YEAR, 0, 1, 0, 0, 0, 0));
|
||||
var ccn_datetime_MAX_DATETIME = new Date(Date.UTC(ccn_datetime_MAX_YEAR, 0, 1, 0, 0, 0, 0));
|
||||
var ccn_datetime_MIN_TIMESTAMP = Math.floor(ccn_datetime_MIN_DATETIME.getTime() / 60000);
|
||||
var ccn_datetime_MAX_TIMESTAMP = Math.floor(ccn_datetime_MAX_DATETIME.getTime() / 60000);
|
||||
|
||||
@@ -261,8 +261,76 @@ function ccn_datetime_ResolveLoopRules4Event(loopRules, loopDateTimeStart, loopD
|
||||
return realResult;
|
||||
}
|
||||
|
||||
function ccn_datetime_ResolveLoopRules4Text(loopRules) {
|
||||
return "";
|
||||
function ccn_datetime_ResolveLoopRules4Text(strl, startDateTime, timezoneOffset) {
|
||||
if (strl == '') return "";
|
||||
|
||||
var sp = strl.split('-');
|
||||
if (sp.length != 2) return "";
|
||||
var loopRules = undefined;
|
||||
var loopStopRules = undefined;
|
||||
var datetimeInstance = new Date((startDateTime + timezoneOffset) * 60000)
|
||||
|
||||
if (ccn_datetime_precompiledLoopRules.year.test(sp[0])) {
|
||||
if (RegExp.$1 == 'S')
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-modeStrict');
|
||||
else
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-modeRough');
|
||||
loopRules += $.i18n.prop('ccn-i18n-datetime-loopRuleText-year')
|
||||
.format(parseInt(RegExp.$2), datetimeInstance.toLocaleDateString(undefined, {timeZone: "UTC"}));
|
||||
} else if (ccn_datetime_precompiledLoopRules.month.test(sp[0])) {
|
||||
if (RegExp.$1 == 'S')
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-modeStrict');
|
||||
else
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-modeRough');
|
||||
|
||||
var dayInMonth = ccn_datetime_GetDayInMonth(
|
||||
datetimeInstance.getUTCFullYear(),
|
||||
datetimeInstance.getUTCMonth() + 1,
|
||||
datetimeInstance.getUTCDate());
|
||||
switch(RegExp.$2) {
|
||||
case 'A':
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-monthA')
|
||||
.format(parseInt(RegExp.$3), dayInMonth[0]);
|
||||
break;
|
||||
case 'B':
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-monthB')
|
||||
.format(parseInt(RegExp.$3), dayInMonth[1]);
|
||||
break;
|
||||
case 'C':
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-monthC')
|
||||
.format(parseInt(RegExp.$3), dayInMonth[2], dayInMonth[3]);
|
||||
break;
|
||||
case 'D':
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-monthD')
|
||||
.format(parseInt(RegExp.$3), dayInMonth[4], dayInMonth[5]);
|
||||
break;
|
||||
}
|
||||
} else if (ccn_datetime_precompiledLoopRules.week.test(sp[0])) {
|
||||
var weekOfDayCache = [];
|
||||
for (var i = 0; i < 7; i++) {
|
||||
if (RegExp.$1[i] == 'T')
|
||||
weekOfDayCache.push(ccn_i18n_UniversalGetDayOfWeek(i));
|
||||
}
|
||||
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-week')
|
||||
.format(parseInt(RegExp.$2), weekOfDayCache.join(', '));
|
||||
} else if (ccn_datetime_precompiledLoopRules.day.test(sp[0])) {
|
||||
loopRules = $.i18n.prop('ccn-i18n-datetime-loopRuleText-day')
|
||||
.format(parseInt(RegExp.$1));
|
||||
} else return "";
|
||||
|
||||
|
||||
if (ccn_datetime_precompiledLoopStopRules.infinity.test(sp[1])) {
|
||||
loopStopRules = $.i18n.prop('ccn-i18n-datetime-loopStopRuleText-infinity');
|
||||
} else if (ccn_datetime_precompiledLoopStopRules.datetime.test(sp[1])) {
|
||||
loopStopRules = $.i18n.prop('ccn-i18n-datetime-loopStopRuleText-datetime')
|
||||
.format(new Date(parseInt(RegExp.$1)).toLocaleDateString());
|
||||
} else if (ccn_datetime_precompiledLoopStopRules.times.test(sp[1])) {
|
||||
loopStopRules = $.i18n.prop('ccn-i18n-datetime-loopStopRuleText-times')
|
||||
.format(parseInt(RegExp.$1));
|
||||
} else return "";
|
||||
|
||||
return (loopRules + loopStopRules);
|
||||
}
|
||||
|
||||
function ccn_datetime_LeapYearCountEx(endYear, includeThis, baseYear, includeBase) {
|
||||
522
frontend/static/js/datetimepicker.js
Normal file
522
frontend/static/js/datetimepicker.js
Normal file
@@ -0,0 +1,522 @@
|
||||
var ccn_datetimepicker_tabType = {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 2,
|
||||
hour: 3,
|
||||
minute: 4
|
||||
};
|
||||
|
||||
var ccn_datetimepicker_dialPlateWidth = 200;
|
||||
var ccn_datetimepicker_dialPlateRadius = ccn_datetimepicker_dialPlateWidth / 2;
|
||||
var ccn_datetimepicker_dialPlateHourInnerPercent = 0.6;
|
||||
var ccn_datetimepicker_dialPlateHourOutterPercent = 0.8;
|
||||
var ccn_datetimepicker_dialPlateHourDistinguishPercent = 0.7;
|
||||
var ccn_datetimepicker_dialPlateMinutePercent = 0.8;
|
||||
var ccn_datetimepicker_dialPlateHourResolution = Math.PI * 2 / 12;
|
||||
var ccn_datetimepicker_dialPlateMinuteResolution = Math.PI * 2 / 60;
|
||||
|
||||
var ccn_datetimepicker_mode = undefined;
|
||||
var ccn_datetimepicker_isUTC = undefined;
|
||||
var ccn_datetimepicker_pickerIndex = undefined;
|
||||
|
||||
var ccn_datetimepicker_enableMinuteDrag = false;
|
||||
var ccn_datetimepicker_enableHourDrag = false;
|
||||
|
||||
var ccn_datetimepicker_internalDateTime = new Date();
|
||||
var ccn_datetimepicker_displayCacheDateTime = new Date();
|
||||
|
||||
// ========================================= export func
|
||||
|
||||
function ccn_datetimepicker_Insert() {
|
||||
$('body').append(ccn_template_datetimepicker.render());
|
||||
|
||||
// bind size event and trigge once
|
||||
$(window).resize(ccn_datetimepicker_RefreshSvg).resize();
|
||||
|
||||
// add data attr
|
||||
for(var i = 0; i < 3; i++) {
|
||||
for(var j = 0; j < 4; j++) {
|
||||
$('#ccn-datetimepiacker-panelMonth-table > div:nth-child({0}) > div:nth-child({1})'.format(i + 1, j + 1))
|
||||
.attr('data', i * 4 + j);
|
||||
}
|
||||
}
|
||||
|
||||
// bind header event
|
||||
$('header.pickerHeader > div').click(function() {
|
||||
ccn_datetimepicker_SwitchTab(ccn_datetimepicker_Str2TabType($(this).attr('type')));
|
||||
});
|
||||
|
||||
// bind button event
|
||||
$('#ccn-datetimepiacker-panelYear-prevBtn').click(function() {
|
||||
ccn_datetimepicker_PrevNextYear(true);
|
||||
});
|
||||
$('#ccn-datetimepiacker-panelYear-nextBtn').click(function() {
|
||||
ccn_datetimepicker_PrevNextYear(false);
|
||||
});
|
||||
$('#ccn-datetimepiacker-panelMonth-prevBtn').click(function() {
|
||||
ccn_datetimepicker_PrevNextMonth(true);
|
||||
});
|
||||
$('#ccn-datetimepiacker-panelMonth-nextBtn').click(function() {
|
||||
ccn_datetimepicker_PrevNextMonth(false);
|
||||
});
|
||||
$('#ccn-datetimepiacker-panelDay-prevBtn').click(function() {
|
||||
ccn_datetimepicker_PrevNextDay(true);
|
||||
});
|
||||
$('#ccn-datetimepiacker-panelDay-nextBtn').click(function() {
|
||||
ccn_datetimepicker_PrevNextDay(false);
|
||||
});
|
||||
|
||||
$('#ccn-datetimepiacker-panelYear-table > div > div').click(ccn_datetimepicker_ClickYear);
|
||||
$('#ccn-datetimepiacker-panelMonth-table > div > div').click(ccn_datetimepicker_ClickMonth);
|
||||
$('#ccn-datetimepiacker-panelDay-table > div:nth-child(n+1) > div').click(ccn_datetimepicker_ClickDay);
|
||||
|
||||
$('#ccn-datetimepicker-panelHour')
|
||||
.mousedown(ccn_datetimepicker_StartDragHour)
|
||||
.mousemove(ccn_datetimepicker_DraggingHour)
|
||||
.mouseup(ccn_datetimepicker_StopDragHour)
|
||||
.on('touchstart', ccn_datetimepicker_StartDragHour)
|
||||
.on('touchmove', ccn_datetimepicker_DraggingHour)
|
||||
.on('touchend', ccn_datetimepicker_StopDragHour);
|
||||
|
||||
$('#ccn-datetimepicker-panelMinute')
|
||||
.mousedown(ccn_datetimepicker_StartDragMinute)
|
||||
.mousemove(ccn_datetimepicker_DraggingMinute)
|
||||
.mouseup(ccn_datetimepicker_StopDragMinute)
|
||||
.on('touchstart', ccn_datetimepicker_StartDragMinute)
|
||||
.on('touchmove', ccn_datetimepicker_DraggingMinute)
|
||||
.on('touchend', ccn_datetimepicker_StopDragMinute);
|
||||
|
||||
$('#ccn-datetimepicker-btnConfirm').click(ccn_datetimepicker_Confirm);
|
||||
$('#ccn-datetimepicker-btnCancel').click(ccn_datetimepicker_Cancel);
|
||||
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Modal(mode, pickerIndex, isUTC) {
|
||||
ccn_datetimepicker_mode = mode;
|
||||
ccn_datetimepicker_isUTC = isUTC;
|
||||
ccn_datetimepicker_pickerIndex = pickerIndex;
|
||||
|
||||
ccn_datetimepicker_internalDateTime = ccn_datetimepicker_Get(pickerIndex, false);
|
||||
|
||||
$('header.pickerHeader > div').hide();
|
||||
switch(mode) {
|
||||
case ccn_datetimepicker_tabType.minute:
|
||||
$('header.pickerHeader > div[type=minute]').show();
|
||||
case ccn_datetimepicker_tabType.hour:
|
||||
$('header.pickerHeader > div[type=hour]').show();
|
||||
case ccn_datetimepicker_tabType.day:
|
||||
$('header.pickerHeader > div[type=day]').show();
|
||||
case ccn_datetimepicker_tabType.month:
|
||||
$('header.pickerHeader > div[type=month]').show();
|
||||
case ccn_datetimepicker_tabType.year:
|
||||
$('header.pickerHeader > div[type=year]').show();
|
||||
break;
|
||||
}
|
||||
|
||||
$('#ccn-datetimepicker-modal').addClass('is-active');
|
||||
ccn_datetimepicker_SwitchTab(mode); // this call is set in there by design. if you don't show the dialog, the call of svg resize will fail.
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Confirm() {
|
||||
// update and call callback func
|
||||
ccn_datetimepicker_Set(
|
||||
ccn_datetimepicker_pickerIndex,
|
||||
ccn_datetimepicker_internalDateTime,
|
||||
ccn_datetimepicker_isUTC,
|
||||
ccn_datetimepicker_mode
|
||||
);
|
||||
|
||||
$('#ccn-datetimepicker-modal').removeClass('is-active');
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Cancel() {
|
||||
$('#ccn-datetimepicker-modal').removeClass('is-active');
|
||||
}
|
||||
|
||||
// ========================================= internal func
|
||||
|
||||
function ccn_datetimepicker_OnSvgResize(ele) {
|
||||
var scale = 200 / Math.min(ele.width(), ele.height());
|
||||
ele.css('font-size', scale + 'em');
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_SwitchTab(newTab) {
|
||||
$('div.pickerContainer > *').hide();
|
||||
|
||||
ccn_datetimepicker_displayCacheDateTime.setTime(ccn_datetimepicker_internalDateTime.getTime());
|
||||
ccn_datetimepicker_RefreshDisplay(newTab);
|
||||
|
||||
switch(newTab) {
|
||||
case ccn_datetimepicker_tabType.year:
|
||||
$('#ccn-datetimepicker-panelYear').show();
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.month:
|
||||
$('#ccn-datetimepicker-panelMonth').show();
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.day:
|
||||
$('#ccn-datetimepicker-panelDay').show();
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.hour:
|
||||
$('#ccn-datetimepicker-panelHour').show();
|
||||
ccn_datetimepicker_RefreshSvg(); // immediately trigger once svg resize
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.minute:
|
||||
$('#ccn-datetimepicker-panelMinute').show();
|
||||
ccn_datetimepicker_RefreshSvg(); // immediately trigger once svg resize
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_RefreshDisplay(tab) {
|
||||
// header should be refreshed entirely
|
||||
$('#ccn-datetimepicker-datetime-year').text(ccn_datetimepicker_internalDateTime.getFullYear());
|
||||
$('#ccn-datetimepicker-datetime-month').text(ccn_datetimepicker_internalDateTime.getMonth() + 1);
|
||||
$('#ccn-datetimepicker-datetime-day').text(ccn_datetimepicker_internalDateTime.getDate());
|
||||
$('#ccn-datetimepicker-datetime-hour').text(ccn_datetimepicker_internalDateTime.getHours());
|
||||
$('#ccn-datetimepicker-datetime-minute').text(ccn_datetimepicker_internalDateTime.getMinutes());
|
||||
|
||||
// refresh tab according to specific `tab`
|
||||
switch(tab) {
|
||||
case ccn_datetimepicker_tabType.year:
|
||||
var startYear = Math.floor((ccn_datetimepicker_displayCacheDateTime.getFullYear() - ccn_datetime_MIN_YEAR) / 12) * 12 + ccn_datetime_MIN_YEAR;
|
||||
var counter = startYear;
|
||||
for(var i = 0; i < 3; i++) {
|
||||
for(var j = 0; j < 4; j++, counter++) {
|
||||
var ele = $('#ccn-datetimepiacker-panelYear-table > div:nth-child({0}) > div:nth-child({1})'.format(i + 1, j + 1));
|
||||
if (counter < ccn_datetime_MAX_YEAR) {
|
||||
ele.attr('data', counter)
|
||||
.text(counter);
|
||||
} else {
|
||||
ele.attr('data', '')
|
||||
.html(' ');
|
||||
}
|
||||
|
||||
if (counter == ccn_datetimepicker_internalDateTime.getFullYear()) ele.attr('picked', 'true');
|
||||
else ele.attr('picked', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
$('#ccn-datetimepiacker-panelYear-title')
|
||||
.text('{0} - {1}'.format(startYear, startYear + 12 < ccn_datetime_MAX_YEAR ? startYear + 12 : ccn_datetime_MAX_YEAR));
|
||||
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.month:
|
||||
$('#ccn-datetimepiacker-panelMonth-table > div > div').attr('picked', 'false');
|
||||
if (ccn_datetimepicker_internalDateTime.getFullYear() == ccn_datetimepicker_displayCacheDateTime.getFullYear()) {
|
||||
var month = ccn_datetimepicker_internalDateTime.getMonth();
|
||||
$('#ccn-datetimepiacker-panelMonth-table > div:nth-child({0}) > div:nth-child({1})'.format(Math.floor(month / 4) + 1, (month % 4) + 1))
|
||||
.attr('picked', 'true');
|
||||
}
|
||||
|
||||
$('#ccn-datetimepiacker-panelMonth-title')
|
||||
.text(ccn_datetimepicker_displayCacheDateTime.getFullYear());
|
||||
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.day:
|
||||
var gottenYear = ccn_datetimepicker_displayCacheDateTime.getFullYear();
|
||||
var gottenMonth = ccn_datetimepicker_displayCacheDateTime.getMonth() + 1;
|
||||
var counter = -ccn_datetime_DayOfWeek(gottenYear, gottenMonth, 1);
|
||||
var days = ccn_datetime_monthDayCount[gottenMonth - 1] + ((gottenMonth == 2 && ccn_datetime_IsLeapYear(gottenYear)) ? 1 : 0);
|
||||
for(var i = 0; i < 6; i++) {
|
||||
for(var j = 0; j < 7; j++, counter++) {
|
||||
var ele = $('#ccn-datetimepiacker-panelDay-table > div:nth-child({0}) > div:nth-child({1})'.format(i + 2, j + 1));
|
||||
if (counter < 0 || counter >= days) ele.attr('data', '').html(' ');
|
||||
else ele.attr('data', counter + 1).text(counter + 1);
|
||||
|
||||
if (counter + 1 == ccn_datetimepicker_internalDateTime.getDate()) ele.attr('picked', 'true');
|
||||
else ele.attr('picked', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
$('#ccn-datetimepiacker-panelDay-title')
|
||||
.text('{0} - {1}'.format(
|
||||
ccn_datetimepicker_displayCacheDateTime.getFullYear(),
|
||||
ccn_i18n_UniversalGetMonth(ccn_datetimepicker_displayCacheDateTime.getMonth())
|
||||
));
|
||||
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.hour:
|
||||
var gottenHour = ccn_datetimepicker_displayCacheDateTime.getHours();
|
||||
var newX = Math.cos((3 - gottenHour) * Math.PI * 2 / 12);
|
||||
var newY = Math.sin((3 - gottenHour) * Math.PI * 2 / 12);
|
||||
var radius = ccn_datetimepicker_dialPlateRadius * (gottenHour < 12 ? ccn_datetimepicker_dialPlateHourOutterPercent : ccn_datetimepicker_dialPlateHourInnerPercent);
|
||||
newX = newX * radius + ccn_datetimepicker_dialPlateRadius;
|
||||
newY = (-newY * radius) + ccn_datetimepicker_dialPlateRadius;
|
||||
|
||||
$('#ccn-datetimepicker-panelHour > line')
|
||||
.attr('x2', newX)
|
||||
.attr('y2', newY);
|
||||
|
||||
$('#ccn-datetimepicker-panelHour > circle[type=symbol]')
|
||||
.attr('cx', newX)
|
||||
.attr('cy', newY);
|
||||
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.minute:
|
||||
var gottenMinute = ccn_datetimepicker_displayCacheDateTime.getMinutes();
|
||||
var newX = Math.cos((15 - gottenMinute) * Math.PI * 2 / 60);
|
||||
var newY = Math.sin((15 - gottenMinute) * Math.PI * 2 / 60);
|
||||
var radius = ccn_datetimepicker_dialPlateRadius * ccn_datetimepicker_dialPlateMinutePercent;
|
||||
newX = newX * radius + ccn_datetimepicker_dialPlateRadius;
|
||||
newY = (-newY * radius) + ccn_datetimepicker_dialPlateRadius;
|
||||
|
||||
$('#ccn-datetimepicker-panelMinute > line')
|
||||
.attr('x2', newX)
|
||||
.attr('y2', newY);
|
||||
|
||||
$('#ccn-datetimepicker-panelMinute > circle[type=symbol]')
|
||||
.attr('cx', newX)
|
||||
.attr('cy', newY);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_RefreshSvg() {
|
||||
// svg resize only can be called when the svg is showing.
|
||||
// so call this func in window resize event or
|
||||
// displaying svg.
|
||||
$('div.pickerContainer > svg').each(function() {
|
||||
ccn_datetimepicker_OnSvgResize($(this));
|
||||
});
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Str2TabType(strl) {
|
||||
switch(strl) {
|
||||
case 'year':
|
||||
return ccn_datetimepicker_tabType.year
|
||||
case 'month':
|
||||
return ccn_datetimepicker_tabType.month
|
||||
case 'day':
|
||||
return ccn_datetimepicker_tabType.day
|
||||
case 'hour':
|
||||
return ccn_datetimepicker_tabType.hour
|
||||
case 'minute':
|
||||
return ccn_datetimepicker_tabType.minute
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_GetUniformedXY(mouseOrTouchEvent, elements) {
|
||||
var offset = {
|
||||
left: elements.offset().left,
|
||||
top: elements.offset().top,
|
||||
halfWidth: elements.width() / 2,
|
||||
halfHeight: elements.height() / 2,
|
||||
halfSquareWidthHeight: Math.min(elements.width(), elements.height()) / 2
|
||||
}
|
||||
if(typeof(mouseOrTouchEvent.pageX) != 'undefined' && typeof(mouseOrTouchEvent.pageY) != 'undefined') {
|
||||
offset.realX = mouseOrTouchEvent.pageX;
|
||||
offset.realY = mouseOrTouchEvent.pageY;
|
||||
} else if(typeof(mouseOrTouchEvent.targetTouches) != 'undefined' && mouseOrTouchEvent.targetTouches.length >= 1) {
|
||||
offset.realX = mouseOrTouchEvent.targetTouches[0].pageX;
|
||||
offset.realY = mouseOrTouchEvent.targetTouches[0].pageY;
|
||||
} else {
|
||||
offset.realX = 0;
|
||||
offset.realY = 0;
|
||||
}
|
||||
|
||||
var _x = (offset.realX - offset.left - offset.halfWidth) / offset.halfSquareWidthHeight * ccn_datetimepicker_dialPlateRadius;
|
||||
var _y = -((offset.realY - offset.top - offset.halfHeight) / offset.halfSquareWidthHeight * ccn_datetimepicker_dialPlateRadius);
|
||||
|
||||
return {x: _x, y: _y};
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_PrevNextYear(isPrev) {
|
||||
ccn_datetimepicker_displayCacheDateTime.setFullYear(
|
||||
ccn_datetimepicker_displayCacheDateTime.getFullYear() + (isPrev ? -12 : 12));
|
||||
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_displayCacheDateTime);
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.year);
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_PrevNextMonth(isPrev) {
|
||||
ccn_datetimepicker_displayCacheDateTime.setFullYear(
|
||||
ccn_datetimepicker_displayCacheDateTime.getFullYear() + (isPrev ? -1 : 1));
|
||||
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_displayCacheDateTime);
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.month);
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_PrevNextDay(isPrev) {
|
||||
ccn_datetimepicker_displayCacheDateTime.setMonth(
|
||||
ccn_datetimepicker_displayCacheDateTime.getMonth() + (isPrev ? -1 : 1));
|
||||
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_displayCacheDateTime);
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.day);
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_ClickYear() {
|
||||
var ele = $(this);
|
||||
if (ele.attr('data') == '') return;
|
||||
|
||||
ccn_datetimepicker_internalDateTime.setFullYear(parseInt(ele.attr('data')));
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_internalDateTime);
|
||||
|
||||
if (ccn_datetimepicker_mode != ccn_datetimepicker_tabType.year)
|
||||
ccn_datetimepicker_SwitchTab(ccn_datetimepicker_tabType.month);
|
||||
else
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.year);
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_ClickMonth() {
|
||||
var ele = $(this);
|
||||
if (ele.attr('data') == '') return;
|
||||
|
||||
ccn_datetimepicker_internalDateTime.setFullYear(
|
||||
ccn_datetimepicker_displayCacheDateTime.getFullYear(),
|
||||
parseInt(ele.attr('data'))
|
||||
);
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_internalDateTime);
|
||||
|
||||
if (ccn_datetimepicker_mode != ccn_datetimepicker_tabType.month)
|
||||
ccn_datetimepicker_SwitchTab(ccn_datetimepicker_tabType.day);
|
||||
else
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.month);
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_ClickDay() {
|
||||
var ele = $(this);
|
||||
if (ele.attr('data') == '') return;
|
||||
|
||||
ccn_datetimepicker_internalDateTime.setFullYear(
|
||||
ccn_datetimepicker_displayCacheDateTime.getFullYear(),
|
||||
ccn_datetimepicker_displayCacheDateTime.getMonth(),
|
||||
parseInt(ele.attr('data'))
|
||||
);
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_internalDateTime);
|
||||
|
||||
if (ccn_datetimepicker_mode != ccn_datetimepicker_tabType.day)
|
||||
ccn_datetimepicker_SwitchTab(ccn_datetimepicker_tabType.hour);
|
||||
else
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.day);
|
||||
}
|
||||
|
||||
|
||||
function ccn_datetimepicker_StartDragHour() { ccn_datetimepicker_enableHourDrag = true; }
|
||||
function ccn_datetimepicker_DraggingHour(e) {
|
||||
if (!ccn_datetimepicker_enableHourDrag) return;
|
||||
|
||||
var offset = ccn_datetimepicker_GetUniformedXY(e, $('#ccn-datetimepicker-panelHour'));
|
||||
var x = offset.x;
|
||||
var y = offset.y;
|
||||
|
||||
var distance = Math.sqrt(x * x + y * y);
|
||||
var angle = Math.acos(x / distance);
|
||||
if (y < 0) angle = Math.PI * 2 - angle; // correct negative y axis angle
|
||||
|
||||
angle += (ccn_datetimepicker_dialPlateHourResolution / 2); // correct offset
|
||||
if (angle > Math.PI * 2)
|
||||
angle -= Math.PI * 2;
|
||||
|
||||
var number = Math.floor(angle / ccn_datetimepicker_dialPlateHourResolution);
|
||||
if (number >= 12) number = 11; // prevent unexpected result at the edge.
|
||||
number = (15 - number) % 12;
|
||||
if (distance < ccn_datetimepicker_dialPlateRadius * ccn_datetimepicker_dialPlateHourDistinguishPercent)
|
||||
number += 12;
|
||||
|
||||
// judge
|
||||
if (ccn_datetimepicker_displayCacheDateTime.getHours() != number) {
|
||||
ccn_datetimepicker_displayCacheDateTime.setHours(number);
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.hour);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
function ccn_datetimepicker_StopDragHour() {
|
||||
ccn_datetimepicker_enableHourDrag = false;
|
||||
|
||||
ccn_datetimepicker_internalDateTime.setHours(ccn_datetimepicker_displayCacheDateTime.getHours());
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_internalDateTime);
|
||||
|
||||
if (ccn_datetimepicker_mode != ccn_datetimepicker_tabType.hour)
|
||||
ccn_datetimepicker_SwitchTab(ccn_datetimepicker_tabType.minute);
|
||||
}
|
||||
|
||||
|
||||
function ccn_datetimepicker_StartDragMinute() { ccn_datetimepicker_enableMinuteDrag = true; }
|
||||
function ccn_datetimepicker_DraggingMinute(e) {
|
||||
if (!ccn_datetimepicker_enableMinuteDrag) return;
|
||||
|
||||
var offset = ccn_datetimepicker_GetUniformedXY(e, $('#ccn-datetimepicker-panelMinute'));
|
||||
var x = offset.x;
|
||||
var y = offset.y;
|
||||
|
||||
var distance = Math.sqrt(x * x + y * y);
|
||||
var angle = Math.acos(x / distance);
|
||||
if (y < 0) angle = Math.PI * 2 - angle; // correct negative y axis angle
|
||||
|
||||
angle += (ccn_datetimepicker_dialPlateMinuteResolution / 2); // correct offset
|
||||
if (angle > Math.PI * 2)
|
||||
angle -= Math.PI * 2;
|
||||
|
||||
var number = Math.floor(angle / ccn_datetimepicker_dialPlateMinuteResolution);
|
||||
if (number >= 60) number = 59; // prevent unexpected result at the edge.
|
||||
number = (75 - number) % 60;
|
||||
|
||||
// judge
|
||||
if (ccn_datetimepicker_displayCacheDateTime.getMinutes() != number) {
|
||||
ccn_datetimepicker_displayCacheDateTime.setMinutes(number);
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.minute);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
function ccn_datetimepicker_StopDragMinute() {
|
||||
ccn_datetimepicker_enableMinuteDrag = false;
|
||||
|
||||
ccn_datetimepicker_internalDateTime.setMinutes(ccn_datetimepicker_displayCacheDateTime.getMinutes());
|
||||
ccn_datetimepicker_ClampDateTime(ccn_datetimepicker_internalDateTime);
|
||||
|
||||
// no page need to go to
|
||||
// but we need refresh current page
|
||||
ccn_datetimepicker_RefreshDisplay(ccn_datetimepicker_tabType.minute);
|
||||
}
|
||||
|
||||
|
||||
function ccn_datetimepicker_ClampDateTime(dateObj) {
|
||||
if (dateObj < ccn_datetime_MIN_DATETIME)
|
||||
dateObj.setTime(ccn_datetime_MIN_DATETIME.getTime());
|
||||
if (dateObj >= ccn_datetime_MAX_DATETIME)
|
||||
dateObj.setTime(ccn_datetime_MAX_DATETIME.getTime());
|
||||
}
|
||||
|
||||
// ========================================================== universal function
|
||||
|
||||
function ccn_datetimepicker_Set(pickerIndex, dt, isUTC, mode) {
|
||||
var ele = $('[datetimepicker=' + pickerIndex + ']');
|
||||
while(true) {
|
||||
if (mode < ccn_datetimepicker_tabType.year) break;
|
||||
ele.attr('datetimepicker-year', isUTC ? dt.getUTCFullYear() : dt.getFullYear());
|
||||
if (mode < ccn_datetimepicker_tabType.month) break;
|
||||
ele.attr('datetimepicker-month', (isUTC ? dt.getUTCMonth() : dt.getMonth()) + 1);
|
||||
if (mode < ccn_datetimepicker_tabType.day) break;
|
||||
ele.attr('datetimepicker-day', isUTC ? dt.getUTCDate() : dt.getDate());
|
||||
if (mode < ccn_datetimepicker_tabType.hour) break;
|
||||
ele.attr('datetimepicker-hour', isUTC ? dt.getUTCHours() : dt.getHours());
|
||||
if (mode < ccn_datetimepicker_tabType.minute) break;
|
||||
ele.attr('datetimepicker-minute', isUTC ? dt.getUTCMinutes() : dt.getMinutes());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeof(ele.prop('funcs')) != 'undefined' && typeof(ele.prop('funcs').callback) == 'function')
|
||||
ele.prop('funcs').callback();
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Get(pickerIndex, isUTC) {
|
||||
var ele = $('[datetimepicker=' + pickerIndex + ']');
|
||||
year = ele.attr('datetimepicker-year');
|
||||
month = ele.attr('datetimepicker-month');
|
||||
day = ele.attr('datetimepicker-day');
|
||||
hour = ele.attr('datetimepicker-hour');
|
||||
minute = ele.attr('datetimepicker-minute');
|
||||
if (IsUndefinedOrEmpty(year)) year = ccn_datetime_MIN_YEAR;
|
||||
if (IsUndefinedOrEmpty(month)) month = 1;
|
||||
if (IsUndefinedOrEmpty(day)) day = 1;
|
||||
if (IsUndefinedOrEmpty(hour)) hour = 0;
|
||||
if (IsUndefinedOrEmpty(minute)) minute = 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);
|
||||
}
|
||||
@@ -76,3 +76,14 @@ function ccn_i18n_ApplyLanguage2Content(ctx) {
|
||||
$(this).html($.i18n.prop($(this).attr('i18n-name')));
|
||||
});
|
||||
}
|
||||
|
||||
// note: month is zero based
|
||||
function ccn_i18n_UniversalGetMonth(month) {
|
||||
return $.i18n.prop('ccn-i18n-universal-month-' + (month + 1));
|
||||
}
|
||||
|
||||
// note: day of week is zero based
|
||||
function ccn_i18n_UniversalGetDayOfWeek(dayOfWeek) {
|
||||
return $.i18n.prop('ccn-i18n-universal-week-' + (dayOfWeek + 1));
|
||||
}
|
||||
|
||||
@@ -30,9 +30,10 @@ $(document).ready(function() {
|
||||
// process calendar it self
|
||||
ccn_calendar_calendar_LoadCalendarBody();
|
||||
|
||||
// init datetimepicker
|
||||
// init datetimepicker and preset
|
||||
ccn_datetimepicker_Insert();
|
||||
ccn_datetimepicker_Init();
|
||||
var nowtime = new Date();
|
||||
ccn_datetimepicker_Set(1, nowtime, false, ccn_datetimepicker_tabType.month);
|
||||
|
||||
// bind tab control switcher and set current tab
|
||||
$("#tabcontrol-tab-1-1").click(function(){
|
||||
@@ -59,7 +60,14 @@ $(document).ready(function() {
|
||||
// bind event
|
||||
$('#ccn-calendar-collection-btnRefresh').click(ccn_calendar_collection_Refresh);
|
||||
|
||||
$('#ccn-calendar-calendar-btnJump').click(ccn_calendar_calendar_btnRefresh);
|
||||
$('#ccn-calendar-calendar-btnJump')
|
||||
.prop('funcs', {callback: ccn_calendar_calendar_btnRefresh})
|
||||
.click(function() {
|
||||
ccn_datetimepicker_Modal(
|
||||
ccn_datetimepicker_tabType.month,
|
||||
1,
|
||||
false);
|
||||
});
|
||||
$('#ccn-calendar-calendar-btnToday').click(ccn_calendar_calendar_btnToday);
|
||||
$('#ccn-calendar-calendar-btnAdd').click(ccn_calendar_calendar_btnAdd);
|
||||
});
|
||||
@@ -75,6 +83,7 @@ function ccn_calendar_calendar_Refresh() {
|
||||
var gottenDateTime = ccn_datetimepicker_Get(1, false);
|
||||
var gottenYear = gottenDateTime.getFullYear();
|
||||
var gottenMonth = gottenDateTime.getMonth() + 1;
|
||||
$('#ccn-calendar-calendar-textMonth').text('{0} - {1}'.format(gottenYear, ccn_i18n_UniversalGetMonth(gottenMonth - 1)));
|
||||
// don't need to set anything, because its default value is enough to use.
|
||||
|
||||
var gottenWeek = ccn_datetime_DayOfWeek(gottenYear, gottenMonth, 1);
|
||||
@@ -148,13 +157,13 @@ function ccn_calendar_calendar_Analyse() {
|
||||
color: deserializedDescription.color,
|
||||
isVisible: true,
|
||||
isLocked: typeof(ccn_calendar_owned_displayCache[item[0]]) != 'undefined',
|
||||
loopText: " ", // todo: finish this
|
||||
loopText: ccn_datetime_ResolveLoopRules4Text(item[8], item[5], item[7]),
|
||||
timezoneWarning: mytimezone != item[7],
|
||||
start: eventDateTime.toLocaleTimeString(),
|
||||
end: undefined // filled in follwing code
|
||||
}
|
||||
eventDateTime.setHours(23, 59, 0, 0);
|
||||
if (Math.floor(eventDateTime.getTime() / 60000) > it[1]) {
|
||||
if (it[1] <= Math.floor(eventDateTime.getTime() / 60000)) {
|
||||
exitFlag = true;
|
||||
eventDateTime.setTime(it[1] * 60000);
|
||||
}
|
||||
@@ -248,7 +257,7 @@ function ccn_calendar_calendar_btnRefresh() {
|
||||
|
||||
function ccn_calendar_calendar_btnToday() {
|
||||
var nowtime = new Date();
|
||||
ccn_datetimepicker_Set(1, nowtime, false);
|
||||
ccn_datetimepicker_Set(1, nowtime, false, ccn_datetimepicker_tabType.month);
|
||||
ccn_calendar_calendar_Refresh();
|
||||
ccn_calendar_calendar_Analyse();
|
||||
ccn_calendar_calendar_Render();
|
||||
@@ -20,7 +20,6 @@ $(document).ready(function() {
|
||||
|
||||
// init datetimepicker
|
||||
ccn_datetimepicker_Insert();
|
||||
ccn_datetimepicker_Init();
|
||||
|
||||
// apply i18n
|
||||
ccn_i18n_LoadLanguage();
|
||||
@@ -29,15 +28,23 @@ $(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);
|
||||
$('#ccn-event-btnStartDateTime')
|
||||
.prop('funcs', {callback: function() {
|
||||
ccn_event_UpdateDateTimePickerButton(1);
|
||||
ccn_event_RefreshLoopMonthType();
|
||||
}})
|
||||
.click(ccn_event_btnDateTimePicker);
|
||||
$('#ccn-event-btnEndDateTime')
|
||||
.prop('funcs', {callback: function() {ccn_event_UpdateDateTimePickerButton(2);}})
|
||||
.click(ccn_event_btnDateTimePicker);
|
||||
$('#ccn-event-btnLoopStopDateTime')
|
||||
.prop('funcs', {callback: function() {ccn_event_UpdateDateTimePickerButton(3);}})
|
||||
.click(ccn_event_btnDateTimePicker);
|
||||
|
||||
// init form
|
||||
ccn_event_Init();
|
||||
@@ -61,9 +68,6 @@ function ccn_event_Init() {
|
||||
.attr('step', 1)
|
||||
.val(1);
|
||||
|
||||
// now, init 3 datetimepicker
|
||||
//ccn_datetimepicker_Init();
|
||||
|
||||
// in there, we need get uuid from meta
|
||||
var uuid = $('meta[name=uuid]').attr('content');
|
||||
if (uuid != "")
|
||||
@@ -243,6 +247,26 @@ function ccn_event_RefreshLoopMonthType() {
|
||||
$('#ccn-event-loopMonth-textD').text($.i18n.prop('ccn-i18n-event-loopWeek-optionD').format(data[4], data[5] + 1));
|
||||
}
|
||||
|
||||
function ccn_event_UpdateDateTimePickerButton(index) {
|
||||
switch(index) {
|
||||
case 1:
|
||||
$('#ccn-event-btnStartDateTime-text').text(
|
||||
ccn_datetimepicker_Get(1, false).toLocaleString()
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
$('#ccn-event-btnEndDateTime-text').text(
|
||||
ccn_datetimepicker_Get(2, false).toLocaleString()
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
$('#ccn-event-btnLoopStopDateTime-text').text(
|
||||
ccn_datetimepicker_Get(3, false).toLocaleDateString()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// return undefined to indicate an error
|
||||
// or
|
||||
// [belongTo, title, description, eventDateTimeStart, eventDateTimeEnd, timezoneOffset, loopRules]
|
||||
@@ -366,6 +390,20 @@ function ccn_event_btnFullDay() {
|
||||
ccn_datetimepicker_Set(2, datetime, false);
|
||||
}
|
||||
|
||||
function ccn_event_btnDateTimePicker() {
|
||||
switch(parseInt($(this).attr('datetimepicker'))) {
|
||||
case 1:
|
||||
ccn_datetimepicker_Modal(ccn_datetimepicker_tabType.minute, 1, false);
|
||||
break;
|
||||
case 2:
|
||||
ccn_datetimepicker_Modal(ccn_datetimepicker_tabType.minute, 2, false);
|
||||
break;
|
||||
case 3:
|
||||
ccn_datetimepicker_Modal(ccn_datetimepicker_tabType.day, 3, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function ccn_event_btnCancel() {
|
||||
window.location.href = '/web/calendar';
|
||||
}
|
||||
@@ -1,74 +1,74 @@
|
||||
<div id="ccn-datetimepicker-modal" class="modal is-active" style="float: left; position: fixed; top: 0; bottom: 0; left: 0; right: 0;">
|
||||
<div id="ccn-datetimepicker-modal" class="modal" style="float: left; position: fixed; top: 0; bottom: 0; left: 0; right: 0;">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card" style="height: 70%;">
|
||||
<header class="modal-card-head pickerHeader">
|
||||
<div><small i18n-name="ccn-i18n-universal-text-year"></small><span id="ccn-datetimepicker-datetime-year"> </span></div>
|
||||
<div><small i18n-name="ccn-i18n-universal-text-month"></small><span id="ccn-datetimepicker-datetime-month"> </span></div>
|
||||
<div><small i18n-name="ccn-i18n-universal-text-day"></small><span id="ccn-datetimepicker-datetime-day"> </span></div>
|
||||
<div><small i18n-name="ccn-i18n-universal-text-hour"></small><span id="ccn-datetimepicker-datetime-hour"> </span></div>
|
||||
<div><small i18n-name="ccn-i18n-universal-text-minute"></small><span id="ccn-datetimepicker-datetime-minute"> </span></div>
|
||||
<div type="year"><small i18n-name="ccn-i18n-universal-text-year"></small><span id="ccn-datetimepicker-datetime-year"> </span></div>
|
||||
<div type="month"><small i18n-name="ccn-i18n-universal-text-month"></small><span id="ccn-datetimepicker-datetime-month"> </span></div>
|
||||
<div type="day"><small i18n-name="ccn-i18n-universal-text-day"></small><span id="ccn-datetimepicker-datetime-day"> </span></div>
|
||||
<div type="hour"><small i18n-name="ccn-i18n-universal-text-hour"></small><span id="ccn-datetimepicker-datetime-hour"> </span></div>
|
||||
<div type="minute"><small i18n-name="ccn-i18n-universal-text-minute"></small><span id="ccn-datetimepicker-datetime-minute"> </span></div>
|
||||
</header>
|
||||
<div class="modal-card-body pickerContainer">
|
||||
<div id="ccn-datetimepicker-panel-year">
|
||||
<div id="ccn-datetimepicker-panelYear">
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div class="level-item control">
|
||||
<a class="button">
|
||||
<a id="ccn-datetimepiacker-panelYear-prevBtn" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-left"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item">this is a title</div>
|
||||
<div id="ccn-datetimepiacker-panelYear-title" class="level-item"></div>
|
||||
<div class="level-right">
|
||||
<div class="level-item control">
|
||||
<a class="button">
|
||||
<a id="ccn-datetimepiacker-panelYear-nextBtn" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-right"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="perfectTable">
|
||||
<div id="ccn-datetimepiacker-panelYear-table" class="perfectTable">
|
||||
<div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div>abc</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ccn-datetimepicker-panel-month">
|
||||
<div id="ccn-datetimepicker-panelMonth">
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div class="level-item control">
|
||||
<a class="button">
|
||||
<a id="ccn-datetimepiacker-panelMonth-prevBtn" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-left"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item">this is a title</div>
|
||||
<div id="ccn-datetimepiacker-panelMonth-title" class="level-item"></div>
|
||||
<div class="level-right">
|
||||
<div class="level-item control">
|
||||
<a class="button">
|
||||
<a id="ccn-datetimepiacker-panelMonth-nextBtn" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-right"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="perfectTable">
|
||||
<div id="ccn-datetimepiacker-panelMonth-table" class="perfectTable">
|
||||
<div>
|
||||
<div i18n-name="ccn-i18n-universal-month-1"></div>
|
||||
<div i18n-name="ccn-i18n-universal-month-2"></div>
|
||||
@@ -89,26 +89,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ccn-datetimepicker-panel-day">
|
||||
<div id="ccn-datetimepicker-panelDay">
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div class="level-item control">
|
||||
<a class="button">
|
||||
<a id="ccn-datetimepiacker-panelDay-prevBtn" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-left"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item">this is a title</div>
|
||||
<div id="ccn-datetimepiacker-panelDay-title" class="level-item"></div>
|
||||
<div class="level-right">
|
||||
<div class="level-item control">
|
||||
<a class="button">
|
||||
<a id="ccn-datetimepiacker-panelDay-nextBtn" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-right"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="perfectTable">
|
||||
<div id="ccn-datetimepiacker-panelDay-table" class="perfectTable">
|
||||
<div>
|
||||
<div i18n-name="ccn-i18n-universal-week-1"></div>
|
||||
<div i18n-name="ccn-i18n-universal-week-2"></div>
|
||||
@@ -138,7 +138,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svg id="ccn-datetimepicker-panel-hour" xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMidYMid" viewBox="0 0 200 200">
|
||||
<svg id="ccn-datetimepicker-panelHour" xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMidYMid" viewBox="0 0 200 200">
|
||||
<circle cx="100.000000" cy="100.000000" r="100.000000" type="background"></circle>
|
||||
<line x1="100" y1="100" x2="100.000000" y2="20.000000"></line>
|
||||
<circle cx="100.000000" cy="20.000000" r="1em" type="symbol"></circle>
|
||||
@@ -168,7 +168,7 @@
|
||||
<text x="48.038476" y="70.000000">22</text>
|
||||
<text x="70.000000" y="48.038476">23</text>
|
||||
</svg>
|
||||
<svg id="ccn-datetimepicker-panel-minute" xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMidYMid" viewBox="0 0 200 200">
|
||||
<svg id="ccn-datetimepicker-panelMinute" xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMidYMid" viewBox="0 0 200 200">
|
||||
<circle cx="100.000000" cy="100.000000" r="100.000000" type="background"></circle>
|
||||
<line x1="100" y1="100" x2="100.000000" y2="20.000000"></line>
|
||||
<circle cx="100.000000" cy="20.000000" r="1em" type="symbol"></circle>
|
||||
@@ -15,6 +15,9 @@
|
||||
<p class="level-item"><b>{{>title}}</b></p>
|
||||
<p class="level-item">{{>description}}</p>
|
||||
<p class="level-item"><span>{{>start}}</span>-<span>{{>end}}</span></p>
|
||||
{{if loopText != ""}}
|
||||
<p><span class="icon is-small"><i class="fas fa-retweet"></i></span><span>{{>loopText}}</span></p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="schedule-event-icon">
|
||||
{{if isLocked}}
|
||||
@@ -22,6 +22,6 @@
|
||||
</div>
|
||||
|
||||
<div id="ccn-tokenItem-btnLogout-{{:uuid}}" uuid="{{:uuid}}" class="token-item-icon control">
|
||||
<a class="button"><span class="icon is-small"><i class="fas fa-sign-out"></i></span></a>
|
||||
<a class="button"><span class="icon is-small"><i class="fas fa-sign-out-alt"></i></span></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -52,38 +52,36 @@
|
||||
</div>
|
||||
|
||||
<div id="tabcontrol-panel-1-1" class="container tabcontrol-panel-1" style="margin-top: 20px;">
|
||||
<nav class="level">
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnPrevMonth" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-left"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="level-item">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-year" type="number">
|
||||
</div>
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-month" type="number">
|
||||
</div>
|
||||
<div class="control">
|
||||
<a id="ccn-calendar-calendar-btnJump" i18n-name="ccn-i18n-calendar-calendar-jump" class="button is-info"></a>
|
||||
</div>
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnPrevMonth" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-left"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnJump" class="button" datetimepicker="1">
|
||||
<span id="ccn-calendar-calendar-textMonth"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="level-right">
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnNextMonth" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-right"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="level is-mobile">
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnToday" i18n-name="ccn-i18n-calendar-calendar-today" class="button is-info"></a>
|
||||
</div>
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnAdd" i18n-name="ccn-i18n-calendar-calendar-add" class="button is-primary"></a>
|
||||
</div>
|
||||
<div class="level-item control">
|
||||
<a id="ccn-calendar-calendar-btnNextMonth" class="button">
|
||||
<span class="icon is-small"><i class="fas fa-chevron-circle-right"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="ccn-calendar-calendarBody" class="card" style="padding: 1.25rem; display: flex; flex-flow: column;">
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -73,38 +73,9 @@
|
||||
<section class="section">
|
||||
|
||||
<h2 class="subtitle" i18n-name="ccn-i18n-event-startDateTime"></h2>
|
||||
<div class="control-list">
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-year"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-year" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-month"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-month" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-day"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-day" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-hour"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-hour" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-minute"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="1" class="input datetimepicker-minute" type="number">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a id="ccn-event-btnStartDateTime" class="button" datetimepicker="1">
|
||||
<span id="ccn-event-btnStartDateTime-text"></span>
|
||||
</a>
|
||||
|
||||
<h2 class="subtitle" i18n-name="ccn-i18n-event-endDateTime"></h2>
|
||||
<div class="control-list">
|
||||
@@ -115,38 +86,9 @@
|
||||
<a id="ccn-event-btnFullDay" class="button is-link" i18n-name="ccn-i18n-event-btnFullDay"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-list">
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-year"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="2" class="input datetimepicker-year" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-month"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="2" class="input datetimepicker-month" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-day"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="2" class="input datetimepicker-day" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-hour"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="2" class="input datetimepicker-hour" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-minute"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="2" class="input datetimepicker-minute" type="number">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a id="ccn-event-btnEndDateTime" class="button" datetimepicker="2">
|
||||
<span id="ccn-event-btnEndDateTime-text"></span>
|
||||
</a>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -279,28 +221,9 @@
|
||||
</label>
|
||||
</div>
|
||||
<div id="ccn-event-boxLoopStopDateTime">
|
||||
<div class="field">
|
||||
<div class="control-list">
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-year"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="3" class="input datetimepicker-year" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-month"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="3" class="input datetimepicker-month" type="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" i18n-name="ccn-i18n-universal-text-day"></label>
|
||||
<div class="control">
|
||||
<input datetimepicker="3" class="input datetimepicker-day" type="number">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a id="ccn-event-btnLoopStopDateTime" class="button" datetimepicker="3">
|
||||
<span id="ccn-event-btnLoopStopDateTime-text"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="ccn-event-boxLoopStopTimes">
|
||||
<div class="field">
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title id="ccn-pageName"></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<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>
|
||||
@@ -1,54 +0,0 @@
|
||||
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()
|
||||
@@ -1,7 +0,0 @@
|
||||
import json
|
||||
|
||||
CustomConfig = None
|
||||
|
||||
# read cfg
|
||||
with open('config.cfg', 'r', encoding='utf-8') as f:
|
||||
CustomConfig = json.load(f)
|
||||
@@ -1,155 +0,0 @@
|
||||
var ccn_datetimepicker_tabType = {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 2,
|
||||
hour: 3,
|
||||
minute: 4
|
||||
};
|
||||
var ccn_datetimepicker_currentTab = undefined;
|
||||
|
||||
var ccn_datetimepicker_mode = undefined;
|
||||
var ccn_datetimepicker_pickerIndex = undefined;
|
||||
var ccn_datetimepicker_isUTC = undefined;
|
||||
var ccn_datetimepicker_callbackFunc = undefined;
|
||||
|
||||
var internalDateTime = new Date();
|
||||
|
||||
// ========================================= export func
|
||||
|
||||
function ccn_datetimepicker_Insert() {
|
||||
$('body').append(ccn_template_datetimepicker.render());
|
||||
|
||||
// bind event and trigge once
|
||||
$(window).resize(function (){
|
||||
$('div.pickerContainer > svg').each(function (){
|
||||
ccn_datetimepicker_OnSvgResize($(this));
|
||||
});
|
||||
}).resize();
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Modal(mode, pickerIndex, isUTC, callbackFunc) {
|
||||
ccn_datetimepicker_mode = mode;
|
||||
ccn_datetimepicker_pickerIndex = pickerIndex;
|
||||
ccn_datetimepicker_isUTC = isUTC;
|
||||
ccn_datetimepicker_callbackFunc = callbackFunc;
|
||||
|
||||
ccn_datetimepicker_SwitchTab(mode);
|
||||
|
||||
$('#ccn-datetimepicker-modal').show();
|
||||
}
|
||||
|
||||
// ========================================= internal func
|
||||
|
||||
function ccn_datetimepicker_OnSvgResize(ele) {
|
||||
var scale = 200 / Math.min(ele.width(), ele.height());
|
||||
ele.css('font-size', scale + 'em');
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_SwitchTab(newTab) {
|
||||
$('div.pickerContainer > svg').hide();
|
||||
switch(newTab) {
|
||||
case ccn_datetimepicker_tabType.year:
|
||||
$('#ccn-datetimepicker-panel-year').show();
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.month:
|
||||
$('#ccn-datetimepicker-panel-month').show();
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.day:
|
||||
$('#ccn-datetimepicker-panel-day').show();
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.hour:
|
||||
ccn_datetimepicker_OnSvgResize($('#ccn-datetimepicker-panel-hour').show());
|
||||
break;
|
||||
case ccn_datetimepicker_tabType.minute:
|
||||
ccn_datetimepicker_OnSvgResize($('#ccn-datetimepicker-panel-minute').show());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_RefreshDisplayDateTime() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
function ccn_datetimepicker_Init() {
|
||||
var nowtime = new Date();
|
||||
|
||||
$('.datetimepicker-year').attr('min', ccn_datetime_MIN_YEAR)
|
||||
.attr('max', ccn_datetime_MAX_YEAR)
|
||||
.attr('step', 1)
|
||||
.val(nowtime.getFullYear())
|
||||
.bind('input propertychange', ccn_datetimepicker_Sync);
|
||||
|
||||
$('.datetimepicker-month').attr('min', 1)
|
||||
.attr('max', 12)
|
||||
.attr('step', 1)
|
||||
.val(nowtime.getMonth() + 1)
|
||||
.bind('input propertychange', ccn_datetimepicker_Sync);
|
||||
|
||||
$('.datetimepicker-day').attr('min', 1)
|
||||
.attr('step', 1)
|
||||
.each(function(){
|
||||
ccn_datetimepicker_SyncEx($(this).attr("datetimepicker"));
|
||||
})
|
||||
.val(nowtime.getDate());
|
||||
|
||||
$('.datetimepicker-hour').attr('min', 0)
|
||||
.attr('max', 23)
|
||||
.attr('step', 1)
|
||||
.val(nowtime.getHours());
|
||||
|
||||
$('.datetimepicker-minute').attr('min', 0)
|
||||
.attr('max', 59)
|
||||
.attr('step', 1)
|
||||
.val(nowtime.getMinutes());
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Sync() {
|
||||
var pickerIndex = $(this).attr("datetimepicker");
|
||||
ccn_datetimepicker_SyncEx(pickerIndex);
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_SyncEx(pickerIndex) {
|
||||
year = parseInt($('.datetimepicker-year[datetimepicker=' + pickerIndex + ']').val());
|
||||
month = parseInt($('.datetimepicker-month[datetimepicker=' + pickerIndex + ']').val());
|
||||
|
||||
dayDOM = $('.datetimepicker-day[datetimepicker=' + pickerIndex + ']');
|
||||
if (typeof(year) == 'undefined' || typeof(month) == 'undefined') {
|
||||
dayDOM.attr('max', 1)
|
||||
.val(1);
|
||||
} else {
|
||||
dayDOM.attr('max', ccn_datetime_monthDayCount[month - 1] + ((month == 2 && ccn_datetime_IsLeapYear(year) ? 1 : 0)))
|
||||
.val(1);
|
||||
}
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Set(pickerIndex, dt, isUTC, mode) {
|
||||
var ele = $('[datetimepicker=' + pickerIndex + ']');
|
||||
if (mode < ccn_datetimepicker_tabType.year) return;
|
||||
ele.attr('datetimepicker-year', isUTC ? dt.getUTCFullYear() : dt.getFullYear());
|
||||
if (mode < ccn_datetimepicker_tabType.month) return;
|
||||
ele.attr('datetimepicker-month', (isUTC ? dt.getUTCMonth() : dt.getMonth()) + 1);
|
||||
if (mode < ccn_datetimepicker_tabType.day) return;
|
||||
ele.attr('datetimepicker-day', isUTC ? dt.getUTCDate() : dt.getDate());
|
||||
if (mode < ccn_datetimepicker_tabType.hour) return;
|
||||
ele.attr('datetimepicker-hour', isUTC ? dt.getUTCHours() : dt.getHours());
|
||||
if (mode < ccn_datetimepicker_tabType.minute) return;
|
||||
ele.attr('datetimepicker-minute', isUTC ? dt.getUTCMinutes() : dt.getMinutes());
|
||||
}
|
||||
|
||||
function ccn_datetimepicker_Get(pickerIndex, isUTC) {
|
||||
var ele = $('[datetimepicker=' + pickerIndex + ']');
|
||||
year = ele.attr('datetimepicker-year');
|
||||
month = ele.attr('datetimepicker-month');
|
||||
day = ele.attr('datetimepicker-day');
|
||||
hour = ele.attr('datetimepicker-hour');
|
||||
minute = ele.attr('datetimepicker-minute');
|
||||
if (IsUndefinedOrEmpty(year)) year = ccn_datetime_MIN_YEAR;
|
||||
if (IsUndefinedOrEmpty(month)) month = 1;
|
||||
if (IsUndefinedOrEmpty(day)) day = 1;
|
||||
if (IsUndefinedOrEmpty(hour)) hour = 0;
|
||||
if (IsUndefinedOrEmpty(minute)) minute = 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);
|
||||
}
|
||||
Reference in New Issue
Block a user