Compare commits

..
9 Commits
Author SHA1 Message Date
ignace c27556b3b8 added artwork 2026-08-09 14:36:39 +02:00
ignace cafec256b5 more modest h1 2026-08-06 23:02:06 +02:00
ignace 897477bec9 8 second timeout on the show screen 2026-08-06 22:24:28 +02:00
ignace 73380b0c07 another deployment update 2026-07-26 12:44:50 +02:00
ignace d642e0e2b8 updated readme for gunicorn 2026-07-26 12:24:36 +02:00
ignace ab82ecd306 added familiar icon 2026-07-18 22:19:10 +02:00
ignace 882bf0d409 removed a few labels and unused files 2026-07-18 21:16:07 +02:00
ignace 4a5ebc726e more modest ux 2026-07-18 21:00:33 +02:00
ignace 399f539b4f rewritten after codex recommendations 2026-07-18 20:27:19 +02:00
83 changed files with 1666 additions and 16754 deletions
+5
View File
@@ -4,3 +4,8 @@ __pycache__/flower_vase.cpython-314.pyc
__pycache__/flowers.cpython-314.pyc
__pycache__/FlowerServices.cpython-314.pyc
__pycache__/Log.cpython-314.pyc
__pycache__/
tests/__pycache__/
.pytest_cache/
secrets.yaml
.DS_Store
+58 -30
View File
@@ -1,40 +1,68 @@
from Config import *
import pickle, os
import datetime
"""Small file-backed login throttle.
The legacy pickle file is deliberately not read: pickle is unsafe for mutable
runtime files. A JSON document is used instead and malformed files fail closed
to an empty recent-attempt list.
"""
from __future__ import annotations
import json
import os
import tempfile
from datetime import datetime, timedelta, timezone
from pathlib import Path
from Config import ACCESSFILE
from Log import Log
class Access():
access_list = []
def __init__(self):
five_minutes_ago = datetime.datetime.now() - datetime.timedelta(minutes=5)
new_list = []
if os.path.exists(ACCESSFILE):
with open(ACCESSFILE, 'rb') as pf:
self.access_list = pickle.load(pf)
for entry in self.access_list:
if entry['time'] > five_minutes_ago:
new_list.append(entry)
self.access_list = new_list
class Access:
limit = 3
window = timedelta(minutes=5)
def granted(self, ipaddress):
result = 0
for entry in self.access_list:
if entry['ip'] == ipaddress:
result += 1
def __init__(self, path: str = ACCESSFILE) -> None:
self.path = Path(path)
self.access_list = self._load()
if result<3:
def granted(self, ipaddress: str) -> bool:
failures = sum(entry["ip"] == ipaddress for entry in self.access_list)
if failures < self.limit:
return True
Log.info("Access denied for {}".format(ipaddress))
self.deny(ipaddress)
Log.info(f"Access denied for {ipaddress}")
return False
def deny(self, ipaddress: str) -> None:
self.access_list.append(
{"ip": str(ipaddress), "time": datetime.now(timezone.utc).isoformat()}
)
self._save()
def deny(self, ipaddress):
now = datetime.datetime.now()
self.access_list.append({'ip': ipaddress, 'time': now})
with open(ACCESSFILE, 'wb') as pf:
pickle.dump(self.access_list, pf, 2)
def _load(self) -> list[dict[str, str]]:
cutoff = datetime.now(timezone.utc) - self.window
try:
raw = json.loads(self.path.read_text(encoding="utf-8"))
recent = []
for entry in raw:
recorded = datetime.fromisoformat(entry["time"])
if recorded.tzinfo is None:
recorded = recorded.replace(tzinfo=timezone.utc)
if recorded > cutoff:
recent.append({"ip": str(entry["ip"]), "time": recorded.isoformat()})
return recent
except (OSError, ValueError, TypeError, KeyError):
return []
def _save(self) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary = tempfile.mkstemp(
prefix=f".{self.path.name}.", dir=str(self.path.parent), text=True
)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
json.dump(self.access_list, handle, separators=(",", ":"))
os.chmod(temporary, 0o600)
os.replace(temporary, self.path)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
+61 -8
View File
@@ -1,9 +1,62 @@
"""Runtime configuration for Flowers.
LOGFILE = '/tmp/wsgi_flowers.log'
DEBUG = 0
MAXTEXTLEN = 70
MINFIELDLEN = 3
INFO = 1
DONOTSETFILTER = 'DoNotSetFilterinCookie'
ACCESSFILE = '/tmp/wsgi_flower_accessfile'
URLPREFIX = '' #'/flowers'
Secrets are read from the git-ignored ``secrets.yaml`` file. Environment
variables take precedence, which is useful for container deployments.
"""
import os
import secrets
from pathlib import Path
import yaml
BASE_DIR = Path(__file__).resolve().parent
SECRETS_FILE = Path(
os.getenv("FLOWERS_SECRETS_FILE", str(BASE_DIR / "secrets.yaml"))
)
def _load_secrets(path: Path) -> dict:
try:
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
except FileNotFoundError:
return {}
except (OSError, yaml.YAMLError) as exc:
raise RuntimeError(f"Could not read Flowers secrets file: {path}") from exc
if not isinstance(data, dict):
raise RuntimeError(f"Flowers secrets file must contain a mapping: {path}")
return data
_SECRETS = _load_secrets(SECRETS_FILE)
_FLASK_SECRETS = _SECRETS.get("flask", {})
_DATABASE_SECRETS = _SECRETS.get("database", {})
if not isinstance(_FLASK_SECRETS, dict) or not isinstance(_DATABASE_SECRETS, dict):
raise RuntimeError("The flask and database secrets sections must be mappings")
LOGFILE = os.getenv("FLOWERS_LOG_FILE", "/tmp/wsgi_flowers.log")
DEBUG = os.getenv("FLOWERS_DEBUG", "0") == "1"
INFO = os.getenv("FLOWERS_INFO", "1") == "1"
MAXTEXTLEN = int(os.getenv("FLOWERS_MAX_TEXT_LENGTH", "70"))
MINFIELDLEN = int(os.getenv("FLOWERS_MIN_FIELD_LENGTH", "3"))
DONOTSETFILTER = "DoNotSetFilterinCookie"
ACCESSFILE = os.getenv("FLOWERS_ACCESS_FILE", "/tmp/wsgi_flower_accessfile")
URLPREFIX = os.getenv("FLOWERS_URL_PREFIX", "").rstrip("/")
DB_HOST = os.getenv("FLOWERS_DB_HOST", "localhost")
DB_PORT = int(os.getenv("FLOWERS_DB_PORT", "3306"))
DB_NAME = os.getenv("FLOWERS_DB_NAME", "Flowers")
DB_ADMIN_USER = os.getenv("FLOWERS_DB_ADMIN_USER", "flower")
DB_ADMIN_PASSWORD = os.getenv(
"FLOWERS_DB_ADMIN_PASSWORD", str(_DATABASE_SECRETS.get("admin_password", ""))
)
SESSION_MINUTES = int(os.getenv("FLOWERS_SESSION_MINUTES", "10"))
SECRET_KEY = (
os.getenv("FLOWERS_SECRET_KEY")
or str(_FLASK_SECRETS.get("secret_key", ""))
or secrets.token_hex(32)
)
COOKIE_SECURE = os.getenv("FLOWERS_COOKIE_SECURE", "0") == "1"
-79
View File
@@ -1,79 +0,0 @@
@app.route('/loadconfig' , methods=['GET'])
def loadconfig():
user=None
mode='text'
try:
user = request.args.get('userId')
mode = request.args.get('mode')
except HTTPException as err:
print("http error: {0}".format(err))
except:
print('some error', sys.exc_info()[0])
cfg=Dashboard(userId=user)
resp = make_response(json.dumps({'plugs':cfg.plugs.listAsText()}))
resp.headers['Content-type'] = 'application/json; charset=utf-8'
return resp
@app.route('/loadPlugin' , methods=['GET'])
def loadPlugin():
try:
plname = request.args.get('plugin')
params = request.args.get('parameters')
ClassType = getattr(Plugin, plname)
p = ClassType(params)
resp = make_response(json.dumps({'plugin':p.asHtml()}))
except HTTPException as err:
print("http error: {0}".format(err))
resp = make_response(json.dumps({'plugin':err}))
except:
print('some error', sys.exc_info()[0])
resp = make_response(json.dumps({'plugin':'System error loading plugin'}))
resp.headers['Content-type'] = 'application/json; charset=utf-8'
return resp
@app.route('/saveplugs' , methods=['POST'])
def saveplugs():
user=request.form['userId']
mode=request.form['mode']
plugs=request.form['plugs']
cfg=Dashboard(userId=user)
cfg.updatePlugs(plugs)
cfg.save()
resp = make_response(json.dumps({'plugs':cfg.plugs.listAsText()}))
resp.headers['Content-type'] = 'application/json; charset=utf-8'
return resp
@app.route('/json', methods=['GET'])
def myjson():
user=request.form['userId']
resp = make_response(json.dumps({'plugs':'user'}))
resp.headers['Content-type'] = 'application/json; charset=utf-8'
return resp
@app.route('/upload', methods=['GET'])
def upload():
resp = make_response('function upload')
resp.headers['Content-type'] = 'text/plain; charset=utf-8'
if request.method == 'GET':
try:
f = request.args.get('user')
print(f)
except HTTPException as err:
print("http error: {0}".format(err))
except:
print('some error', sys.exc_info()[0])
return resp
@app.route('/echo' , methods=['GET'])
def echo():
text=None
try:
text = request.args.get('text')
except HTTPException as err:
print("http error: {0}".format(err))
except:
print('some error', sys.exc_info()[0])
resp = make_response(text)
resp.headers['Content-type'] = 'text/plain; charset=utf-8'
return resp
+293 -235
View File
@@ -1,269 +1,327 @@
from fernet import Fernet
from Log import Log
from Config import *
"""Database and encryption services for the legacy Flowers data format.
The public ``Flower`` API intentionally remains compatible with the original
application. Existing tables and Fernet ciphertext require no migration.
"""
from __future__ import annotations
import re
from contextlib import contextmanager
from typing import Callable, Iterator
import pymysql as mdb
from fernet import Fernet
#encryption stuff
SECRET = b'XBxB603cX_mULEXxfavOg3FDc0Ox3gChwYEY-Uxd3tE='
from Config import (
DB_ADMIN_PASSWORD,
DB_ADMIN_USER,
DB_HOST,
DB_NAME,
DB_PORT,
MINFIELDLEN,
)
from Log import Log
class Flower():
_USERNAME = re.compile(r"^[A-Za-z0-9_]{1,31}$")
def __init__(self, user, pwd):
self.db = 'Flowers'
self.db_username = user + '_'
self.db_password = (pwd*10)[:43]+"="
class InvalidCredentials(ValueError):
"""The supplied username/password cannot address a Flowers vault."""
class StorageError(RuntimeError):
"""A database operation failed."""
def legacy_key(password: str) -> str:
"""Return the exact DB password/Fernet key used by historical releases."""
if not isinstance(password, str) or not password:
raise InvalidCredentials("A password is required")
key = (password * 10)[:43] + "="
try:
Fernet(key.encode("ascii"))
except (UnicodeEncodeError, ValueError) as exc:
raise InvalidCredentials(
"Password must use Base64-compatible characters"
) from exc
return key
def _identifier(username: str) -> str:
if not isinstance(username, str) or not _USERNAME.fullmatch(username):
raise InvalidCredentials(
"Username must contain only letters, numbers, and underscores"
)
return f"{username}_"
class Flower:
"""Read and write one user's existing Flowers table."""
def __init__(
self,
user: str,
pwd: str,
connection_factory: Callable[..., object] | None = None,
) -> None:
self.db = DB_NAME
self.db_username = _identifier(user)
self.db_password = legacy_key(pwd)
self.customer_table = self.db_username
self.fernet = Fernet(bytes(self.db_password, 'ascii'))
self._table = f"`{self.customer_table}`"
self.fernet = Fernet(self.db_password.encode("ascii"))
self._connect = connection_factory or mdb.connect
def encode(self, s):
if len(s)==0:
return ''
return self.fernet.encrypt(s.encode()).decode()
@contextmanager
def _connection(self) -> Iterator[object]:
connection = None
try:
connection = self._connect(
host=DB_HOST,
port=DB_PORT,
user=self.db_username,
passwd=self.db_password,
db=self.db,
charset="latin1",
)
yield connection
except mdb.Error as exc:
Log.debug(f"Database error: {exc}")
raise StorageError("The vault database is unavailable") from exc
finally:
if connection is not None:
connection.close()
def decode(self, s):
if len(s)==0:
return ''
return self.fernet.decrypt(s.encode()).decode()
def encode(self, value: str) -> str:
if not value:
return ""
return self.fernet.encrypt(value.encode("utf-8")).decode("ascii")
def add(self, organization, myname, myid, secret, deleted=0, timestamp=None):
''' POST a new flower
'''
if len(organization)>MINFIELDLEN and len(myid)>MINFIELDLEN and len(secret)>MINFIELDLEN:
sql = 'INSERT INTO {} (organization ,myID ,myName ,mySecret, deleted) VALUES ("{}", "{}", "{}", "{}", {})'.format(self.customer_table, organization, self.encode(myid), self.encode(myname), self.encode(secret), deleted)
if timestamp is not None:
sql = 'INSERT INTO {} (organization ,myID ,myName ,mySecret, deleted, dateCreated) VALUES ("{}", "{}", "{}", "{}", {}, "{}")'.format(self.customer_table, organization, self.encode(myid), self.encode(myname), self.encode(secret), deleted, timestamp)
result, data = self.do_db_commit(sql)
Log.debug(result)
if result==1:
return {'organization': organization, 'dateCreated': 'STORED', 'myID': myid, 'myName': myname,'mySecret': secret}
return {'organization': organization, 'dateCreated': 'FAILED', 'myID': myid, 'myName': myname,'mySecret': secret}
def decode(self, value: str) -> str:
if not value:
return ""
return self.fernet.decrypt(value.encode("ascii")).decode("utf-8")
def authenticate(self) -> bool:
try:
with self._connection() as connection:
with connection.cursor() as cursor:
cursor.execute(f"SELECT COUNT(*) FROM {self._table}")
cursor.fetchone()
return True
except (StorageError, ValueError):
return False
def all(self, isdeleted=0):
''' GET flowers list
'''
#sql = "SELECT organization, myID, myName, dateCreated FROM %s " % (self.customer_table)
sql = """
select uniqueflowers.organization, uniqueflowers.myID, uniqueflowers.dateCreated from {0} as uniqueflowers
inner join (
select organization, max(dateCreated) as lastCreated from {0} group by organization order by dateCreated DESC) allflowers
on allflowers.organization=uniqueflowers.organization and lastCreated=uniqueflowers.dateCreated where uniqueflowers.deleted={1}
""".format(self.customer_table, isdeleted)
result, data = self.do_db_allrows(sql)
#Log.debug(sql_result)
all_flowers=[]
if result==1:
for row in data:
all_flowers.append({'organization': str(row[0]), 'myID': self.decode(row[1]), 'dateCreated': str(row[2])})
return all_flowers
def one(self, organization, datetimestamp ):
''' flower for user, org and datetime
'''
result = -1
secret = ':-('
sql = "SELECT mySecret, myID, myName FROM %s where organization='%s' and dateCreated='%s' order by dateCreated DESC limit 1" % (self.customer_table, organization, datetimestamp)
result, data = self.do_db_1row(sql)
if result == 1:
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': self.decode(data[1]), 'myName': self.decode(data[2]),'mySecret': self.decode(data[0])}
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': '', 'myName': '','mySecret': '-- Not found --'}
def deactivate(self, organization, datetimestamp ):
''' flower for user, org and datetime
'''
result = -1
secret = ':-('
sql = "update {} set deleted=1 where organization='{}' and dateCreated='{}' ".format(self.customer_table, organization, datetimestamp)
result, data = self.do_db_commit(sql)
if result == 1:
secret = '- DELETED -'
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': '', 'myName': '','mySecret': secret}
def empty(self):
''' empty flower
'''
return {'organization': '', 'dateCreated': '', 'myID': '', 'myName': '','mySecret': ''}
def numberOfEntries(self):
''' GET login info, check if its valid
'''
sql = "SELECT count(*) FROM {};".format(self.customer_table)
result, data = self.do_db_1row(sql)
if result == 1:
return(data[0])
def numberOfEntries(self) -> int:
"""Compatibility method used by older clients and scripts."""
try:
with self._connection() as connection:
with connection.cursor() as cursor:
cursor.execute(f"SELECT COUNT(*) FROM {self._table}")
row = cursor.fetchone()
return int(row[0]) if row else 0
except StorageError:
return -1
def add(
self,
organization: str,
myname: str,
myid: str,
secret: str,
deleted: int = 0,
timestamp=None,
) -> dict:
item = self._item(organization, "FAILED", myid, myname, secret)
if not all(isinstance(v, str) for v in (organization, myname, myid, secret)):
return item
if not (
len(organization) > MINFIELDLEN
and len(myid) > MINFIELDLEN
and len(secret) > MINFIELDLEN
):
return item
def update_pwd(self, new_pwd):
columns = "organization, myID, myName, mySecret, deleted"
values = [
organization,
self.encode(myid),
self.encode(myname),
self.encode(secret),
int(bool(deleted)),
]
placeholders = "%s, %s, %s, %s, %s"
if timestamp is not None:
columns += ", dateCreated"
placeholders += ", %s"
values.append(timestamp)
long_newpwd = (new_pwd*10)[:43]+"="
self.new_fernet = Fernet(bytes(long_newpwd, 'ascii'))
sql = f"INSERT INTO {self._table} ({columns}) VALUES ({placeholders})"
self._execute_write(sql, values)
return self._item(organization, "STORED", myid, myname, secret)
def new_encode(s):
if len(s)==0:
return ''
return self.new_fernet.encrypt(s.encode()).decode()
def all(self, isdeleted: int = 0) -> list[dict]:
sql = f"""
SELECT latest.organization, latest.myID, latest.dateCreated
FROM {self._table} AS latest
INNER JOIN (
SELECT organization, MAX(dateCreated) AS lastCreated
FROM {self._table}
GROUP BY organization
) AS versions
ON versions.organization = latest.organization
AND versions.lastCreated = latest.dateCreated
WHERE latest.deleted = %s
ORDER BY latest.dateCreated DESC, latest.organization ASC
"""
with self._connection() as connection:
with connection.cursor() as cursor:
cursor.execute(sql, (int(bool(isdeleted)),))
rows = cursor.fetchall()
return [
{
"organization": str(row[0]),
"myID": self.decode(row[1]),
"dateCreated": str(row[2]),
}
for row in rows
]
end_result = "ERROR updating hushes\n"
sql = """
select myID, myName, mySecret, organization, dateCreated from {0}""".format(self.customer_table)
result, data = self.do_db_allrows(sql)
# Log.debug(sql_result)
if result == 1:
# convert all hushes
sql = ""
for row in data:
sql += "update {} set myID='{}', myName='{}', mySecret='{}' where organization='{}' and dateCreated='{}';\n".\
format(self.customer_table,
new_encode(self.decode(row[0])),
new_encode(self.decode(row[1])),
new_encode(self.decode(row[2])),row[3], row[4])
def one(self, organization: str, datetimestamp) -> dict:
sql = f"""
SELECT mySecret, myID, myName
FROM {self._table}
WHERE organization = %s AND dateCreated = %s
ORDER BY dateCreated DESC
LIMIT 1
"""
with self._connection() as connection:
with connection.cursor() as cursor:
cursor.execute(sql, (organization, datetimestamp))
row = cursor.fetchone()
if row:
return self._item(
organization,
str(datetimestamp),
self.decode(row[1]),
self.decode(row[2]),
self.decode(row[0]),
)
return self._item(organization, str(datetimestamp), "", "", "-- Not found --")
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "ERROR updating user-password\n"
# update the password of the user
sql = "SET PASSWORD FOR '{}'@'localhost' = PASSWORD('{}'); ".format(self.db_username, long_newpwd)
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "SUCCESSFULLY updated, now login again\n"
return end_result
def deactivate(self, organization: str, datetimestamp) -> dict:
sql = f"UPDATE {self._table} SET deleted = 1 WHERE organization = %s AND dateCreated = %s"
changed = self._execute_write(sql, (organization, datetimestamp))
message = "- DELETED -" if changed else "-- Not found --"
return self._item(organization, str(datetimestamp), "", "", message)
def empty(self) -> dict:
return self._item("", "", "", "", "")
def update_pwd(self, new_pwd: str) -> str:
"""Re-encrypt every value and update the legacy per-user DB password."""
new_db_password = legacy_key(new_pwd)
new_fernet = Fernet(new_db_password.encode("ascii"))
def do_db_commit(self, sql):
Log.debug('INSc: '+sql)
result = 0
data = ''
con = None
try:
con = mdb.connect(host='localhost', passwd=self.db_password, user=self.db_username, db=self.db);
cur = con.cursor()
if ";\n" in sql:
for s in sql.split(";\n"):
if len(s)>1:
cur.execute(s)
else:
cur.execute(sql)
cur.close()
con.commit()
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
def do_db_1row(self, sql):
Log.debug('1row sql: '+sql)
result = 0
con = None
with self._connection() as connection:
try:
con = mdb.connect(host='localhost', passwd=self.db_password, user=self.db_username, db=self.db);
cur = con.cursor()
cur.execute(sql)
data = cur.fetchone()
if data:
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
with connection.cursor() as cursor:
cursor.execute(
f"SELECT myID, myName, mySecret, organization, dateCreated FROM {self._table}"
)
rows = cursor.fetchall()
update = f"""
UPDATE {self._table}
SET myID = %s, myName = %s, mySecret = %s
WHERE organization = %s AND dateCreated = %s
"""
for row in rows:
encrypted = [
new_fernet.encrypt(self.decode(value).encode("utf-8")).decode("ascii")
if value else ""
for value in row[:3]
]
cursor.execute(update, (*encrypted, row[3], row[4]))
# This form is supported by the MariaDB versions used by
# historical Flowers installations.
cursor.execute("SET PASSWORD = PASSWORD(%s)", (new_db_password,))
connection.commit()
except Exception:
connection.rollback()
raise
except Exception as exc:
Log.debug(f"Password update failed: {exc}")
return "ERROR updating password"
return "SUCCESSFULLY updated, now login again"
def do_db_allrows(self, sql):
Log.debug('all rows sql: '+sql)
result = 0
con = None
def _execute_write(self, sql: str, parameters) -> int:
with self._connection() as connection:
try:
con = mdb.connect(host='localhost', passwd=self.db_password, user=self.db_username, db=self.db);
cur = con.cursor()
cur.execute(sql)
data = cur.fetchall()
if data:
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
with connection.cursor() as cursor:
changed = cursor.execute(sql, parameters)
connection.commit()
return int(changed)
except Exception:
connection.rollback()
raise
@staticmethod
def _item(organization, timestamp, myid, myname, secret) -> dict:
return {
"organization": organization,
"dateCreated": timestamp,
"myID": myid,
"myName": myname,
"mySecret": secret,
}
class SuperFlower(Flower):
"""Provision a legacy per-user table and database account."""
def __init__(self, customer, pwd):
super().__init__(customer, pwd)
self.customer_name = customer + '_'
self.customer_pwd = (pwd*10)[:43]+"="
self.db_username = 'flower'
self.db_password = '608f0b988db4a96066af7dd8870de96c'
def createNewTable(self):
# create new table and user
sql = """
CREATE TABLE `{}` (
`organization` varchar(80) NOT NULL,
`myID` varchar(255) NOT NULL,
`myName` varchar(255) NOT NULL,
`mySecret` varchar(255) NOT NULL,
`dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` int(11) DEFAULT '0',
PRIMARY KEY (`organization`,`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;""".format(self.customer_table)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """create user {}@localhost identified by '{}';""".format(self.customer_name, self.customer_pwd)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """grant select,update,insert on Flowers.{} to {}@localhost;""".format(self.customer_table, self.customer_name)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
self.flush_privs()
def __init__(self, customer: str, pwd: str, connection_factory=None) -> None:
super().__init__(customer, pwd, connection_factory)
self.customer_name = self.customer_table
self.customer_pwd = self.db_password
self.db_username = DB_ADMIN_USER
self.db_password = DB_ADMIN_PASSWORD
def createNewTable(self) -> int:
statements = [
f"""CREATE TABLE {self._table} (
organization varchar(80) NOT NULL,
myID varchar(255) NOT NULL,
myName varchar(255) NOT NULL,
mySecret varchar(255) NOT NULL,
dateCreated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted int(11) DEFAULT 0,
PRIMARY KEY (organization, dateCreated)
) ENGINE=InnoDB DEFAULT CHARSET=latin1""",
f"CREATE USER '{self.customer_name}'@'localhost' IDENTIFIED BY %s",
f"GRANT SELECT, UPDATE, INSERT ON `{self.db}`.{self._table} TO '{self.customer_name}'@'localhost'",
]
try:
with self._connection() as connection:
with connection.cursor() as cursor:
cursor.execute(statements[0])
cursor.execute(statements[1], (self.customer_pwd,))
cursor.execute(statements[2])
connection.commit()
return 1
def flush_privs(self):
sql = """flush privileges;"""
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
except StorageError as exc:
Log.info(f"Could not create vault: {exc}")
return 0
def flush_privs(self) -> int:
try:
self._execute_write("FLUSH PRIVILEGES", ())
return 1
except StorageError:
return 0
if __name__ == '__main__':
print("this is a library, sorry dude")
if __name__ == "__main__":
print("This module is a library.")
+22 -15
View File
@@ -1,20 +1,27 @@
from sys import stderr
from Config import *
"""Compatibility logging facade."""
import logging
from logging.handlers import RotatingFileHandler
from Config import DEBUG, INFO, LOGFILE
_logger = logging.getLogger("flowers")
if not _logger.handlers and (DEBUG or INFO):
try:
handler = RotatingFileHandler(LOGFILE, maxBytes=1_000_000, backupCount=2)
handler.setFormatter(logging.Formatter("%(asctime)s flowers %(levelname)s %(message)s"))
_logger.addHandler(handler)
_logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
except OSError:
_logger.addHandler(logging.NullHandler())
class Log:
@staticmethod
def info(someString):
if INFO:
f = open(LOGFILE, "a")
f.write( "flower: %s \n" % someString )
f.close()
if DEBUG:
print(someString)
def info(message) -> None:
_logger.info("%s", message)
@staticmethod
def debug(someString):
if DEBUG:
f = open(LOGFILE, "a")
f.write( "flower: %s \n" % someString )
f.close()
def debug(message) -> None:
_logger.debug("%s", message)
+261 -164
View File
@@ -1,230 +1,327 @@
# Flowers
Flowers is a small, self-hosted credential vault built with Flask and MariaDB/MySQL. It stores a label, login name, login ID, and secret for each entry, and provides both a server-rendered browser interface and a form-encoded JSON endpoint.
Flowers is a small self-hosted credential vault built with Flask and MariaDB/MySQL. This version rewrites the legacy server and browser UI while intentionally preserving its on-disk data contract.
> [!WARNING]
> This is legacy, personal-use software and is **not production-ready in its current form**. The repository contains hard-coded application/database secrets and committed data exports (`db.sql` and `pins.csv`). The application also builds SQL with string interpolation, has no CSRF protection, and relies on an unusual password-derived encryption scheme. Treat every committed dump as sensitive, rotate any real credentials it contains, and review the [security notes](#security-notes) before exposing the service to a network.
## Existing database compatibility
## What it does
No data migration is required. The rewritten application continues to use:
- Creates a separate database user and table for each Flowers user.
- Encrypts the login name, login ID, and secret with Fernet before storing them.
- Lists the most recent active record for each organization.
- Supports searching, viewing, adding, and soft-deleting records.
- Keeps older records when a new version of an organization is added.
- Locks an IP address out after three failed logins within five minutes.
- Exposes the same core operations through `POST /app` for a companion client.
- the `Flowers` database;
- one database account and table named `<username>_` per vault;
- the existing `organization`, `myID`, `myName`, `mySecret`, `dateCreated`, and `deleted` columns;
- the historical password transformation `(password * 10)[:43] + "="` for both the database password and Fernet key;
- Fernet ciphertext in the three protected columns;
- append-only edits, where the newest timestamp is the visible version; and
- `deleted = 1` for deactivation.
The included `client/Daisy.py` is only a Kivy “Hello world” scaffold; it is not a working Flowers client.
The old form-encoded `POST /app` endpoint is also retained. Its `list` and `flower` properties remain JSON strings inside the outer JSON response for existing client compatibility.
## How the application is organized
The legacy key derivation is preserved only because changing it would make existing data unreadable. It is not a modern password KDF. A future KDF upgrade requires an explicit, tested data migration rather than an in-place code change.
```text
flowers.py Flask application factory
flower_vase.py Browser routes and the form-based JSON endpoint
FlowerServices.py Fernet encryption and MariaDB/MySQL operations
AccessControl.py File-backed failed-login throttling
Config.py Paths, limits, logging, and URL prefix
templates/ Jinja browser interface
static/ CSS, JavaScript, fonts, and images
flowers.wsgi Legacy mod_wsgi entry point (currently stale)
apache.conf Legacy Apache/mod_wsgi example
db.sql, pins.csv Historical data exports; sensitive, not seed data
Services.py,
SecretServices.py Older AES-based implementations retained for migration
```
## What changed
### Data model
Each application username maps to:
- a database account named `<username>_`;
- a table named `<username>_` in the `Flowers` database; and
- a database password/Fernet key derived by repeating the application password and truncating it to 43 characters, then appending `=`.
Each table contains:
| Column | Purpose |
| --- | --- |
| `organization` | Plain-text label used to group versions of an entry |
| `myID` | Fernet-encrypted login ID |
| `myName` | Fernet-encrypted display/login name |
| `mySecret` | Fernet-encrypted secret |
| `dateCreated` | Creation time and version identifier |
| `deleted` | Soft-delete flag |
The organization and timestamp are not encrypted. “Editing” an item inserts a new row, so the latest timestamp becomes the visible version. Deactivation marks a selected row as deleted.
- All stored values are passed to MariaDB as query parameters.
- Usernames are validated before being used as table/account identifiers.
- Database and runtime settings can be supplied through environment variables.
- Browser forms have CSRF protection.
- Database credentials are stored in server memory behind an opaque session token, not in Flask's signed browser cookie.
- Browser sessions expire after ten minutes of inactivity.
- Failed-login state is JSON rather than unsafe pickle data.
- The browser interface is responsive and no longer depends on jQuery, Pure CSS, or remote assets.
- The Flask application factory and WSGI entry point both work.
- Compatibility tests cover legacy encryption, reads, writes, listing, and the old API format.
## Requirements
- Python 3
- MariaDB or MySQL running on `localhost`
- Python packages: Flask, Waitress, `fernet`, PyMySQL, and pytz
- Apache with `mod_proxy` only if using the optional reverse-proxy setup
No dependency lock file is included, so the project does not currently define tested package or Python version ranges.
## Local setup
### 1. Create a virtual environment
- Python 3.10 or newer
- MariaDB or MySQL on the configured host
- the packages in `requirements.txt`
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install flask waitress fernet pymysql pytz
pip install -r requirements.txt
```
The code imports `Fernet` from the third-party `fernet` package, not from `cryptography.fernet`.
## Configuration
### 2. Bootstrap MariaDB/MySQL
Local secrets are loaded from the git-ignored `secrets.yaml` file:
The application expects a database named `Flowers` and a provisioning account named `flower`. With the repository's current defaults, an administrator can create them with:
```yaml
flask:
secret_key: "a-long-random-value"
database:
admin_password: "the-provisioning-account-password"
```
Use `secrets.example.yaml` as a template. `FLOWERS_SECRETS_FILE` can point to a different file. Secret environment variables take precedence over YAML, which is useful for containers and managed deployments.
The other settings remain environment-based:
| Environment variable | Default |
| --- | --- |
| `FLOWERS_SECRETS_FILE` | `secrets.yaml` beside the application |
| `FLOWERS_SECRET_KEY` | overrides `flask.secret_key` from YAML |
| `FLOWERS_DB_HOST` | `localhost` |
| `FLOWERS_DB_PORT` | `3306` |
| `FLOWERS_DB_NAME` | `Flowers` |
| `FLOWERS_DB_ADMIN_USER` | `flower` |
| `FLOWERS_DB_ADMIN_PASSWORD` | overrides `database.admin_password` from YAML |
| `FLOWERS_URL_PREFIX` | empty |
| `FLOWERS_SESSION_MINUTES` | `10` |
| `FLOWERS_COOKIE_SECURE` | `0`; set to `1` behind HTTPS |
| `FLOWERS_ACCESS_FILE` | `/tmp/wsgi_flower_accessfile` |
| `FLOWERS_LOG_FILE` | `/tmp/wsgi_flowers.log` |
The provisioning account is needed only by the web-based `/create` flow. Existing vault reads and writes connect with their existing per-user database accounts.
## Database bootstrap for a new installation
An existing Flowers database should be left untouched. For a new installation, create the database and provisioning user, replacing the example password:
```sql
CREATE DATABASE Flowers;
CREATE USER 'flower'@'localhost'
IDENTIFIED BY '608f0b988db4a96066af7dd8870de96c';
CREATE USER 'flower'@'localhost' IDENTIFIED BY 'replace-this';
GRANT CREATE, SELECT, UPDATE, INSERT ON Flowers.*
TO 'flower'@'localhost' WITH GRANT OPTION;
GRANT CREATE USER, RELOAD ON *.* TO 'flower'@'localhost';
FLUSH PRIVILEGES;
```
That password is hard-coded in `SuperFlower` in `FlowerServices.py`. Change it in both the database and the code before real use. Do **not** import `db.sql` as ordinary sample data: it is a historical dump containing user-specific encrypted records.
Put the same value in `database.admin_password` in `secrets.yaml` before starting Flowers.
### 3. Select the URL prefix
## Run locally
Set `URLPREFIX` in `Config.py`:
```python
URLPREFIX = '' # serve at http://127.0.0.1:5012/
# URLPREFIX = '/flowers' # serve below /flowers
```
### 4. Start the server
For local development:
Development:
```bash
python flowers.py
```
For a non-debug application server:
To exercise the production WSGI server locally:
```bash
waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app
gunicorn --workers 1 --threads 6 --bind 127.0.0.1:5012 'flowers:create_app()'
```
Open the configured base URL in a browser. To create the first vault user, visit `/create` under that base URL—for example, `http://127.0.0.1:5012/create` when `URLPREFIX` is empty. After creation, return to the home page and log in.
Flowers keeps authenticated database credentials in process memory. Always use exactly
one Gunicorn worker: users would otherwise appear to be logged out whenever a request
reached a different worker. Threads provide concurrency within that worker. Restarting
Gunicorn intentionally expires all active Flowers sessions.
The creation form limits passwords to 10 characters. Because the password is also transformed directly into a Fernet key, use only characters valid in URL-safe Base64 (`A-Z`, `a-z`, `0-9`, `-`, and `_`) with the current implementation.
## Deploy with Nginx, Gunicorn, and systemd
## Browser routes
The following example installs Flowers in `/opt/flowers`, runs it as an unprivileged
`flowers` user, binds Gunicorn only to loopback, and exposes it through Nginx over
HTTPS. Adapt the paths, hostname, user, and certificate locations for your server.
All routes are relative to `URLPREFIX`.
### 1. Install the application
| Route | Methods | Purpose |
| --- | --- | --- |
| `/` | GET | Login page |
| `/browser` | GET, POST | Login and browser actions (`list`, `show`, `new`, `save`, `deactivate`, `logout`) |
| `/create` | GET, POST | Create a database user, table, and initial demo entry |
| `/update_pwd` | GET, POST | Re-encrypt all records and change the database password |
| `/app` | POST | Form-encoded programmatic interface returning JSON text |
Browser sessions expire after 10 minutes of inactivity. Failed logins are recorded in `ACCESSFILE`; three failures from one IP within five minutes cause temporary denial.
## Programmatic endpoint
`POST /app` accepts `application/x-www-form-urlencoded` fields. Every request includes:
- `action`: `login`, `list`, `one`, `save`, or `deactivate`;
- `name`: Flowers username; and
- `password`: Flowers password.
Additional fields depend on the action:
| Action | Additional fields |
| --- | --- |
| `list` | none |
| `one` | `organization`, `dateCreated` |
| `save` | `organization`, `myname`, `myid`, `secret` |
| `deactivate` | `organization`, `dateCreated` |
Example login request:
Install Python, its virtual-environment support, MariaDB/MySQL client libraries, Nginx,
and your distribution's certificate tooling. Then copy or clone the repository and
create the service account and virtual environment:
```bash
curl -X POST http://127.0.0.1:5012/app \
--data-urlencode 'action=login' \
--data-urlencode 'name=demo' \
--data-urlencode 'password=replace-me'
sudo useradd --system --user-group --home /opt/flowers \
--shell /usr/sbin/nologin flowers
sudo install -d -o flowers -g flowers /opt/flowers
sudo -u flowers git clone https://example.invalid/flowers.git /opt/flowers
sudo -u flowers python3 -m venv /opt/flowers/.venv
sudo -u flowers /opt/flowers/.venv/bin/pip install \
-r /opt/flowers/requirements.txt
```
A typical response is:
Replace the example clone URL with this repository's URL. For an artifact-based
deployment, copy the release into `/opt/flowers` instead and make it owned by
`flowers:flowers`.
```json
{"result": 1, "message": "Access granted"}
```
### 2. Configure secrets and the environment
For historical compatibility, the `list` and `flower` properties are JSON-encoded strings inside the outer JSON response, so clients must decode those properties a second time. The endpoint catches all exceptions and may return only the generic `{"result": -1, "message": "Invalid entry"}` response when an internal error occurs.
## Apache reverse proxy
Waitress can remain bound to localhost while Apache publishes the `/flowers` path. Set `URLPREFIX = '/flowers'`, enable the proxy modules, and add the proxy rules to the relevant virtual host:
Keep deployment secrets outside the repository:
```bash
sudo a2enmod proxy proxy_http
sudo install -d -m 0750 -o root -g flowers /etc/flowers
sudo install -m 0640 -o root -g flowers \
/opt/flowers/secrets.example.yaml /etc/flowers/secrets.yaml
sudoedit /etc/flowers/secrets.yaml
sudoedit /etc/flowers/flowers.env
sudo chown root:flowers /etc/flowers/flowers.env
sudo chmod 0640 /etc/flowers/flowers.env
```
```apache
ProxyPass "/flowers" "http://127.0.0.1:5012/flowers"
ProxyPassReverse "/flowers" "http://127.0.0.1:5012/flowers"
Use this as `/etc/flowers/flowers.env`:
```dotenv
FLOWERS_SECRETS_FILE=/etc/flowers/secrets.yaml
FLOWERS_DB_HOST=127.0.0.1
FLOWERS_DB_PORT=3306
FLOWERS_DB_NAME=Flowers
FLOWERS_DB_ADMIN_USER=flower
FLOWERS_COOKIE_SECURE=1
FLOWERS_ACCESS_FILE=/var/lib/flowers/access
FLOWERS_LOG_FILE=/var/log/flowers/flowers.log
```
Then run Waitress with the command shown above and configure HTTPS on Apache. The checked-in `apache.conf` and `flowers.wsgi` describe an older mod_wsgi deployment; `flowers.wsgi` imports an `app` object that no longer exists, so use the application factory or update that file before relying on mod_wsgi.
## Configuration
`Config.py` contains the available settings:
| Setting | Default purpose |
| --- | --- |
| `LOGFILE` | Application log path (`/tmp/wsgi_flowers.log`) |
| `DEBUG` | Enables debug messages and debug logging when non-zero |
| `INFO` | Enables informational file logging when non-zero |
| `MAXTEXTLEN` | Intended maximum text length used by the UI/domain code |
| `MINFIELDLEN` | Minimum accepted length for organization, login ID, and secret |
| `ACCESSFILE` | Pickle file used for failed-login throttling |
| `DONOTSETFILTER` | Sentinel used by the browser search/filter form |
| `URLPREFIX` | Blueprint prefix, such as `''` or `'/flowers'` |
The Flask session signing key is currently hard-coded in `flowers.py`. Configuration is not loaded from environment variables.
## Tests and maintenance scripts
There is currently no automated test suite. A syntax-only check for the primary modules can be run without a database:
Create the writable locations referenced above:
```bash
python -m py_compile \
flowers.py flower_vase.py FlowerServices.py \
AccessControl.py Config.py Log.py
sudo install -d -m 0750 -o flowers -g flowers /var/lib/flowers
sudo install -d -m 0750 -o flowers -g flowers /var/log/flowers
```
The files `test.py`, `test_fernet.py`, `import.py`, and `dumpl.py` are ad-hoc scripts with embedded usernames/passwords or destructive/database-dependent behavior. Read and edit them before running them; they are not safe, isolated unit tests.
Set a stable, random `flask.secret_key` and the database provisioning password in
`/etc/flowers/secrets.yaml`. Do not generate a new Flask secret on each deployment,
because changing it invalidates every browser session. If the database is on another
host, adjust `FLOWERS_DB_HOST` and ensure its grants and firewall allow the Flowers
server.
## Security notes
### 3. Create the systemd service
Before any serious deployment, at minimum:
Create `/etc/systemd/system/flowers.service`:
2. Move the Flask secret key, provisioning database password, database name/host, and filesystem paths to environment-based configuration.
3. Replace string-formatted SQL with parameterized queries and validate table/account identifiers.
4. Replace the current password-to-Fernet-key construction with a password KDF such as Argon2id, scrypt, or PBKDF2 using a unique salt and appropriate work factor.
5. Add CSRF protection, secure cookie settings, explicit error handling, and tests for authentication and authorization.
6. Run only behind HTTPS and restrict access at the firewall or reverse proxy.
7. Rework user provisioning so the web process does not hold `CREATE USER`, `RELOAD`, and grant privileges during normal operation.
8. Replace the pickle-backed access-control file with a safe, concurrency-aware rate limiter.
```ini
[Unit]
Description=Flowers credential vault
After=network-online.target
Wants=network-online.target
Fernet protects stored field contents from casual database inspection, but it does not compensate for weak/reused passwords, exposed keys, SQL injection, a compromised application host, or unencrypted transport.
[Service]
Type=simple
User=flowers
Group=flowers
WorkingDirectory=/opt/flowers
EnvironmentFile=/etc/flowers/flowers.env
ExecStart=/opt/flowers/.venv/bin/gunicorn --workers 1 --threads 6 --bind 127.0.0.1:5012 --access-logfile - --error-logfile - "flowers:create_app()"
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
PrivateTmp=true
NoNewPrivileges=true
UMask=0077
## License
[Install]
WantedBy=multi-user.target
```
No license file is currently included. Unless a license is added, normal copyright restrictions apply.
Enable the service and confirm that Gunicorn answers locally:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now flowers
sudo systemctl status flowers
curl --fail --head http://127.0.0.1:5012/
```
Service and access logs are available through `journalctl -u flowers`. The application
also writes its own log to the configured `FLOWERS_LOG_FILE`.
### 4. Configure Nginx
Create `/etc/nginx/sites-available/flowers` (or the equivalent include path on your
distribution):
```nginx
server {
listen 80;
listen [::]:80;
server_name flowers.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name flowers.example.com;
ssl_certificate /etc/letsencrypt/live/flowers.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/flowers.example.com/privkey.pem;
client_max_body_size 64k;
location / {
proxy_pass http://127.0.0.1:5012;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 30s;
}
}
```
Obtain the certificate before enabling the TLS server, or first use your ACME client's
HTTP-only bootstrap configuration. Then enable and validate the site:
```bash
sudo ln -s /etc/nginx/sites-available/flowers /etc/nginx/sites-enabled/flowers
sudo nginx -t
sudo systemctl reload nginx
curl --fail --head https://flowers.example.com/
```
Keep port `5012` closed in the host firewall; only ports 80 and 443 need to be public.
If another reverse proxy sits in front of Nginx, configure Nginx to accept client IP
headers only from that proxy rather than from arbitrary clients.
### Deploy under a URL prefix
To publish Flowers at `https://example.com/flowers/`, add this setting to
`/etc/flowers/flowers.env`:
```dotenv
FLOWERS_URL_PREFIX=/flowers
```
Use these Nginx locations without a trailing path on `proxy_pass`, so the `/flowers`
prefix reaches Flask unchanged:
```nginx
location = /flowers {
return 301 /flowers/;
}
location /flowers/ {
proxy_pass http://127.0.0.1:5012;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
```
After changing the environment or application code, restart Gunicorn with
`sudo systemctl restart flowers`. After changing Nginx, run `sudo nginx -t` before
reloading it.
## Tests
The tests do not need a live database; they use the actual legacy Fernet implementation with a MariaDB-compatible fake connection.
```bash
pip install -r requirements-dev.txt
python -m pytest
```
They can also run using only the standard library test runner:
```bash
python -m unittest discover -s tests -v
```
## Remaining security constraints
The rewrite removes the immediately exploitable SQL interpolation and browser-cookie credential storage, but Flowers remains a small personal vault with a legacy storage design:
- the historical password-derived Fernet key is intentionally still weak;
- the web process still has provisioning privileges if `/create` is enabled;
- the in-memory credential store is suitable for one application process, not a multi-process cluster; and
- rate-limit state is local to one host.
Use a strong unique password made only of Base64-compatible characters, bind Gunicorn
to localhost, and expose it only through an authenticated HTTPS reverse proxy. Set
stable provisioning and Flask secrets before deployment.
-315
View File
@@ -1,315 +0,0 @@
####################
#
# file is replaced by FlowerServices.py
#
1= # intentional python syntax error
#
#####################
from Crypto.Cipher import AES
from Crypto.Hash import MD5
import base64
from Log import Log
from Config import *
import pymysql as mdb
#encryption stuff
SECRET='HSPh1O5vc6YY2Xt8jUxlTp'
BLOCK_SIZE = 32
PADDING='_'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
def md5(message):
hash = MD5.new()
hash.update(message.encode('utf-8'))
return hash.hexdigest()
class Flower():
def __init__(self, user, pwd):
self.db = 'Flowers'
self.db_username = user + '_'
self.db_password = md5(pwd)
self.customer_table = self.db_username
self.cipher = AES.new(pad(SECRET + pwd))
def encode(self, s):
return str(base64.b64encode(self.cipher.encrypt(pad(s))).decode("utf-8"))
def decode(self, e):
return str(self.cipher.decrypt(base64.b64decode(e)).decode("utf-8")).rstrip(PADDING)
def add(self, organization, myname, myid, secret, deleted=0, timestamp=None):
''' POST a new flower
'''
if len(organization)>MINFIELDLEN and len(myid)>MINFIELDLEN and len(secret)>MINFIELDLEN:
sql = "INSERT INTO {} (organization ,myID ,myName ,mySecret, deleted) VALUES ('{}', '{}', '{}', '{}', {})".format(self.customer_table, organization, self.encode(myid), self.encode(myname), self.encode(secret), deleted)
if timestamp is not None:
sql = "INSERT INTO {} (organization ,myID ,myName ,mySecret, deleted, dateCreated) VALUES ('{}', '{}', '{}', '{}', {}, '{}')".format(self.customer_table, organization, self.encode(myid), self.encode(myname), self.encode(secret), deleted, timestamp)
result, data = self.do_db_commit(sql)
Log.debug(result)
if result==1:
return {'organization': organization, 'dateCreated': 'STORED', 'myID': myid, 'myName': myname,'mySecret': secret}
return {'organization': organization, 'dateCreated': 'FAILED', 'myID': myid, 'myName': myname,'mySecret': secret}
def all(self, isdeleted=0):
''' GET flowers list
'''
#sql = "SELECT organization, myID, myName, dateCreated FROM %s " % (self.customer_table)
sql = """
select uniqueflowers.organization, uniqueflowers.myID, uniqueflowers.dateCreated from {0} as uniqueflowers
inner join (
select organization, max(dateCreated) as lastCreated from {0} group by organization order by dateCreated DESC) allflowers
on allflowers.organization=uniqueflowers.organization and lastCreated=uniqueflowers.dateCreated where uniqueflowers.deleted={1}
""".format(self.customer_table, isdeleted)
result, data = self.do_db_allrows(sql)
#Log.debug(sql_result)
all_flowers=[]
if result==1:
for row in data:
all_flowers.append({'organization': str(row[0]), 'myID': self.decode(row[1]), 'dateCreated': str(row[2])})
return all_flowers
def one(self, organization, datetimestamp ):
''' flower for user, org and datetime
'''
result = -1
secret = ':-('
sql = "SELECT mySecret, myID, myName FROM %s where organization='%s' and dateCreated='%s' order by dateCreated DESC limit 1" % (self.customer_table, organization, datetimestamp)
result, data = self.do_db_1row(sql)
if result == 1:
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': self.decode(data[1]), 'myName': self.decode(data[2]),'mySecret': self.decode(data[0])}
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': '', 'myName': '','mySecret': '-- Not found --'}
def deactivate(self, organization, datetimestamp ):
''' flower for user, org and datetime
'''
result = -1
secret = ':-('
sql = "update {} set deleted=1 where organization='{}' and dateCreated='{}' ".format(self.customer_table, organization, datetimestamp)
result, data = self.do_db_commit(sql)
if result == 1:
secret = '- DELETED -'
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': '', 'myName': '','mySecret': secret}
def empty(self):
''' empty flower
'''
return {'organization': '', 'dateCreated': '', 'myID': '', 'myName': '','mySecret': ''}
def numberOfEntries(self):
''' GET login info, check if its valid
'''
sql = "SELECT count(*) FROM {};".format(self.customer_table)
result, data = self.do_db_1row(sql)
if result == 1:
return(data[0])
return -1
def update_pwd(self, new_pwd):
newcipher = AES.new(pad(SECRET + new_pwd))
def new_encode(s):
return str(base64.b64encode(newcipher.encrypt(pad(s))).decode("utf-8"))
end_result = "ERROR updating hushes\n"
sql = """
select myID, myName, mySecret, organization, dateCreated from {0}""".format(self.customer_table)
result, data = self.do_db_allrows(sql)
# Log.debug(sql_result)
if result == 1:
# convert all hushes
sql = ""
for row in data:
sql += "update {} set myID='{}', myName='{}', mySecret='{}' where organization='{}' and dateCreated='{}';\n".\
format(self.customer_table,
new_encode(self.decode(row[0])),
new_encode(self.decode(row[1])),
new_encode(self.decode(row[2])),row[3], row[4])
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "ERROR updating user-password\n"
# update the password of the user
sql = "SET PASSWORD FOR '{}'@'localhost' = PASSWORD('{}'); ".format(self.db_username, md5(new_pwd))
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "SUCCESSFULLY updated, now login again\n"
return end_result
def do_db_commit(self, sql):
Log.debug('INSc: '+sql)
result = 0
data = ''
con = None
try:
con = mdb.connect(host='localhost', passwd=self.db_password, user=self.db_username, db=self.db);
cur = con.cursor()
if ";\n" in sql:
for s in sql.split(";\n"):
if len(s)>1:
cur.execute(s)
else:
cur.execute(sql)
cur.close()
con.commit()
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
def do_db_1row(self, sql):
Log.debug('1row sql: '+sql)
result = 0
con = None
try:
con = mdb.connect(host='localhost', passwd=self.db_password, user=self.db_username, db=self.db);
cur = con.cursor()
cur.execute(sql)
data = cur.fetchone()
if data:
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
def do_db_allrows(self, sql):
Log.debug('all rows sql: '+sql)
result = 0
con = None
try:
con = mdb.connect(host='localhost', passwd=self.db_password, user=self.db_username, db=self.db);
cur = con.cursor()
cur.execute(sql)
data = cur.fetchall()
if data:
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
class SuperFlower(Flower):
def __init__(self, customer, pwd):
super().__init__(customer, pwd)
self.customer_name = customer + '_'
self.customer_pwd = pwd
self.db_username = 'flower'
self.db_password = md5('flower')
def createNewTable(self):
# create new table and user
sql = """
CREATE TABLE `{}` (
`organization` varchar(80) NOT NULL,
`myID` varchar(129) NOT NULL,
`myName` varchar(129) NOT NULL,
`mySecret` varchar(65) NOT NULL,
`dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` int(11) DEFAULT '0',
PRIMARY KEY (`organization`,`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;""".format(self.customer_table)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """create user {}@localhost identified by '{}';""".format(self.customer_name, md5(self.customer_pwd))
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """grant select,update,insert on Flowers.{} to {}@localhost;""".format(self.customer_table, self.customer_name)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
self.flush_privs()
return 1
def flush_privs(self):
sql = """flush privileges;"""
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
return 1
def migrate(self, old_customer_name):
OLDSECRET = 'HSPh1O5vc6YY2Xt8jUxlTpd*DZsjMMAH'
OLDCIPHER = AES.new(OLDSECRET)
old_DecodeAES = lambda e: str(OLDCIPHER.decrypt(base64.b64decode(e)).decode("utf-8")).rstrip(PADDING)
def sql_datetime(datetime_type):
return datetime_type.strftime("%Y-%m-%d %H:%M:%S")
end_result = "ERROR creting new table\n"
if self.createNewTable():
end_result = "ERROR updating hushes\n"
sql = """
select myID, myName, mySecret, organization, dateCreated, deleted from {0}""".format(old_customer_name)
# 0 1 2 3 4 5
result, data = self.do_db_allrows(sql)
# Log.debug(sql_result)
if result == 1:
# convert all hushes
sql = ""
for row in data:
#sql += "insert {} set organization='{}', myID='{}', myName='{}', mySecret='{}', deleted={}, dateCreated='{}' where organization='{}' and dateCreated='{}';\n". \
sql += "INSERT INTO {} (organization ,myID ,myName ,mySecret, deleted, dateCreated) VALUES ('{}', '{}', '{}', '{}', {}, '{}') ;\n".\
format(self.customer_table,
row[3],
self.encode(row[0]),
self.encode(row[1]),
self.encode(old_DecodeAES(row[2])),
row[5],
sql_datetime(row[4]))
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "SUCCESSFULLY updated, now login again\n"
return end_result
if __name__ == '__main__':
print("this is a library, sorry dude")
-272
View File
@@ -1,272 +0,0 @@
####################
#
# file is replaced by SecretServices.py
#
1= # intentional python syntax error
#
####################
from Crypto.Cipher import AES
from Crypto.Hash import MD5
import base64
import os, time, re, sys
from Log import Log
from Config import *
import json
import pymysql as mdb
import datetime
import hashlib
#encryption stuff
SECRET='HSPh1O5vc6YY2Xt8jUxlTpd*DZsjMMAH'
BLOCK_SIZE = 32
PADDING='_'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
CIPHER = AES.new(SECRET)
EncodeAES = lambda s: str(base64.b64encode(CIPHER.encrypt(pad(s))).decode("utf-8"))
DecodeAES = lambda e: str(CIPHER.decrypt(base64.b64decode(e)).decode("utf-8")).rstrip(PADDING)
def md5(message):
hash = MD5.new()
hash.update(message.encode('utf-8'))
return hash.hexdigest()
class Flower():
def __init__(self, user, pwd):
self.database = 'Flowers'
self.table = user
self.username = user
self.password = md5(pwd)
def add(self, organization, myname, myid, secret):
''' POST a new flower
'''
if len(organization)>MINFIELDLEN and len(myid)>MINFIELDLEN and len(secret)>MINFIELDLEN:
sql = "INSERT INTO {} (organization ,myID ,myName ,mySecret, deleted) VALUES ('{}', '{}', '{}', '{}', 0)".format(self.table, organization, myid, myname, EncodeAES(secret))
result, data = self.do_db_commit(sql)
Log.debug(result)
if result==1:
return {'organization': organization, 'dateCreated': 'STORED', 'myID': myid, 'myName': myname,'mySecret': secret}
return {'organization': organization, 'dateCreated': 'FAILED', 'myID': myid, 'myName': myname,'mySecret': secret}
def all(self, isdeleted=0):
''' GET flowers list
'''
#sql = "SELECT organization, myID, myName, dateCreated FROM %s " % (self.table)
sql = """
select uniqueflowers.organization, uniqueflowers.myID, uniqueflowers.dateCreated from {0} as uniqueflowers
inner join (
select organization, max(dateCreated) as lastCreated from {0} group by organization order by dateCreated DESC) allflowers
on allflowers.organization=uniqueflowers.organization and lastCreated=uniqueflowers.dateCreated where uniqueflowers.deleted={1}
""".format(self.table, isdeleted)
result, data = self.do_db_allrows(sql)
#Log.debug(sql_result)
all_flowers=[]
if result==1:
for row in data:
all_flowers.append({'organization': str(row[0]), 'myID': str(row[1]), 'dateCreated': str(row[2])})
return all_flowers
def one(self, organization, datetimestamp ):
''' flower for user, org and datetime
'''
result = -1
secret = ':-('
sql = "SELECT mySecret, myID, myName FROM %s where organization='%s' and dateCreated='%s' order by dateCreated DESC limit 1" % (self.table, organization, datetimestamp)
result, data = self.do_db_1row(sql)
if result == 1:
secret = DecodeAES(data[0])
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': data[1], 'myName': data[2],'mySecret': secret}
def deactivate(self, organization, datetimestamp ):
''' flower for user, org and datetime
'''
result = -1
secret = ':-('
sql = "update %s set deleted=1 where organization='%s' and dateCreated='%s' " % (self.table, organization, datetimestamp)
result, data = self.do_db_commit(sql)
if result == 1:
secret = '- DELETED -'
return {'organization': organization, 'dateCreated': datetimestamp, 'myID': '', 'myName': '','mySecret': secret}
def empty(self):
''' empty flower
'''
return {'organization': '', 'dateCreated': '', 'myID': '', 'myName': '','mySecret': ''}
def numberOfEntries(self):
''' GET login info, check if its valid
'''
sql = "SELECT count(*) FROM {};".format(self.table)
result, data = self.do_db_1row(sql)
if result == 1:
return(data[0])
return -1
def do_db_commit(self, sql):
Log.debug('INSc: '+sql)
result = 0
data = ''
con = None
try:
con = mdb.connect(host='localhost', passwd=self.password, user=self.username, db=self.database);
cur = con.cursor()
cur.execute(sql)
con.commit()
cur.close()
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
def do_db_1row(self, sql):
Log.debug('1row sql: '+sql)
result = 0
con = None
try:
con = mdb.connect(host='localhost', passwd=self.password, user=self.username, db=self.database);
cur = con.cursor()
cur.execute(sql)
data = cur.fetchone()
if data:
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
def do_db_allrows(self, sql):
Log.debug('all rows sql: '+sql)
result = 0
con = None
try:
con = mdb.connect(host='localhost', passwd=self.password, user=self.username, db=self.database);
cur = con.cursor()
cur.execute(sql)
data = cur.fetchall()
if data:
result = 1
except mdb.Error as e:
m = "Error %d: %s" % (e.args[0],e.args[1])
data = {'message': m}
Log.debug(m)
result = -1
finally:
if con:
con.close()
return result, data
class SuperFlower(Flower):
def __init__(self, user, pwd):
self.database = 'Flowers'
self.table = user
self.table_hush = pwd
self.username = 'flower'
self.password = md5('flower')
def createNewTable(self):
# create new table and user
sql = """
CREATE TABLE `{0}` (
`organization` varchar(50) NOT NULL,
`myID` varchar(50) NOT NULL,
`myName` varchar(50) NOT NULL,
`mySecret` varchar(50) NOT NULL,
`dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` int(11) DEFAULT '0',
PRIMARY KEY (`organization`,`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;""".format(self.table)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """create user {0}@localhost identified by '{1}';""".format(self.table, md5(self.table_hush))
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """grant select,update,insert on Flowers.{0} to {0}@localhost;""".format(self.table)
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
sql = """flush privileges;"""
result, data = self.do_db_1row(sql)
if result < 0:
Log.info("SQL ERROR:")
Log.info(data)
return 0
return 1
def re_hush(self, new_hush):
end_result = "Starting Rehush\n"
sql = """
select mySecret, organization, dateCreated from {0}""".format(self.table)
result, data = self.do_db_allrows(sql)
# Log.debug(sql_result)
if result == 1:
# convert all hushes
sql = ""
for row in data:
old_hush = DecodeAES(row[0])
sql += "update {} set mySecret='{}' where organization='{}' and dateCreated='{}';\n".format(self.table, md5(old_hush), row[1], row[2])
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "Finished Rehush\n"
end_result = "Starting update user password\n"
# update the password of the user
sql = "SET PASSWORD FOR '{}'@'localhost' = PASSWORD(md5(secret));\n".format(self.table)
sql += "flush privileges;"
result, data = self.do_db_commit(sql)
if result == 1:
end_result = "Finished update user password\n"
if __name__ == '__main__':
print("this is a library, sorry dude")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-9
View File
@@ -1,9 +0,0 @@
WSGIDaemonProcess flowers user=www-data group=www-data threads=5
WSGIScriptAlias /flowers /usr/share/projects/Flowers/flowers.wsgi
<Directory /usr/share/projects/Flowers>
WSGIProcessGroup flowers
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
-17
View File
@@ -1,17 +0,0 @@
import kivy
kivy.require('1.0.6') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text='Hello world')
if __name__ == '__main__':
MyApp().run()
-7
View File
@@ -1,7 +0,0 @@
import requests
r = requests.post('http://0.0.0.0:8080/app', data = {'name':'piet', 'password': 'piet', 'action': 'list'})
#r = requests.post('http://0.0.0.0:8080/app', data = {'name':'ignace', 'password': 'white', 'action': 'one', 'dateCreated': '2018-05-30 21:18:00', 'organization': 'https://mijntele2.tele2.nl/'})
#r = requests.post('http://0.0.0.0:8080/app', data = {'name':'ignace', 'password': 'white', 'action': 'save'})
#r = requests.post('http://0.0.0.0:8080/app', data = {'name':'ignace', 'password': 'white', 'action': 'list'})
# print(r.text)
-54
View File
@@ -1,54 +0,0 @@
# from flask import Flask
from Config import *
import sys
import datetime
from SecretServices import Flower,SuperFlower
from AccessControl import Access
import json
# from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash, json, make_response
# from werkzeug.exceptions import HTTPException
newuser = 'ignace'
newhush = 'black'
# login db with generic user
# f = SuperFlower(newuser, newhush)
# if f.createNewTable():
# login as new user
# u = Flower(newuser, newhush)
# add one line
#u.add('Demo organisation', 'Demo name', 'Demo login', 'Demo hush')
# read one line
#create user
# login db with generic user
# f = SuperFlower(newuser, newhush)
# if f.createNewTable():
# # login as new user
# u = Flower(newuser, newhush)
# # add one line
# u.add('Demo organisation', 'Demo name', 'Demo login', 'Demo hush')
# retrieve all flowers
# u = Flower(newuser, newhush)
# flowers=u.all()
# print(flowers)
# retrieve one flowers
# u = Flower(newuser, newhush)
# flower=u.one('ah', '2003-03-17 17:30:27')
# print(flower)
# dump all flowers with pwds
u = Flower(newuser, newhush)
flowers=u.all()
for f in flowers:
flower = u.one(f['organization'], f['dateCreated'])
print('"{}","{}","{}","{}","{}",'.format(flower['organization'],flower['dateCreated'],flower['myID'],flower['myName'],flower['mySecret']))
+310 -184
View File
@@ -1,202 +1,328 @@
from flask import Flask, Blueprint, send_from_directory
from Config import *
import sys
import datetime
import pytz
from FlowerServices import Flower,SuperFlower
from AccessControl import Access
"""HTTP routes for the Flowers browser UI and legacy form API."""
from __future__ import annotations
import json
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash, json, make_response
from werkzeug.exceptions import HTTPException
import secrets
import threading
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
# configuration
# DEBUG = False
# SECRET_KEY = 'development key'
flower_bp = Blueprint("flower_vase", __name__, url_prefix=URLPREFIX)
from flask import (
Blueprint,
abort,
current_app,
flash,
jsonify,
redirect,
render_template,
request,
session,
url_for,
)
from AccessControl import Access
from Config import DONOTSETFILTER, SESSION_MINUTES, URLPREFIX
from FlowerServices import Flower, InvalidCredentials, StorageError, SuperFlower
# app = Flask(__name__)
# app.config.from_object(__name__)
flower_bp = Blueprint(
"flower_vase",
__name__,
url_prefix=URLPREFIX,
static_folder="static",
static_url_path="/static",
template_folder="templates",
)
@flower_bp.route('/static/<filename>')
def statix(filename):
return send_from_directory('static', filename)
@flower_bp.route('/')
@dataclass
class _Credential:
username: str
password: str
expires_at: datetime
class CredentialStore:
"""Keep database credentials server-side instead of in Flask's cookie."""
def __init__(self) -> None:
self._items: dict[str, _Credential] = {}
self._lock = threading.Lock()
def create(self, username: str, password: str) -> str:
token = secrets.token_urlsafe(32)
with self._lock:
self._purge()
self._items[token] = _Credential(username, password, self._deadline())
return token
def get(self, token: str | None) -> tuple[str, str] | None:
if not token:
return None
with self._lock:
self._purge()
credential = self._items.get(token)
if credential is None:
return None
credential.expires_at = self._deadline()
return credential.username, credential.password
def revoke(self, token: str | None) -> None:
if token:
with self._lock:
self._items.pop(token, None)
def _purge(self) -> None:
now = datetime.now(timezone.utc)
expired = [key for key, value in self._items.items() if value.expires_at <= now]
for key in expired:
del self._items[key]
@staticmethod
def _deadline() -> datetime:
return datetime.now(timezone.utc) + timedelta(minutes=SESSION_MINUTES)
def _credentials() -> CredentialStore:
return current_app.extensions["credential_store"]
def _csrf_token() -> str:
token = session.get("csrf_token")
if not token:
token = secrets.token_urlsafe(32)
session["csrf_token"] = token
return token
@flower_bp.context_processor
def template_context() -> dict:
return {"csrf_token": _csrf_token()}
@flower_bp.before_request
def protect_browser_posts():
if request.method != "POST" or request.endpoint == "flower_vase.web_service":
return None
supplied = request.form.get("csrf_token", "")
expected = session.get("csrf_token", "")
if not expected or not secrets.compare_digest(supplied, expected):
abort(400, description="Invalid or expired form token")
return None
def _vault_from_session() -> Flower | None:
credential = _credentials().get(session.get("auth_token"))
if not credential:
return None
try:
return Flower(*credential)
except InvalidCredentials:
return None
def _login_page(name="Welcome back", password="Enter your bouquet password", status=200):
return render_template(
"index.html", name_suggestion=name, pwd_suggestion=password
), status
@flower_bp.get("/")
def index():
print(request.remote_addr)
return render_template('index.html', name_suggestion="Hello", pwd_suggestion="Want to try your luck?")
return _login_page()
@flower_bp.route('/browser' , methods=['POST', 'GET'])
@flower_bp.route("/browser", methods=["GET", "POST"])
def application():
now = datetime.datetime.now(pytz.timezone('Europe/Amsterdam'))
if request.method == "GET":
vault = _vault_from_session()
if vault is None:
return redirect(url_for("flower_vase.index"))
try:
selected_filter = session.get("filter", "")
return render_template(
"list.html", flowers=vault.all(), filter=selected_filter
)
except StorageError:
flash("The bouquet database could not complete that request.", "error")
return redirect(url_for("flower_vase.index"))
if request.method == 'GET':
return render_template('index.html', name_suggestion="Faded out", pwd_suggestion="Want to retry your luck?")
access = Access()
client_ip = request.remote_addr or "unknown"
if not access.granted(client_ip):
return _login_page("Too many attempts", "Try again in five minutes", 429)
access_ctrl = Access()
if not access_ctrl.granted(request.remote_addr):
return render_template('index.html', name_suggestion="Sorry", pwd_suggestion="Access denied")
action = request.form.get("action", "")
if action == "login":
return _login(access, client_ip)
if action == "logout":
_credentials().revoke(session.pop("auth_token", None))
session.pop("filter", None)
flash("You have been signed out.", "success")
return redirect(url_for("flower_vase.index"))
action=request.form['action']
filter=''
if 'filter' in session:
filter = session['filter']
vault = _vault_from_session()
if vault is None:
session.pop("auth_token", None)
flash("Your session expired. Please sign in again.", "error")
return redirect(url_for("flower_vase.index"))
if action == 'logout':
session['filter'] = request.form['filter']
session['timeout'] = now - datetime.timedelta(minutes=999)
return render_template('index.html', name_suggestion="Bye", pwd_suggestion="Come back any time")
if action == 'login':
user=request.form['name']
pwd=request.form['password']
F = Flower(user, pwd)
if F.numberOfEntries()>=0:
session['dbuser'] = user
session['dbpwd'] = pwd
session['timeout'] = now
session['filter'] = filter
return render_template('list.html', flowers=F.all(), filter=filter)
#login failure
access_ctrl.deny(request.remote_addr)
print("Flowers - authorization failed for {}".format(user))
return render_template('index.html', name_suggestion="Sorry", pwd_suggestion="Better next time")
if 'timeout' in session and session['timeout']+datetime.timedelta(minutes=10)>now:
session['timeout'] = now
if 'filter' in request.form.keys() and len(request.form['filter'])>1 and request.form['filter'] != DONOTSETFILTER:
session['filter']=request.form['filter']
filter = request.form['filter']
F = Flower(session['dbuser'], session['dbpwd'])
if F.numberOfEntries()>=0:
if action == 'list':
return render_template('list.html', flowers=F.all(), filter=filter)
if action == 'show':
return render_template('show.html', flower=F.one(request.form['organization'], request.form['datetime']))
if action == 'edit':
return render_template('edit.html', flower=F.one(request.form['organization'], request.form['datetime']))
if action == 'new':
return render_template('edit.html', flower=F.empty())
if action == 'save':
flower = F.add(request.form['organization'],request.form['myname'],request.form['myid'],request.form['secret'])
session['filter']=request.form['organization']
return render_template('show.html', flower=flower)
if action == 'deactivate':
flower = F.deactivate(request.form['organization'],request.form['datetime'])
session['filter']=''
return render_template('show.html', flower=flower)
if action == 'rehush':
return render_template('rehush.html', flower=F.empty())
else:
return render_template('new.html', flower=F.empty())
return render_template('index.html', name_suggestion="Sorry", pwd_suggestion="Better next time")
@flower_bp.route('/create' , methods=['POST','GET'])
def create():
access_ctrl = Access()
if not access_ctrl.granted(request.remote_addr):
return render_template('index.html', name_suggestion="Sorry", pwd_suggestion="Access denied")
if 'name' not in request.form:
return render_template('create.html')
newuser = request.form['name']
newhush = request.form['password']
# login db with generic user
f = SuperFlower(newuser, newhush)
if f.createNewTable():
# login as new user
u = Flower(newuser, newhush)
# add one line
u.add('Demo organisation', 'Demo name', 'Demo login', 'Demo hush')
# return to the login page
return render_template('index.html', name_suggestion="Now login", pwd_suggestion="for your very first time")
return render_template('create.html', message="Sorry - System error - check log files")
@flower_bp.route('/update_pwd', methods=['POST','GET'])
def update_pwd():
if 'name' not in request.form:
return render_template('rehush.html')
user = request.form['name']
oldhush = request.form['old_password']
newhush = request.form['new_password']
# login db with generic user
f = Flower(user, oldhush)
message = f.update_pwd(newhush)
f = SuperFlower(user, oldhush)
f.flush_privs()
return render_template('index.html', name_suggestion=message, pwd_suggestion="...")
# @app.route('/migrate', methods=['POST','GET'])
# def migrate():
# user = 'ignace'
# hush = 'black'
#
# f = SuperFlower(user, hush)
# f.migrate(user)
#
# return render_template('index.html', name_suggestion='login again', pwd_suggestion="...")
@flower_bp.route('/app' , methods=['POST'])
def web_service():
# same thing as 'aplication, but returns go in json
result = json.dumps({'result': -1, 'message': 'Invalid entry'})
selected_filter = session.get("filter", "")
form_filter = request.form.get("filter", "")
if len(form_filter) > 1 and form_filter != DONOTSETFILTER:
selected_filter = form_filter
session["filter"] = selected_filter
try:
access_ctrl = Access()
if not access_ctrl.granted(request.remote_addr):
result = json.dumps({'result': -1, 'message': 'Access denied'})
if action == "list":
return render_template("list.html", flowers=vault.all(), filter=selected_filter)
if action in {"show", "edit"}:
flower = vault.one(
request.form.get("organization", ""), request.form.get("datetime", "")
)
return render_template(f"{action}.html", flower=flower, filter=selected_filter)
if action == "new":
return render_template("edit.html", flower=vault.empty(), filter=selected_filter)
if action == "save":
flower = vault.add(
request.form.get("organization", "").strip(),
request.form.get("myname", "").strip(),
request.form.get("myid", "").strip(),
request.form.get("secret", ""),
)
if flower["dateCreated"] == "FAILED":
flash("Organization, login, and secret must each be at least four characters.", "error")
return render_template("edit.html", flower=flower, filter=selected_filter), 422
session["filter"] = flower["organization"]
flash("Entry saved as a new version.", "success")
return render_template("show.html", flower=flower, filter=flower["organization"])
if action == "deactivate":
flower = vault.deactivate(
request.form.get("organization", ""), request.form.get("datetime", "")
)
session["filter"] = ""
flash("Entry removed from the active list.", "success")
return render_template("show.html", flower=flower, filter="")
if action == "rehush":
return render_template("rehush.html")
except StorageError:
flash("The bouquet database could not complete that request.", "error")
return redirect(url_for("flower_vase.index"))
else:
#no session variables here - the client will take care of fileter and timeout
action = request.form['action']
user = request.form['name']
pwd = request.form['password']
F = Flower(user, pwd)
if F.numberOfEntries() == -1:
#login failure
access_ctrl.deny(request.remote_addr)
result = json.dumps({'result': 0, 'message': 'Invalid username or password'})
elif action == 'login':
result = json.dumps({'result': 1, 'message': 'Access granted'})
elif action == 'list':
result = json.dumps({'result': 1, 'message': 'Ok', 'list': json.dumps(F.all())})
elif action == 'one':
result = json.dumps({'result': 1, 'message': 'Ok', 'flower': json.dumps( F.one(request.form['organization'], request.form['dateCreated']) )})
elif action == 'save':
result = json.dumps({'result': 1, 'message': 'Ok', 'flower': json.dumps(F.add(request.form['organization'],request.form['myname'],request.form['myid'],request.form['secret']) )})
elif action == 'deactivate':
result = json.dumps({'result': 1, 'message': 'Ok', 'flower': json.dumps(F.deactivate(request.form['organization'], request.form['dateCreated']))})
except:
pass
return result
abort(400, description="Unknown action")
def _login(access: Access, client_ip: str):
username = request.form.get("name", "").strip()
password = request.form.get("password", "")
try:
vault = Flower(username, password)
authenticated = vault.authenticate()
except InvalidCredentials:
authenticated = False
if not authenticated:
access.deny(client_ip)
return _login_page("Sign-in failed", "Check your username and password", 401)
old_token = session.get("auth_token")
_credentials().revoke(old_token)
session.clear()
session["csrf_token"] = secrets.token_urlsafe(32)
session["auth_token"] = _credentials().create(username, password)
session["filter"] = ""
return redirect(url_for("flower_vase.application"))
@flower_bp.route("/create", methods=["GET", "POST"])
def create():
if request.method == "GET":
return render_template("create.html")
access = Access()
client_ip = request.remote_addr or "unknown"
if not access.granted(client_ip):
return _login_page("Too many attempts", "Try again later", 429)
username = request.form.get("name", "").strip()
password = request.form.get("password", "")
try:
provisioner = SuperFlower(username, password)
created = provisioner.createNewTable()
except InvalidCredentials as exc:
return render_template("create.html", message=str(exc)), 422
if created:
Flower(username, password).add(
"Demo organisation", "Demo name", "Demo login", "Demo secret"
)
flash("Your bouquet is ready. Sign in to continue.", "success")
return redirect(url_for("flower_vase.index"))
return render_template(
"create.html", message="The bouquet could not be created. Check the server log."
), 500
@flower_bp.route("/update_pwd", methods=["GET", "POST"])
def update_pwd():
if request.method == "GET":
return render_template("rehush.html")
try:
vault = Flower(
request.form.get("name", "").strip(),
request.form.get("old_password", ""),
)
if not vault.authenticate():
raise InvalidCredentials("Current credentials are not valid")
message = vault.update_pwd(request.form.get("new_password", ""))
except InvalidCredentials as exc:
return render_template("rehush.html", message=str(exc)), 422
_credentials().revoke(session.pop("auth_token", None))
flash(message, "success" if message.startswith("SUCCESS") else "error")
return redirect(url_for("flower_vase.index"))
@flower_bp.post("/app")
def web_service():
"""Preserve the historical form API, including double-encoded payloads."""
access = Access()
client_ip = request.remote_addr or "unknown"
if not access.granted(client_ip):
return jsonify(result=-1, message="Access denied")
action = request.form.get("action", "")
username = request.form.get("name", "")
password = request.form.get("password", "")
try:
vault = Flower(username, password)
if not vault.authenticate():
access.deny(client_ip)
return jsonify(result=0, message="Invalid username or password")
if action == "login":
return jsonify(result=1, message="Access granted")
if action == "list":
return jsonify(result=1, message="Ok", list=json.dumps(vault.all()))
if action == "one":
item = vault.one(
request.form.get("organization", ""),
request.form.get("dateCreated", ""),
)
return jsonify(result=1, message="Ok", flower=json.dumps(item))
if action == "save":
item = vault.add(
request.form.get("organization", ""),
request.form.get("myname", ""),
request.form.get("myid", ""),
request.form.get("secret", ""),
)
return jsonify(result=1, message="Ok", flower=json.dumps(item))
if action == "deactivate":
item = vault.deactivate(
request.form.get("organization", ""),
request.form.get("dateCreated", ""),
)
return jsonify(result=1, message="Ok", flower=json.dumps(item))
except (InvalidCredentials, StorageError, ValueError):
current_app.logger.exception("Flowers API request failed")
return jsonify(result=-1, message="Invalid entry")
+19 -8
View File
@@ -1,16 +1,27 @@
"""Flowers application factory."""
from flask import Flask
from flower_vase import flower_bp
from Config import COOKIE_SECURE, DEBUG, SECRET_KEY
from flower_vase import CredentialStore, flower_bp
def create_app():
def create_app(test_config: dict | None = None) -> Flask:
app = Flask(__name__)
app.config["SECRET_KEY"] = 'adhf adsh 8347y92347rupqo;wjf cowuyergc9b24387ryx1 -923pqr pqwejf qy7i34'
app.config.from_mapping(
SECRET_KEY=SECRET_KEY,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
SESSION_COOKIE_SECURE=COOKIE_SECURE,
MAX_CONTENT_LENGTH=64 * 1024,
)
if test_config:
app.config.update(test_config)
app.extensions["credential_store"] = CredentialStore()
app.register_blueprint(flower_bp)
return app
if __name__ == '__main__':
my_app = create_app()
# if needed - initialize data
my_app.run(debug=True, host='127.0.0.1', port=5012)
if __name__ == "__main__":
create_app().run(debug=DEBUG, host="127.0.0.1", port=5012)
+4 -2
View File
@@ -1,7 +1,9 @@
#!/usr/bin/python
import sys
sys.path.insert(0, '/usr/share/projects/wsgi_apps/flowers')
from flowers import app as application
sys.path.insert(0, "/usr/share/projects/wsgi_apps/flowers")
from flowers import create_app
application = create_app()
-28
View File
@@ -1,28 +0,0 @@
from Config import *
import sys
import datetime
from FlowerServices import Flower,SuperFlower
from AccessControl import Access
file1 = open('pins.csv', 'r')
Lines = file1.readlines()
newuser = 'janine'
newhush = 'demaat196'
# create user in the db.
f = SuperFlower(newuser, newhush)
if f.createNewTable():
# login as new user
u = Flower(newuser, newhush)
count = 0
# Strips the newline character
for line in Lines:
count += 1
line=line.strip()
print(line)
(org, date, id, name, pwd) = line.split('","',5)
org = org[1:]
pwd = pwd[:-2]
u.add(org, name, id, pwd, 0, date)
+2
View File
@@ -0,0 +1,2 @@
-r requirements.txt
pytest>=8.0,<10
+6
View File
@@ -0,0 +1,6 @@
Flask>=3.1,<4
fernet>=1.0,<2
gunicorn>=23.0,<24
PyMySQL>=1.1,<3
PyYAML>=6.0,<7
+6
View File
@@ -0,0 +1,6 @@
# Copy this file to secrets.yaml and replace both values.
flask:
secret_key: "replace-with-a-long-random-value"
database:
admin_password: "replace-with-the-flower-database-user-password"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

-682
View File
@@ -1,682 +0,0 @@
/***
MochiKit.Async 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('Async', ['Base']);
MochiKit.Async.NAME = "MochiKit.Async";
MochiKit.Async.VERSION = "1.4.2";
MochiKit.Async.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.Async.toString = function () {
return this.__repr__();
};
/** @id MochiKit.Async.Deferred */
MochiKit.Async.Deferred = function (/* optional */ canceller) {
this.chain = [];
this.id = this._nextId();
this.fired = -1;
this.paused = 0;
this.results = [null, null];
this.canceller = canceller;
this.silentlyCancelled = false;
this.chained = false;
};
MochiKit.Async.Deferred.prototype = {
/** @id MochiKit.Async.Deferred.prototype.repr */
repr: function () {
var state;
if (this.fired == -1) {
state = 'unfired';
} else if (this.fired === 0) {
state = 'success';
} else {
state = 'error';
}
return 'Deferred(' + this.id + ', ' + state + ')';
},
toString: MochiKit.Base.forwardCall("repr"),
_nextId: MochiKit.Base.counter(),
/** @id MochiKit.Async.Deferred.prototype.cancel */
cancel: function () {
var self = MochiKit.Async;
if (this.fired == -1) {
if (this.canceller) {
this.canceller(this);
} else {
this.silentlyCancelled = true;
}
if (this.fired == -1) {
this.errback(new self.CancelledError(this));
}
} else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
this.results[0].cancel();
}
},
_resback: function (res) {
/***
The primitive that means either callback or errback
***/
this.fired = ((res instanceof Error) ? 1 : 0);
this.results[this.fired] = res;
this._fire();
},
_check: function () {
if (this.fired != -1) {
if (!this.silentlyCancelled) {
throw new MochiKit.Async.AlreadyCalledError(this);
}
this.silentlyCancelled = false;
return;
}
},
/** @id MochiKit.Async.Deferred.prototype.callback */
callback: function (res) {
this._check();
if (res instanceof MochiKit.Async.Deferred) {
throw new Error("Deferred instances can only be chained if they are the result of a callback");
}
this._resback(res);
},
/** @id MochiKit.Async.Deferred.prototype.errback */
errback: function (res) {
this._check();
var self = MochiKit.Async;
if (res instanceof self.Deferred) {
throw new Error("Deferred instances can only be chained if they are the result of a callback");
}
if (!(res instanceof Error)) {
res = new self.GenericError(res);
}
this._resback(res);
},
/** @id MochiKit.Async.Deferred.prototype.addBoth */
addBoth: function (fn) {
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
return this.addCallbacks(fn, fn);
},
/** @id MochiKit.Async.Deferred.prototype.addCallback */
addCallback: function (fn) {
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
return this.addCallbacks(fn, null);
},
/** @id MochiKit.Async.Deferred.prototype.addErrback */
addErrback: function (fn) {
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
return this.addCallbacks(null, fn);
},
/** @id MochiKit.Async.Deferred.prototype.addCallbacks */
addCallbacks: function (cb, eb) {
if (this.chained) {
throw new Error("Chained Deferreds can not be re-used");
}
this.chain.push([cb, eb]);
if (this.fired >= 0) {
this._fire();
}
return this;
},
_fire: function () {
/***
Used internally to exhaust the callback sequence when a result
is available.
***/
var chain = this.chain;
var fired = this.fired;
var res = this.results[fired];
var self = this;
var cb = null;
while (chain.length > 0 && this.paused === 0) {
// Array
var pair = chain.shift();
var f = pair[fired];
if (f === null) {
continue;
}
try {
res = f(res);
fired = ((res instanceof Error) ? 1 : 0);
if (res instanceof MochiKit.Async.Deferred) {
cb = function (res) {
self._resback(res);
self.paused--;
if ((self.paused === 0) && (self.fired >= 0)) {
self._fire();
}
};
this.paused++;
}
} catch (err) {
fired = 1;
if (!(err instanceof Error)) {
err = new MochiKit.Async.GenericError(err);
}
res = err;
}
}
this.fired = fired;
this.results[fired] = res;
if (cb && this.paused) {
// this is for "tail recursion" in case the dependent deferred
// is already fired
res.addBoth(cb);
res.chained = true;
}
}
};
MochiKit.Base.update(MochiKit.Async, {
/** @id MochiKit.Async.evalJSONRequest */
evalJSONRequest: function (req) {
return MochiKit.Base.evalJSON(req.responseText);
},
/** @id MochiKit.Async.succeed */
succeed: function (/* optional */result) {
var d = new MochiKit.Async.Deferred();
d.callback.apply(d, arguments);
return d;
},
/** @id MochiKit.Async.fail */
fail: function (/* optional */result) {
var d = new MochiKit.Async.Deferred();
d.errback.apply(d, arguments);
return d;
},
/** @id MochiKit.Async.getXMLHttpRequest */
getXMLHttpRequest: function () {
var self = arguments.callee;
if (!self.XMLHttpRequest) {
var tryThese = [
function () { return new XMLHttpRequest(); },
function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
function () {
throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
}
];
for (var i = 0; i < tryThese.length; i++) {
var func = tryThese[i];
try {
self.XMLHttpRequest = func;
return func();
} catch (e) {
// pass
}
}
}
return self.XMLHttpRequest();
},
_xhr_onreadystatechange: function (d) {
// MochiKit.Logging.logDebug('this.readyState', this.readyState);
var m = MochiKit.Base;
if (this.readyState == 4) {
// IE SUCKS
try {
this.onreadystatechange = null;
} catch (e) {
try {
this.onreadystatechange = m.noop;
} catch (e) {
}
}
var status = null;
try {
status = this.status;
if (!status && m.isNotEmpty(this.responseText)) {
// 0 or undefined seems to mean cached or local
status = 304;
}
} catch (e) {
// pass
// MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
}
// 200 is OK, 201 is CREATED, 204 is NO CONTENT
// 304 is NOT MODIFIED, 1223 is apparently a bug in IE
if (status == 200 || status == 201 || status == 204 ||
status == 304 || status == 1223) {
d.callback(this);
} else {
var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
if (err.number) {
// XXX: This seems to happen on page change
d.errback(err);
} else {
// XXX: this seems to happen when the server is unreachable
d.errback(err);
}
}
}
},
_xhr_canceller: function (req) {
// IE SUCKS
try {
req.onreadystatechange = null;
} catch (e) {
try {
req.onreadystatechange = MochiKit.Base.noop;
} catch (e) {
}
}
req.abort();
},
/** @id MochiKit.Async.sendXMLHttpRequest */
sendXMLHttpRequest: function (req, /* optional */ sendContent) {
if (typeof(sendContent) == "undefined" || sendContent === null) {
sendContent = "";
}
var m = MochiKit.Base;
var self = MochiKit.Async;
var d = new self.Deferred(m.partial(self._xhr_canceller, req));
try {
req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
req, d);
req.send(sendContent);
} catch (e) {
try {
req.onreadystatechange = null;
} catch (ignore) {
// pass
}
d.errback(e);
}
return d;
},
/** @id MochiKit.Async.doXHR */
doXHR: function (url, opts) {
/*
Work around a Firefox bug by dealing with XHR during
the next event loop iteration. Maybe it's this one:
https://bugzilla.mozilla.org/show_bug.cgi?id=249843
*/
var self = MochiKit.Async;
return self.callLater(0, self._doXHR, url, opts);
},
_doXHR: function (url, opts) {
var m = MochiKit.Base;
opts = m.update({
method: 'GET',
sendContent: ''
/*
queryString: undefined,
username: undefined,
password: undefined,
headers: undefined,
mimeType: undefined
*/
}, opts);
var self = MochiKit.Async;
var req = self.getXMLHttpRequest();
if (opts.queryString) {
var qs = m.queryString(opts.queryString);
if (qs) {
url += "?" + qs;
}
}
// Safari will send undefined:undefined, so we have to check.
// We can't use apply, since the function is native.
if ('username' in opts) {
req.open(opts.method, url, true, opts.username, opts.password);
} else {
req.open(opts.method, url, true);
}
if (req.overrideMimeType && opts.mimeType) {
req.overrideMimeType(opts.mimeType);
}
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (opts.headers) {
var headers = opts.headers;
if (!m.isArrayLike(headers)) {
headers = m.items(headers);
}
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
var name = header[0];
var value = header[1];
req.setRequestHeader(name, value);
}
}
return self.sendXMLHttpRequest(req, opts.sendContent);
},
_buildURL: function (url/*, ...*/) {
if (arguments.length > 1) {
var m = MochiKit.Base;
var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
if (qs) {
return url + "?" + qs;
}
}
return url;
},
/** @id MochiKit.Async.doSimpleXMLHttpRequest */
doSimpleXMLHttpRequest: function (url/*, ...*/) {
var self = MochiKit.Async;
url = self._buildURL.apply(self, arguments);
return self.doXHR(url);
},
/** @id MochiKit.Async.loadJSONDoc */
loadJSONDoc: function (url/*, ...*/) {
var self = MochiKit.Async;
url = self._buildURL.apply(self, arguments);
var d = self.doXHR(url, {
'mimeType': 'text/plain',
'headers': [['Accept', 'application/json']]
});
d = d.addCallback(self.evalJSONRequest);
return d;
},
/** @id MochiKit.Async.wait */
wait: function (seconds, /* optional */value) {
var d = new MochiKit.Async.Deferred();
var m = MochiKit.Base;
if (typeof(value) != 'undefined') {
d.addCallback(function () { return value; });
}
var timeout = setTimeout(
m.bind("callback", d),
Math.floor(seconds * 1000));
d.canceller = function () {
try {
clearTimeout(timeout);
} catch (e) {
// pass
}
};
return d;
},
/** @id MochiKit.Async.callLater */
callLater: function (seconds, func) {
var m = MochiKit.Base;
var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
return MochiKit.Async.wait(seconds).addCallback(
function (res) { return pfunc(); }
);
}
});
/** @id MochiKit.Async.DeferredLock */
MochiKit.Async.DeferredLock = function () {
this.waiting = [];
this.locked = false;
this.id = this._nextId();
};
MochiKit.Async.DeferredLock.prototype = {
__class__: MochiKit.Async.DeferredLock,
/** @id MochiKit.Async.DeferredLock.prototype.acquire */
acquire: function () {
var d = new MochiKit.Async.Deferred();
if (this.locked) {
this.waiting.push(d);
} else {
this.locked = true;
d.callback(this);
}
return d;
},
/** @id MochiKit.Async.DeferredLock.prototype.release */
release: function () {
if (!this.locked) {
throw TypeError("Tried to release an unlocked DeferredLock");
}
this.locked = false;
if (this.waiting.length > 0) {
this.locked = true;
this.waiting.shift().callback(this);
}
},
_nextId: MochiKit.Base.counter(),
repr: function () {
var state;
if (this.locked) {
state = 'locked, ' + this.waiting.length + ' waiting';
} else {
state = 'unlocked';
}
return 'DeferredLock(' + this.id + ', ' + state + ')';
},
toString: MochiKit.Base.forwardCall("repr")
};
/** @id MochiKit.Async.DeferredList */
MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
// call parent constructor
MochiKit.Async.Deferred.apply(this, [canceller]);
this.list = list;
var resultList = [];
this.resultList = resultList;
this.finishedCount = 0;
this.fireOnOneCallback = fireOnOneCallback;
this.fireOnOneErrback = fireOnOneErrback;
this.consumeErrors = consumeErrors;
var cb = MochiKit.Base.bind(this._cbDeferred, this);
for (var i = 0; i < list.length; i++) {
var d = list[i];
resultList.push(undefined);
d.addCallback(cb, i, true);
d.addErrback(cb, i, false);
}
if (list.length === 0 && !fireOnOneCallback) {
this.callback(this.resultList);
}
};
MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
this.resultList[index] = [succeeded, result];
this.finishedCount += 1;
if (this.fired == -1) {
if (succeeded && this.fireOnOneCallback) {
this.callback([index, result]);
} else if (!succeeded && this.fireOnOneErrback) {
this.errback(result);
} else if (this.finishedCount == this.list.length) {
this.callback(this.resultList);
}
}
if (!succeeded && this.consumeErrors) {
result = null;
}
return result;
};
/** @id MochiKit.Async.gatherResults */
MochiKit.Async.gatherResults = function (deferredList) {
var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
d.addCallback(function (results) {
var ret = [];
for (var i = 0; i < results.length; i++) {
ret.push(results[i][1]);
}
return ret;
});
return d;
};
/** @id MochiKit.Async.maybeDeferred */
MochiKit.Async.maybeDeferred = function (func) {
var self = MochiKit.Async;
var result;
try {
var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
if (r instanceof self.Deferred) {
result = r;
} else if (r instanceof Error) {
result = self.fail(r);
} else {
result = self.succeed(r);
}
} catch (e) {
result = self.fail(e);
}
return result;
};
MochiKit.Async.EXPORT = [
"AlreadyCalledError",
"CancelledError",
"BrowserComplianceError",
"GenericError",
"XMLHttpRequestError",
"Deferred",
"succeed",
"fail",
"getXMLHttpRequest",
"doSimpleXMLHttpRequest",
"loadJSONDoc",
"wait",
"callLater",
"sendXMLHttpRequest",
"DeferredLock",
"DeferredList",
"gatherResults",
"maybeDeferred",
"doXHR"
];
MochiKit.Async.EXPORT_OK = [
"evalJSONRequest"
];
MochiKit.Async.__new__ = function () {
var m = MochiKit.Base;
var ne = m.partial(m._newNamedError, this);
ne("AlreadyCalledError",
/** @id MochiKit.Async.AlreadyCalledError */
function (deferred) {
/***
Raised by the Deferred if callback or errback happens
after it was already fired.
***/
this.deferred = deferred;
}
);
ne("CancelledError",
/** @id MochiKit.Async.CancelledError */
function (deferred) {
/***
Raised by the Deferred cancellation mechanism.
***/
this.deferred = deferred;
}
);
ne("BrowserComplianceError",
/** @id MochiKit.Async.BrowserComplianceError */
function (msg) {
/***
Raised when the JavaScript runtime is not capable of performing
the given function. Technically, this should really never be
raised because a non-conforming JavaScript runtime probably
isn't going to support exceptions in the first place.
***/
this.message = msg;
}
);
ne("GenericError",
/** @id MochiKit.Async.GenericError */
function (msg) {
this.message = msg;
}
);
ne("XMLHttpRequestError",
/** @id MochiKit.Async.XMLHttpRequestError */
function (req, msg) {
/***
Raised when an XMLHttpRequest does not complete for any reason.
***/
this.req = req;
this.message = msg;
try {
// Strange but true that this can raise in some cases.
this.number = req.status;
} catch (e) {
// pass
}
}
);
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
};
MochiKit.Async.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Async);
File diff suppressed because it is too large Load Diff
-863
View File
@@ -1,863 +0,0 @@
/***
MochiKit.Color 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito and others. All rights Reserved.
***/
MochiKit.Base._deps('Color', ['Base', 'DOM', 'Style']);
MochiKit.Color.NAME = "MochiKit.Color";
MochiKit.Color.VERSION = "1.4.2";
MochiKit.Color.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.Color.toString = function () {
return this.__repr__();
};
/** @id MochiKit.Color.Color */
MochiKit.Color.Color = function (red, green, blue, alpha) {
if (typeof(alpha) == 'undefined' || alpha === null) {
alpha = 1.0;
}
this.rgb = {
r: red,
g: green,
b: blue,
a: alpha
};
};
// Prototype methods
MochiKit.Color.Color.prototype = {
__class__: MochiKit.Color.Color,
/** @id MochiKit.Color.Color.prototype.colorWithAlpha */
colorWithAlpha: function (alpha) {
var rgb = this.rgb;
var m = MochiKit.Color;
return m.Color.fromRGB(rgb.r, rgb.g, rgb.b, alpha);
},
/** @id MochiKit.Color.Color.prototype.colorWithHue */
colorWithHue: function (hue) {
// get an HSL model, and set the new hue...
var hsl = this.asHSL();
hsl.h = hue;
var m = MochiKit.Color;
// convert back to RGB...
return m.Color.fromHSL(hsl);
},
/** @id MochiKit.Color.Color.prototype.colorWithSaturation */
colorWithSaturation: function (saturation) {
// get an HSL model, and set the new hue...
var hsl = this.asHSL();
hsl.s = saturation;
var m = MochiKit.Color;
// convert back to RGB...
return m.Color.fromHSL(hsl);
},
/** @id MochiKit.Color.Color.prototype.colorWithLightness */
colorWithLightness: function (lightness) {
// get an HSL model, and set the new hue...
var hsl = this.asHSL();
hsl.l = lightness;
var m = MochiKit.Color;
// convert back to RGB...
return m.Color.fromHSL(hsl);
},
/** @id MochiKit.Color.Color.prototype.darkerColorWithLevel */
darkerColorWithLevel: function (level) {
var hsl = this.asHSL();
hsl.l = Math.max(hsl.l - level, 0);
var m = MochiKit.Color;
return m.Color.fromHSL(hsl);
},
/** @id MochiKit.Color.Color.prototype.lighterColorWithLevel */
lighterColorWithLevel: function (level) {
var hsl = this.asHSL();
hsl.l = Math.min(hsl.l + level, 1);
var m = MochiKit.Color;
return m.Color.fromHSL(hsl);
},
/** @id MochiKit.Color.Color.prototype.blendedColor */
blendedColor: function (other, /* optional */ fraction) {
if (typeof(fraction) == 'undefined' || fraction === null) {
fraction = 0.5;
}
var sf = 1.0 - fraction;
var s = this.rgb;
var d = other.rgb;
var df = fraction;
return MochiKit.Color.Color.fromRGB(
(s.r * sf) + (d.r * df),
(s.g * sf) + (d.g * df),
(s.b * sf) + (d.b * df),
(s.a * sf) + (d.a * df)
);
},
/** @id MochiKit.Color.Color.prototype.compareRGB */
compareRGB: function (other) {
var a = this.asRGB();
var b = other.asRGB();
return MochiKit.Base.compare(
[a.r, a.g, a.b, a.a],
[b.r, b.g, b.b, b.a]
);
},
/** @id MochiKit.Color.Color.prototype.isLight */
isLight: function () {
return this.asHSL().b > 0.5;
},
/** @id MochiKit.Color.Color.prototype.isDark */
isDark: function () {
return (!this.isLight());
},
/** @id MochiKit.Color.Color.prototype.toHSLString */
toHSLString: function () {
var c = this.asHSL();
var ccc = MochiKit.Color.clampColorComponent;
var rval = this._hslString;
if (!rval) {
var mid = (
ccc(c.h, 360).toFixed(0)
+ "," + ccc(c.s, 100).toPrecision(4) + "%"
+ "," + ccc(c.l, 100).toPrecision(4) + "%"
);
var a = c.a;
if (a >= 1) {
a = 1;
rval = "hsl(" + mid + ")";
} else {
if (a <= 0) {
a = 0;
}
rval = "hsla(" + mid + "," + a + ")";
}
this._hslString = rval;
}
return rval;
},
/** @id MochiKit.Color.Color.prototype.toRGBString */
toRGBString: function () {
var c = this.rgb;
var ccc = MochiKit.Color.clampColorComponent;
var rval = this._rgbString;
if (!rval) {
var mid = (
ccc(c.r, 255).toFixed(0)
+ "," + ccc(c.g, 255).toFixed(0)
+ "," + ccc(c.b, 255).toFixed(0)
);
if (c.a != 1) {
rval = "rgba(" + mid + "," + c.a + ")";
} else {
rval = "rgb(" + mid + ")";
}
this._rgbString = rval;
}
return rval;
},
/** @id MochiKit.Color.Color.prototype.asRGB */
asRGB: function () {
return MochiKit.Base.clone(this.rgb);
},
/** @id MochiKit.Color.Color.prototype.toHexString */
toHexString: function () {
var m = MochiKit.Color;
var c = this.rgb;
var ccc = MochiKit.Color.clampColorComponent;
var rval = this._hexString;
if (!rval) {
rval = ("#" +
m.toColorPart(ccc(c.r, 255)) +
m.toColorPart(ccc(c.g, 255)) +
m.toColorPart(ccc(c.b, 255))
);
this._hexString = rval;
}
return rval;
},
/** @id MochiKit.Color.Color.prototype.asHSV */
asHSV: function () {
var hsv = this.hsv;
var c = this.rgb;
if (typeof(hsv) == 'undefined' || hsv === null) {
hsv = MochiKit.Color.rgbToHSV(this.rgb);
this.hsv = hsv;
}
return MochiKit.Base.clone(hsv);
},
/** @id MochiKit.Color.Color.prototype.asHSL */
asHSL: function () {
var hsl = this.hsl;
var c = this.rgb;
if (typeof(hsl) == 'undefined' || hsl === null) {
hsl = MochiKit.Color.rgbToHSL(this.rgb);
this.hsl = hsl;
}
return MochiKit.Base.clone(hsl);
},
/** @id MochiKit.Color.Color.prototype.toString */
toString: function () {
return this.toRGBString();
},
/** @id MochiKit.Color.Color.prototype.repr */
repr: function () {
var c = this.rgb;
var col = [c.r, c.g, c.b, c.a];
return this.__class__.NAME + "(" + col.join(", ") + ")";
}
};
// Constructor methods
MochiKit.Base.update(MochiKit.Color.Color, {
/** @id MochiKit.Color.Color.fromRGB */
fromRGB: function (red, green, blue, alpha) {
// designated initializer
var Color = MochiKit.Color.Color;
if (arguments.length == 1) {
var rgb = red;
red = rgb.r;
green = rgb.g;
blue = rgb.b;
if (typeof(rgb.a) == 'undefined') {
alpha = undefined;
} else {
alpha = rgb.a;
}
}
return new Color(red, green, blue, alpha);
},
/** @id MochiKit.Color.Color.fromHSL */
fromHSL: function (hue, saturation, lightness, alpha) {
var m = MochiKit.Color;
return m.Color.fromRGB(m.hslToRGB.apply(m, arguments));
},
/** @id MochiKit.Color.Color.fromHSV */
fromHSV: function (hue, saturation, value, alpha) {
var m = MochiKit.Color;
return m.Color.fromRGB(m.hsvToRGB.apply(m, arguments));
},
/** @id MochiKit.Color.Color.fromName */
fromName: function (name) {
var Color = MochiKit.Color.Color;
// Opera 9 seems to "quote" named colors(?!)
if (name.charAt(0) == '"') {
name = name.substr(1, name.length - 2);
}
var htmlColor = Color._namedColors[name.toLowerCase()];
if (typeof(htmlColor) == 'string') {
return Color.fromHexString(htmlColor);
} else if (name == "transparent") {
return Color.transparentColor();
}
return null;
},
/** @id MochiKit.Color.Color.fromString */
fromString: function (colorString) {
var self = MochiKit.Color.Color;
var three = colorString.substr(0, 3);
if (three == "rgb") {
return self.fromRGBString(colorString);
} else if (three == "hsl") {
return self.fromHSLString(colorString);
} else if (colorString.charAt(0) == "#") {
return self.fromHexString(colorString);
}
return self.fromName(colorString);
},
/** @id MochiKit.Color.Color.fromHexString */
fromHexString: function (hexCode) {
if (hexCode.charAt(0) == '#') {
hexCode = hexCode.substring(1);
}
var components = [];
var i, hex;
if (hexCode.length == 3) {
for (i = 0; i < 3; i++) {
hex = hexCode.substr(i, 1);
components.push(parseInt(hex + hex, 16) / 255.0);
}
} else {
for (i = 0; i < 6; i += 2) {
hex = hexCode.substr(i, 2);
components.push(parseInt(hex, 16) / 255.0);
}
}
var Color = MochiKit.Color.Color;
return Color.fromRGB.apply(Color, components);
},
_fromColorString: function (pre, method, scales, colorCode) {
// parses either HSL or RGB
if (colorCode.indexOf(pre) === 0) {
colorCode = colorCode.substring(colorCode.indexOf("(", 3) + 1, colorCode.length - 1);
}
var colorChunks = colorCode.split(/\s*,\s*/);
var colorFloats = [];
for (var i = 0; i < colorChunks.length; i++) {
var c = colorChunks[i];
var val;
var three = c.substring(c.length - 3);
if (c.charAt(c.length - 1) == '%') {
val = 0.01 * parseFloat(c.substring(0, c.length - 1));
} else if (three == "deg") {
val = parseFloat(c) / 360.0;
} else if (three == "rad") {
val = parseFloat(c) / (Math.PI * 2);
} else {
val = scales[i] * parseFloat(c);
}
colorFloats.push(val);
}
return this[method].apply(this, colorFloats);
},
/** @id MochiKit.Color.Color.fromComputedStyle */
fromComputedStyle: function (elem, style) {
var d = MochiKit.DOM;
var cls = MochiKit.Color.Color;
for (elem = d.getElement(elem); elem; elem = elem.parentNode) {
var actualColor = MochiKit.Style.getStyle.apply(d, arguments);
if (!actualColor) {
continue;
}
var color = cls.fromString(actualColor);
if (!color) {
break;
}
if (color.asRGB().a > 0) {
return color;
}
}
return null;
},
/** @id MochiKit.Color.Color.fromBackground */
fromBackground: function (elem) {
var cls = MochiKit.Color.Color;
return cls.fromComputedStyle(
elem, "backgroundColor", "background-color") || cls.whiteColor();
},
/** @id MochiKit.Color.Color.fromText */
fromText: function (elem) {
var cls = MochiKit.Color.Color;
return cls.fromComputedStyle(
elem, "color", "color") || cls.blackColor();
},
/** @id MochiKit.Color.Color.namedColors */
namedColors: function () {
return MochiKit.Base.clone(MochiKit.Color.Color._namedColors);
}
});
// Module level functions
MochiKit.Base.update(MochiKit.Color, {
/** @id MochiKit.Color.clampColorComponent */
clampColorComponent: function (v, scale) {
v *= scale;
if (v < 0) {
return 0;
} else if (v > scale) {
return scale;
} else {
return v;
}
},
_hslValue: function (n1, n2, hue) {
if (hue > 6.0) {
hue -= 6.0;
} else if (hue < 0.0) {
hue += 6.0;
}
var val;
if (hue < 1.0) {
val = n1 + (n2 - n1) * hue;
} else if (hue < 3.0) {
val = n2;
} else if (hue < 4.0) {
val = n1 + (n2 - n1) * (4.0 - hue);
} else {
val = n1;
}
return val;
},
/** @id MochiKit.Color.hsvToRGB */
hsvToRGB: function (hue, saturation, value, alpha) {
if (arguments.length == 1) {
var hsv = hue;
hue = hsv.h;
saturation = hsv.s;
value = hsv.v;
alpha = hsv.a;
}
var red;
var green;
var blue;
if (saturation === 0) {
red = value;
green = value;
blue = value;
} else {
var i = Math.floor(hue * 6);
var f = (hue * 6) - i;
var p = value * (1 - saturation);
var q = value * (1 - (saturation * f));
var t = value * (1 - (saturation * (1 - f)));
switch (i) {
case 1: red = q; green = value; blue = p; break;
case 2: red = p; green = value; blue = t; break;
case 3: red = p; green = q; blue = value; break;
case 4: red = t; green = p; blue = value; break;
case 5: red = value; green = p; blue = q; break;
case 6: // fall through
case 0: red = value; green = t; blue = p; break;
}
}
return {
r: red,
g: green,
b: blue,
a: alpha
};
},
/** @id MochiKit.Color.hslToRGB */
hslToRGB: function (hue, saturation, lightness, alpha) {
if (arguments.length == 1) {
var hsl = hue;
hue = hsl.h;
saturation = hsl.s;
lightness = hsl.l;
alpha = hsl.a;
}
var red;
var green;
var blue;
if (saturation === 0) {
red = lightness;
green = lightness;
blue = lightness;
} else {
var m2;
if (lightness <= 0.5) {
m2 = lightness * (1.0 + saturation);
} else {
m2 = lightness + saturation - (lightness * saturation);
}
var m1 = (2.0 * lightness) - m2;
var f = MochiKit.Color._hslValue;
var h6 = hue * 6.0;
red = f(m1, m2, h6 + 2);
green = f(m1, m2, h6);
blue = f(m1, m2, h6 - 2);
}
return {
r: red,
g: green,
b: blue,
a: alpha
};
},
/** @id MochiKit.Color.rgbToHSV */
rgbToHSV: function (red, green, blue, alpha) {
if (arguments.length == 1) {
var rgb = red;
red = rgb.r;
green = rgb.g;
blue = rgb.b;
alpha = rgb.a;
}
var max = Math.max(Math.max(red, green), blue);
var min = Math.min(Math.min(red, green), blue);
var hue;
var saturation;
var value = max;
if (min == max) {
hue = 0;
saturation = 0;
} else {
var delta = (max - min);
saturation = delta / max;
if (red == max) {
hue = (green - blue) / delta;
} else if (green == max) {
hue = 2 + ((blue - red) / delta);
} else {
hue = 4 + ((red - green) / delta);
}
hue /= 6;
if (hue < 0) {
hue += 1;
}
if (hue > 1) {
hue -= 1;
}
}
return {
h: hue,
s: saturation,
v: value,
a: alpha
};
},
/** @id MochiKit.Color.rgbToHSL */
rgbToHSL: function (red, green, blue, alpha) {
if (arguments.length == 1) {
var rgb = red;
red = rgb.r;
green = rgb.g;
blue = rgb.b;
alpha = rgb.a;
}
var max = Math.max(red, Math.max(green, blue));
var min = Math.min(red, Math.min(green, blue));
var hue;
var saturation;
var lightness = (max + min) / 2.0;
var delta = max - min;
if (delta === 0) {
hue = 0;
saturation = 0;
} else {
if (lightness <= 0.5) {
saturation = delta / (max + min);
} else {
saturation = delta / (2 - max - min);
}
if (red == max) {
hue = (green - blue) / delta;
} else if (green == max) {
hue = 2 + ((blue - red) / delta);
} else {
hue = 4 + ((red - green) / delta);
}
hue /= 6;
if (hue < 0) {
hue += 1;
}
if (hue > 1) {
hue -= 1;
}
}
return {
h: hue,
s: saturation,
l: lightness,
a: alpha
};
},
/** @id MochiKit.Color.toColorPart */
toColorPart: function (num) {
num = Math.round(num);
var digits = num.toString(16);
if (num < 16) {
return '0' + digits;
}
return digits;
},
__new__: function () {
var m = MochiKit.Base;
/** @id MochiKit.Color.fromRGBString */
this.Color.fromRGBString = m.bind(
this.Color._fromColorString, this.Color, "rgb", "fromRGB",
[1.0/255.0, 1.0/255.0, 1.0/255.0, 1]
);
/** @id MochiKit.Color.fromHSLString */
this.Color.fromHSLString = m.bind(
this.Color._fromColorString, this.Color, "hsl", "fromHSL",
[1.0/360.0, 0.01, 0.01, 1]
);
var third = 1.0 / 3.0;
/** @id MochiKit.Color.colors */
var colors = {
// NSColor colors plus transparent
/** @id MochiKit.Color.blackColor */
black: [0, 0, 0],
/** @id MochiKit.Color.blueColor */
blue: [0, 0, 1],
/** @id MochiKit.Color.brownColor */
brown: [0.6, 0.4, 0.2],
/** @id MochiKit.Color.cyanColor */
cyan: [0, 1, 1],
/** @id MochiKit.Color.darkGrayColor */
darkGray: [third, third, third],
/** @id MochiKit.Color.grayColor */
gray: [0.5, 0.5, 0.5],
/** @id MochiKit.Color.greenColor */
green: [0, 1, 0],
/** @id MochiKit.Color.lightGrayColor */
lightGray: [2 * third, 2 * third, 2 * third],
/** @id MochiKit.Color.magentaColor */
magenta: [1, 0, 1],
/** @id MochiKit.Color.orangeColor */
orange: [1, 0.5, 0],
/** @id MochiKit.Color.purpleColor */
purple: [0.5, 0, 0.5],
/** @id MochiKit.Color.redColor */
red: [1, 0, 0],
/** @id MochiKit.Color.transparentColor */
transparent: [0, 0, 0, 0],
/** @id MochiKit.Color.whiteColor */
white: [1, 1, 1],
/** @id MochiKit.Color.yellowColor */
yellow: [1, 1, 0]
};
var makeColor = function (name, r, g, b, a) {
var rval = this.fromRGB(r, g, b, a);
this[name] = function () { return rval; };
return rval;
};
for (var k in colors) {
var name = k + "Color";
var bindArgs = m.concat(
[makeColor, this.Color, name],
colors[k]
);
this.Color[name] = m.bind.apply(null, bindArgs);
}
var isColor = function () {
for (var i = 0; i < arguments.length; i++) {
if (!(arguments[i] instanceof MochiKit.Color.Color)) {
return false;
}
}
return true;
};
var compareColor = function (a, b) {
return a.compareRGB(b);
};
m.nameFunctions(this);
m.registerComparator(this.Color.NAME, isColor, compareColor);
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": m.concat(this.EXPORT, this.EXPORT_OK)
};
}
});
MochiKit.Color.EXPORT = [
"Color"
];
MochiKit.Color.EXPORT_OK = [
"clampColorComponent",
"rgbToHSL",
"hslToRGB",
"rgbToHSV",
"hsvToRGB",
"toColorPart"
];
MochiKit.Color.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Color);
// Full table of css3 X11 colors <http://www.w3.org/TR/css3-color/#X11COLORS>
MochiKit.Color.Color._namedColors = {
aliceblue: "#f0f8ff",
antiquewhite: "#faebd7",
aqua: "#00ffff",
aquamarine: "#7fffd4",
azure: "#f0ffff",
beige: "#f5f5dc",
bisque: "#ffe4c4",
black: "#000000",
blanchedalmond: "#ffebcd",
blue: "#0000ff",
blueviolet: "#8a2be2",
brown: "#a52a2a",
burlywood: "#deb887",
cadetblue: "#5f9ea0",
chartreuse: "#7fff00",
chocolate: "#d2691e",
coral: "#ff7f50",
cornflowerblue: "#6495ed",
cornsilk: "#fff8dc",
crimson: "#dc143c",
cyan: "#00ffff",
darkblue: "#00008b",
darkcyan: "#008b8b",
darkgoldenrod: "#b8860b",
darkgray: "#a9a9a9",
darkgreen: "#006400",
darkgrey: "#a9a9a9",
darkkhaki: "#bdb76b",
darkmagenta: "#8b008b",
darkolivegreen: "#556b2f",
darkorange: "#ff8c00",
darkorchid: "#9932cc",
darkred: "#8b0000",
darksalmon: "#e9967a",
darkseagreen: "#8fbc8f",
darkslateblue: "#483d8b",
darkslategray: "#2f4f4f",
darkslategrey: "#2f4f4f",
darkturquoise: "#00ced1",
darkviolet: "#9400d3",
deeppink: "#ff1493",
deepskyblue: "#00bfff",
dimgray: "#696969",
dimgrey: "#696969",
dodgerblue: "#1e90ff",
firebrick: "#b22222",
floralwhite: "#fffaf0",
forestgreen: "#228b22",
fuchsia: "#ff00ff",
gainsboro: "#dcdcdc",
ghostwhite: "#f8f8ff",
gold: "#ffd700",
goldenrod: "#daa520",
gray: "#808080",
green: "#008000",
greenyellow: "#adff2f",
grey: "#808080",
honeydew: "#f0fff0",
hotpink: "#ff69b4",
indianred: "#cd5c5c",
indigo: "#4b0082",
ivory: "#fffff0",
khaki: "#f0e68c",
lavender: "#e6e6fa",
lavenderblush: "#fff0f5",
lawngreen: "#7cfc00",
lemonchiffon: "#fffacd",
lightblue: "#add8e6",
lightcoral: "#f08080",
lightcyan: "#e0ffff",
lightgoldenrodyellow: "#fafad2",
lightgray: "#d3d3d3",
lightgreen: "#90ee90",
lightgrey: "#d3d3d3",
lightpink: "#ffb6c1",
lightsalmon: "#ffa07a",
lightseagreen: "#20b2aa",
lightskyblue: "#87cefa",
lightslategray: "#778899",
lightslategrey: "#778899",
lightsteelblue: "#b0c4de",
lightyellow: "#ffffe0",
lime: "#00ff00",
limegreen: "#32cd32",
linen: "#faf0e6",
magenta: "#ff00ff",
maroon: "#800000",
mediumaquamarine: "#66cdaa",
mediumblue: "#0000cd",
mediumorchid: "#ba55d3",
mediumpurple: "#9370db",
mediumseagreen: "#3cb371",
mediumslateblue: "#7b68ee",
mediumspringgreen: "#00fa9a",
mediumturquoise: "#48d1cc",
mediumvioletred: "#c71585",
midnightblue: "#191970",
mintcream: "#f5fffa",
mistyrose: "#ffe4e1",
moccasin: "#ffe4b5",
navajowhite: "#ffdead",
navy: "#000080",
oldlace: "#fdf5e6",
olive: "#808000",
olivedrab: "#6b8e23",
orange: "#ffa500",
orangered: "#ff4500",
orchid: "#da70d6",
palegoldenrod: "#eee8aa",
palegreen: "#98fb98",
paleturquoise: "#afeeee",
palevioletred: "#db7093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
purple: "#800080",
red: "#ff0000",
rosybrown: "#bc8f8f",
royalblue: "#4169e1",
saddlebrown: "#8b4513",
salmon: "#fa8072",
sandybrown: "#f4a460",
seagreen: "#2e8b57",
seashell: "#fff5ee",
sienna: "#a0522d",
silver: "#c0c0c0",
skyblue: "#87ceeb",
slateblue: "#6a5acd",
slategray: "#708090",
slategrey: "#708090",
snow: "#fffafa",
springgreen: "#00ff7f",
steelblue: "#4682b4",
tan: "#d2b48c",
teal: "#008080",
thistle: "#d8bfd8",
tomato: "#ff6347",
turquoise: "#40e0d0",
violet: "#ee82ee",
wheat: "#f5deb3",
white: "#ffffff",
whitesmoke: "#f5f5f5",
yellow: "#ffff00",
yellowgreen: "#9acd32"
};
File diff suppressed because it is too large Load Diff
-222
View File
@@ -1,222 +0,0 @@
/***
MochiKit.DateTime 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('DateTime', ['Base']);
MochiKit.DateTime.NAME = "MochiKit.DateTime";
MochiKit.DateTime.VERSION = "1.4.2";
MochiKit.DateTime.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.DateTime.toString = function () {
return this.__repr__();
};
/** @id MochiKit.DateTime.isoDate */
MochiKit.DateTime.isoDate = function (str) {
str = str + "";
if (typeof(str) != "string" || str.length === 0) {
return null;
}
var iso = str.split('-');
if (iso.length === 0) {
return null;
}
var date = new Date(iso[0], iso[1] - 1, iso[2]);
date.setFullYear(iso[0]);
date.setMonth(iso[1] - 1);
date.setDate(iso[2]);
return date;
};
MochiKit.DateTime._isoRegexp = /(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/;
/** @id MochiKit.DateTime.isoTimestamp */
MochiKit.DateTime.isoTimestamp = function (str) {
str = str + "";
if (typeof(str) != "string" || str.length === 0) {
return null;
}
var res = str.match(MochiKit.DateTime._isoRegexp);
if (typeof(res) == "undefined" || res === null) {
return null;
}
var year, month, day, hour, min, sec, msec;
year = parseInt(res[1], 10);
if (typeof(res[2]) == "undefined" || res[2] === '') {
return new Date(year);
}
month = parseInt(res[2], 10) - 1;
day = parseInt(res[3], 10);
if (typeof(res[4]) == "undefined" || res[4] === '') {
return new Date(year, month, day);
}
hour = parseInt(res[4], 10);
min = parseInt(res[5], 10);
sec = (typeof(res[6]) != "undefined" && res[6] !== '') ? parseInt(res[6], 10) : 0;
if (typeof(res[7]) != "undefined" && res[7] !== '') {
msec = Math.round(1000.0 * parseFloat("0." + res[7]));
} else {
msec = 0;
}
if ((typeof(res[8]) == "undefined" || res[8] === '') && (typeof(res[9]) == "undefined" || res[9] === '')) {
return new Date(year, month, day, hour, min, sec, msec);
}
var ofs;
if (typeof(res[9]) != "undefined" && res[9] !== '') {
ofs = parseInt(res[10], 10) * 3600000;
if (typeof(res[11]) != "undefined" && res[11] !== '') {
ofs += parseInt(res[11], 10) * 60000;
}
if (res[9] == "-") {
ofs = -ofs;
}
} else {
ofs = 0;
}
return new Date(Date.UTC(year, month, day, hour, min, sec, msec) - ofs);
};
/** @id MochiKit.DateTime.toISOTime */
MochiKit.DateTime.toISOTime = function (date, realISO/* = false */) {
if (typeof(date) == "undefined" || date === null) {
return null;
}
var hh = date.getHours();
var mm = date.getMinutes();
var ss = date.getSeconds();
var lst = [
((realISO && (hh < 10)) ? "0" + hh : hh),
((mm < 10) ? "0" + mm : mm),
((ss < 10) ? "0" + ss : ss)
];
return lst.join(":");
};
/** @id MochiKit.DateTime.toISOTimeStamp */
MochiKit.DateTime.toISOTimestamp = function (date, realISO/* = false*/) {
if (typeof(date) == "undefined" || date === null) {
return null;
}
var sep = realISO ? "T" : " ";
var foot = realISO ? "Z" : "";
if (realISO) {
date = new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
}
return MochiKit.DateTime.toISODate(date) + sep + MochiKit.DateTime.toISOTime(date, realISO) + foot;
};
/** @id MochiKit.DateTime.toISODate */
MochiKit.DateTime.toISODate = function (date) {
if (typeof(date) == "undefined" || date === null) {
return null;
}
var _padTwo = MochiKit.DateTime._padTwo;
var _padFour = MochiKit.DateTime._padFour;
return [
_padFour(date.getFullYear()),
_padTwo(date.getMonth() + 1),
_padTwo(date.getDate())
].join("-");
};
/** @id MochiKit.DateTime.americanDate */
MochiKit.DateTime.americanDate = function (d) {
d = d + "";
if (typeof(d) != "string" || d.length === 0) {
return null;
}
var a = d.split('/');
return new Date(a[2], a[0] - 1, a[1]);
};
MochiKit.DateTime._padTwo = function (n) {
return (n > 9) ? n : "0" + n;
};
MochiKit.DateTime._padFour = function(n) {
switch(n.toString().length) {
case 1: return "000" + n; break;
case 2: return "00" + n; break;
case 3: return "0" + n; break;
case 4:
default:
return n;
}
};
/** @id MochiKit.DateTime.toPaddedAmericanDate */
MochiKit.DateTime.toPaddedAmericanDate = function (d) {
if (typeof(d) == "undefined" || d === null) {
return null;
}
var _padTwo = MochiKit.DateTime._padTwo;
return [
_padTwo(d.getMonth() + 1),
_padTwo(d.getDate()),
d.getFullYear()
].join('/');
};
/** @id MochiKit.DateTime.toAmericanDate */
MochiKit.DateTime.toAmericanDate = function (d) {
if (typeof(d) == "undefined" || d === null) {
return null;
}
return [d.getMonth() + 1, d.getDate(), d.getFullYear()].join('/');
};
MochiKit.DateTime.EXPORT = [
"isoDate",
"isoTimestamp",
"toISOTime",
"toISOTimestamp",
"toISODate",
"americanDate",
"toPaddedAmericanDate",
"toAmericanDate"
];
MochiKit.DateTime.EXPORT_OK = [];
MochiKit.DateTime.EXPORT_TAGS = {
":common": MochiKit.DateTime.EXPORT,
":all": MochiKit.DateTime.EXPORT
};
MochiKit.DateTime.__new__ = function () {
// MochiKit.Base.nameFunctions(this);
var base = this.NAME + ".";
for (var k in this) {
var o = this[k];
if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') {
try {
o.NAME = base + k;
} catch (e) {
// pass
}
}
}
};
MochiKit.DateTime.__new__();
if (typeof(MochiKit.Base) != "undefined") {
MochiKit.Base._exportSymbols(this, MochiKit.DateTime);
} else {
(function (globals, module) {
if ((typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined')
|| (MochiKit.__export__ === false)) {
var all = module.EXPORT_TAGS[":all"];
for (var i = 0; i < all.length; i++) {
globals[all[i]] = module[all[i]];
}
}
})(this, MochiKit.DateTime);
}
-793
View File
@@ -1,793 +0,0 @@
/***
MochiKit.DragAndDrop 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
Mochi-ized By Thomas Herve (_firstname_@nimail.org)
***/
MochiKit.Base._deps('DragAndDrop', ['Base', 'Iter', 'DOM', 'Signal', 'Visual', 'Position']);
MochiKit.DragAndDrop.NAME = 'MochiKit.DragAndDrop';
MochiKit.DragAndDrop.VERSION = '1.4.2';
MochiKit.DragAndDrop.__repr__ = function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
};
MochiKit.DragAndDrop.toString = function () {
return this.__repr__();
};
MochiKit.DragAndDrop.EXPORT = [
"Droppable",
"Draggable"
];
MochiKit.DragAndDrop.EXPORT_OK = [
"Droppables",
"Draggables"
];
MochiKit.DragAndDrop.Droppables = {
/***
Manage all droppables. Shouldn't be used, use the Droppable object instead.
***/
drops: [],
remove: function (element) {
this.drops = MochiKit.Base.filter(function (d) {
return d.element != MochiKit.DOM.getElement(element);
}, this.drops);
},
register: function (drop) {
this.drops.push(drop);
},
unregister: function (drop) {
this.drops = MochiKit.Base.filter(function (d) {
return d != drop;
}, this.drops);
},
prepare: function (element) {
MochiKit.Base.map(function (drop) {
if (drop.isAccepted(element)) {
if (drop.options.activeclass) {
MochiKit.DOM.addElementClass(drop.element,
drop.options.activeclass);
}
drop.options.onactive(drop.element, element);
}
}, this.drops);
},
findDeepestChild: function (drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i) {
if (MochiKit.DOM.isChildNode(drops[i].element, deepest.element)) {
deepest = drops[i];
}
}
return deepest;
},
show: function (point, element) {
if (!this.drops.length) {
return;
}
var affected = [];
if (this.last_active) {
this.last_active.deactivate();
}
MochiKit.Iter.forEach(this.drops, function (drop) {
if (drop.isAffected(point, element)) {
affected.push(drop);
}
});
if (affected.length > 0) {
drop = this.findDeepestChild(affected);
MochiKit.Position.within(drop.element, point.page.x, point.page.y);
drop.options.onhover(element, drop.element,
MochiKit.Position.overlap(drop.options.overlap, drop.element));
drop.activate();
}
},
fire: function (event, element) {
if (!this.last_active) {
return;
}
MochiKit.Position.prepare();
if (this.last_active.isAffected(event.mouse(), element)) {
this.last_active.options.ondrop(element,
this.last_active.element, event);
}
},
reset: function (element) {
MochiKit.Base.map(function (drop) {
if (drop.options.activeclass) {
MochiKit.DOM.removeElementClass(drop.element,
drop.options.activeclass);
}
drop.options.ondesactive(drop.element, element);
}, this.drops);
if (this.last_active) {
this.last_active.deactivate();
}
}
};
/** @id MochiKit.DragAndDrop.Droppable */
MochiKit.DragAndDrop.Droppable = function (element, options) {
var cls = arguments.callee;
if (!(this instanceof cls)) {
return new cls(element, options);
}
this.__init__(element, options);
};
MochiKit.DragAndDrop.Droppable.prototype = {
/***
A droppable object. Simple use is to create giving an element:
new MochiKit.DragAndDrop.Droppable('myelement');
Generally you'll want to define the 'ondrop' function and maybe the
'accept' option to filter draggables.
***/
__class__: MochiKit.DragAndDrop.Droppable,
__init__: function (element, /* optional */options) {
var d = MochiKit.DOM;
var b = MochiKit.Base;
this.element = d.getElement(element);
this.options = b.update({
/** @id MochiKit.DragAndDrop.greedy */
greedy: true,
/** @id MochiKit.DragAndDrop.hoverclass */
hoverclass: null,
/** @id MochiKit.DragAndDrop.activeclass */
activeclass: null,
/** @id MochiKit.DragAndDrop.hoverfunc */
hoverfunc: b.noop,
/** @id MochiKit.DragAndDrop.accept */
accept: null,
/** @id MochiKit.DragAndDrop.onactive */
onactive: b.noop,
/** @id MochiKit.DragAndDrop.ondesactive */
ondesactive: b.noop,
/** @id MochiKit.DragAndDrop.onhover */
onhover: b.noop,
/** @id MochiKit.DragAndDrop.ondrop */
ondrop: b.noop,
/** @id MochiKit.DragAndDrop.containment */
containment: [],
tree: false
}, options);
// cache containers
this.options._containers = [];
b.map(MochiKit.Base.bind(function (c) {
this.options._containers.push(d.getElement(c));
}, this), this.options.containment);
MochiKit.Style.makePositioned(this.element); // fix IE
MochiKit.DragAndDrop.Droppables.register(this);
},
/** @id MochiKit.DragAndDrop.isContained */
isContained: function (element) {
if (this.options._containers.length) {
var containmentNode;
if (this.options.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return MochiKit.Iter.some(this.options._containers, function (c) {
return containmentNode == c;
});
} else {
return true;
}
},
/** @id MochiKit.DragAndDrop.isAccepted */
isAccepted: function (element) {
return ((!this.options.accept) || MochiKit.Iter.some(
this.options.accept, function (c) {
return MochiKit.DOM.hasElementClass(element, c);
}));
},
/** @id MochiKit.DragAndDrop.isAffected */
isAffected: function (point, element) {
return ((this.element != element) &&
this.isContained(element) &&
this.isAccepted(element) &&
MochiKit.Position.within(this.element, point.page.x,
point.page.y));
},
/** @id MochiKit.DragAndDrop.deactivate */
deactivate: function () {
/***
A droppable is deactivate when a draggable has been over it and left.
***/
if (this.options.hoverclass) {
MochiKit.DOM.removeElementClass(this.element,
this.options.hoverclass);
}
this.options.hoverfunc(this.element, false);
MochiKit.DragAndDrop.Droppables.last_active = null;
},
/** @id MochiKit.DragAndDrop.activate */
activate: function () {
/***
A droppable is active when a draggable is over it.
***/
if (this.options.hoverclass) {
MochiKit.DOM.addElementClass(this.element, this.options.hoverclass);
}
this.options.hoverfunc(this.element, true);
MochiKit.DragAndDrop.Droppables.last_active = this;
},
/** @id MochiKit.DragAndDrop.destroy */
destroy: function () {
/***
Delete this droppable.
***/
MochiKit.DragAndDrop.Droppables.unregister(this);
},
/** @id MochiKit.DragAndDrop.repr */
repr: function () {
return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
}
};
MochiKit.DragAndDrop.Draggables = {
/***
Manage draggables elements. Not intended to direct use.
***/
drags: [],
register: function (draggable) {
if (this.drags.length === 0) {
var conn = MochiKit.Signal.connect;
this.eventMouseUp = conn(document, 'onmouseup', this, this.endDrag);
this.eventMouseMove = conn(document, 'onmousemove', this,
this.updateDrag);
this.eventKeypress = conn(document, 'onkeypress', this,
this.keyPress);
}
this.drags.push(draggable);
},
unregister: function (draggable) {
this.drags = MochiKit.Base.filter(function (d) {
return d != draggable;
}, this.drags);
if (this.drags.length === 0) {
var disc = MochiKit.Signal.disconnect;
disc(this.eventMouseUp);
disc(this.eventMouseMove);
disc(this.eventKeypress);
}
},
activate: function (draggable) {
// allows keypress events if window is not currently focused
// fails for Safari
window.focus();
this.activeDraggable = draggable;
},
deactivate: function () {
this.activeDraggable = null;
},
updateDrag: function (event) {
if (!this.activeDraggable) {
return;
}
var pointer = event.mouse();
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if (this._lastPointer && (MochiKit.Base.repr(this._lastPointer.page) ==
MochiKit.Base.repr(pointer.page))) {
return;
}
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function (event) {
if (!this.activeDraggable) {
return;
}
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function (event) {
if (this.activeDraggable) {
this.activeDraggable.keyPress(event);
}
},
notify: function (eventName, draggable, event) {
MochiKit.Signal.signal(this, eventName, draggable, event);
}
};
/** @id MochiKit.DragAndDrop.Draggable */
MochiKit.DragAndDrop.Draggable = function (element, options) {
var cls = arguments.callee;
if (!(this instanceof cls)) {
return new cls(element, options);
}
this.__init__(element, options);
};
MochiKit.DragAndDrop.Draggable.prototype = {
/***
A draggable object. Simple instantiate :
new MochiKit.DragAndDrop.Draggable('myelement');
***/
__class__ : MochiKit.DragAndDrop.Draggable,
__init__: function (element, /* optional */options) {
var v = MochiKit.Visual;
var b = MochiKit.Base;
options = b.update({
/** @id MochiKit.DragAndDrop.handle */
handle: false,
/** @id MochiKit.DragAndDrop.starteffect */
starteffect: function (innerelement) {
this._savedOpacity = MochiKit.Style.getStyle(innerelement, 'opacity') || 1.0;
new v.Opacity(innerelement, {duration:0.2, from:this._savedOpacity, to:0.7});
},
/** @id MochiKit.DragAndDrop.reverteffect */
reverteffect: function (innerelement, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2) +
Math.abs(left_offset^2))*0.02;
return new v.Move(innerelement,
{x: -left_offset, y: -top_offset, duration: dur});
},
/** @id MochiKit.DragAndDrop.endeffect */
endeffect: function (innerelement) {
new v.Opacity(innerelement, {duration:0.2, from:0.7, to:this._savedOpacity});
},
/** @id MochiKit.DragAndDrop.onchange */
onchange: b.noop,
/** @id MochiKit.DragAndDrop.zindex */
zindex: 1000,
/** @id MochiKit.DragAndDrop.revert */
revert: false,
/** @id MochiKit.DragAndDrop.scroll */
scroll: false,
/** @id MochiKit.DragAndDrop.scrollSensitivity */
scrollSensitivity: 20,
/** @id MochiKit.DragAndDrop.scrollSpeed */
scrollSpeed: 15,
// false, or xy or [x, y] or function (x, y){return [x, y];}
/** @id MochiKit.DragAndDrop.snap */
snap: false
}, options);
var d = MochiKit.DOM;
this.element = d.getElement(element);
if (options.handle && (typeof(options.handle) == 'string')) {
this.handle = d.getFirstElementByTagAndClassName(null,
options.handle, this.element);
}
if (!this.handle) {
this.handle = d.getElement(options.handle);
}
if (!this.handle) {
this.handle = this.element;
}
if (options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = d.getElement(options.scroll);
this._isScrollChild = MochiKit.DOM.isChildNode(this.element, options.scroll);
}
MochiKit.Style.makePositioned(this.element); // fix IE
this.delta = this.currentDelta();
this.options = options;
this.dragging = false;
this.eventMouseDown = MochiKit.Signal.connect(this.handle,
'onmousedown', this, this.initDrag);
MochiKit.DragAndDrop.Draggables.register(this);
},
/** @id MochiKit.DragAndDrop.destroy */
destroy: function () {
MochiKit.Signal.disconnect(this.eventMouseDown);
MochiKit.DragAndDrop.Draggables.unregister(this);
},
/** @id MochiKit.DragAndDrop.currentDelta */
currentDelta: function () {
var s = MochiKit.Style.getStyle;
return [
parseInt(s(this.element, 'left') || '0'),
parseInt(s(this.element, 'top') || '0')];
},
/** @id MochiKit.DragAndDrop.initDrag */
initDrag: function (event) {
if (!event.mouse().button.left) {
return;
}
// abort on form elements, fixes a Firefox issue
var src = event.target();
var tagName = (src.tagName || '').toUpperCase();
if (tagName === 'INPUT' || tagName === 'SELECT' ||
tagName === 'OPTION' || tagName === 'BUTTON' ||
tagName === 'TEXTAREA') {
return;
}
if (this._revert) {
this._revert.cancel();
this._revert = null;
}
var pointer = event.mouse();
var pos = MochiKit.Position.cumulativeOffset(this.element);
this.offset = [pointer.page.x - pos.x, pointer.page.y - pos.y];
MochiKit.DragAndDrop.Draggables.activate(this);
event.stop();
},
/** @id MochiKit.DragAndDrop.startDrag */
startDrag: function (event) {
this.dragging = true;
if (this.options.selectclass) {
MochiKit.DOM.addElementClass(this.element,
this.options.selectclass);
}
if (this.options.zindex) {
this.originalZ = parseInt(MochiKit.Style.getStyle(this.element,
'z-index') || '0');
this.element.style.zIndex = this.options.zindex;
}
if (this.options.ghosting) {
this._clone = this.element.cloneNode(true);
this.ghostPosition = MochiKit.Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if (this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
MochiKit.DragAndDrop.Droppables.prepare(this.element);
MochiKit.DragAndDrop.Draggables.notify('start', this, event);
if (this.options.starteffect) {
this.options.starteffect(this.element);
}
},
/** @id MochiKit.DragAndDrop.updateDrag */
updateDrag: function (event, pointer) {
if (!this.dragging) {
this.startDrag(event);
}
MochiKit.Position.prepare();
MochiKit.DragAndDrop.Droppables.show(pointer, this.element);
MochiKit.DragAndDrop.Draggables.notify('drag', this, event);
this.draw(pointer);
this.options.onchange(this);
if (this.options.scroll) {
this.stopScrolling();
var p, q;
if (this.options.scroll == window) {
var s = this._getWindowScroll(this.options.scroll);
p = new MochiKit.Style.Coordinates(s.left, s.top);
q = new MochiKit.Style.Coordinates(s.left + s.width,
s.top + s.height);
} else {
p = MochiKit.Position.page(this.options.scroll);
p.x += this.options.scroll.scrollLeft;
p.y += this.options.scroll.scrollTop;
p.x += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
p.y += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
q = new MochiKit.Style.Coordinates(p.x + this.options.scroll.offsetWidth,
p.y + this.options.scroll.offsetHeight);
}
var speed = [0, 0];
if (pointer.page.x > (q.x - this.options.scrollSensitivity)) {
speed[0] = pointer.page.x - (q.x - this.options.scrollSensitivity);
} else if (pointer.page.x < (p.x + this.options.scrollSensitivity)) {
speed[0] = pointer.page.x - (p.x + this.options.scrollSensitivity);
}
if (pointer.page.y > (q.y - this.options.scrollSensitivity)) {
speed[1] = pointer.page.y - (q.y - this.options.scrollSensitivity);
} else if (pointer.page.y < (p.y + this.options.scrollSensitivity)) {
speed[1] = pointer.page.y - (p.y + this.options.scrollSensitivity);
}
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if (/AppleWebKit/.test(navigator.appVersion)) {
window.scrollBy(0, 0);
}
event.stop();
},
/** @id MochiKit.DragAndDrop.finishDrag */
finishDrag: function (event, success) {
var dr = MochiKit.DragAndDrop;
this.dragging = false;
if (this.options.selectclass) {
MochiKit.DOM.removeElementClass(this.element,
this.options.selectclass);
}
if (this.options.ghosting) {
// XXX: from a user point of view, it would be better to remove
// the node only *after* the MochiKit.Visual.Move end when used
// with revert.
MochiKit.Position.relativize(this.element, this.ghostPosition);
MochiKit.DOM.removeElement(this._clone);
this._clone = null;
}
if (success) {
dr.Droppables.fire(event, this.element);
}
dr.Draggables.notify('end', this, event);
var revert = this.options.revert;
if (revert && typeof(revert) == 'function') {
revert = revert(this.element);
}
var d = this.currentDelta();
if (revert && this.options.reverteffect) {
this._revert = this.options.reverteffect(this.element,
d[1] - this.delta[1], d[0] - this.delta[0]);
} else {
this.delta = d;
}
if (this.options.zindex) {
this.element.style.zIndex = this.originalZ;
}
if (this.options.endeffect) {
this.options.endeffect(this.element);
}
dr.Draggables.deactivate();
dr.Droppables.reset(this.element);
},
/** @id MochiKit.DragAndDrop.keyPress */
keyPress: function (event) {
if (event.key().string != "KEY_ESCAPE") {
return;
}
this.finishDrag(event, false);
event.stop();
},
/** @id MochiKit.DragAndDrop.endDrag */
endDrag: function (event) {
if (!this.dragging) {
return;
}
this.stopScrolling();
this.finishDrag(event, true);
event.stop();
},
/** @id MochiKit.DragAndDrop.draw */
draw: function (point) {
var pos = MochiKit.Position.cumulativeOffset(this.element);
var d = this.currentDelta();
pos.x -= d[0];
pos.y -= d[1];
if (this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos.x -= this.options.scroll.scrollLeft - this.originalScrollLeft;
pos.y -= this.options.scroll.scrollTop - this.originalScrollTop;
}
var p = [point.page.x - pos.x - this.offset[0],
point.page.y - pos.y - this.offset[1]];
if (this.options.snap) {
if (typeof(this.options.snap) == 'function') {
p = this.options.snap(p[0], p[1]);
} else {
if (this.options.snap instanceof Array) {
var i = -1;
p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
i += 1;
return Math.round(v/this.options.snap[i]) *
this.options.snap[i];
}, this), p);
} else {
p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
return Math.round(v/this.options.snap) *
this.options.snap;
}, this), p);
}
}
}
var style = this.element.style;
if ((!this.options.constraint) ||
(this.options.constraint == 'horizontal')) {
style.left = p[0] + 'px';
}
if ((!this.options.constraint) ||
(this.options.constraint == 'vertical')) {
style.top = p[1] + 'px';
}
if (style.visibility == 'hidden') {
style.visibility = ''; // fix gecko rendering
}
},
/** @id MochiKit.DragAndDrop.stopScrolling */
stopScrolling: function () {
if (this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
MochiKit.DragAndDrop.Draggables._lastScrollPointer = null;
}
},
/** @id MochiKit.DragAndDrop.startScrolling */
startScrolling: function (speed) {
if (!speed[0] && !speed[1]) {
return;
}
this.scrollSpeed = [speed[0] * this.options.scrollSpeed,
speed[1] * this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(MochiKit.Base.bind(this.scroll, this), 10);
},
/** @id MochiKit.DragAndDrop.scroll */
scroll: function () {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if (this.options.scroll == window) {
var s = this._getWindowScroll(this.options.scroll);
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var dm = delta / 1000;
this.options.scroll.scrollTo(s.left + dm * this.scrollSpeed[0],
s.top + dm * this.scrollSpeed[1]);
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
var d = MochiKit.DragAndDrop;
MochiKit.Position.prepare();
d.Droppables.show(d.Draggables._lastPointer, this.element);
d.Draggables.notify('drag', this);
if (this._isScrollChild) {
d.Draggables._lastScrollPointer = d.Draggables._lastScrollPointer || d.Draggables._lastPointer;
d.Draggables._lastScrollPointer.x += this.scrollSpeed[0] * delta / 1000;
d.Draggables._lastScrollPointer.y += this.scrollSpeed[1] * delta / 1000;
if (d.Draggables._lastScrollPointer.x < 0) {
d.Draggables._lastScrollPointer.x = 0;
}
if (d.Draggables._lastScrollPointer.y < 0) {
d.Draggables._lastScrollPointer.y = 0;
}
this.draw(d.Draggables._lastScrollPointer);
}
this.options.onchange(this);
},
_getWindowScroll: function (win) {
var vp, w, h;
MochiKit.DOM.withWindow(win, function () {
vp = MochiKit.Style.getViewportPosition(win.document);
});
if (win.innerWidth) {
w = win.innerWidth;
h = win.innerHeight;
} else if (win.document.documentElement && win.document.documentElement.clientWidth) {
w = win.document.documentElement.clientWidth;
h = win.document.documentElement.clientHeight;
} else {
w = win.document.body.offsetWidth;
h = win.document.body.offsetHeight;
}
return {top: vp.y, left: vp.x, width: w, height: h};
},
/** @id MochiKit.DragAndDrop.repr */
repr: function () {
return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
}
};
MochiKit.DragAndDrop.__new__ = function () {
MochiKit.Base.nameFunctions(this);
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
};
};
MochiKit.DragAndDrop.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.DragAndDrop);
-304
View File
@@ -1,304 +0,0 @@
/***
MochiKit.Format 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('Format', ['Base']);
MochiKit.Format.NAME = "MochiKit.Format";
MochiKit.Format.VERSION = "1.4.2";
MochiKit.Format.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.Format.toString = function () {
return this.__repr__();
};
MochiKit.Format._numberFormatter = function (placeholder, header, footer, locale, isPercent, precision, leadingZeros, separatorAt, trailingZeros) {
return function (num) {
num = parseFloat(num);
if (typeof(num) == "undefined" || num === null || isNaN(num)) {
return placeholder;
}
var curheader = header;
var curfooter = footer;
if (num < 0) {
num = -num;
} else {
curheader = curheader.replace(/-/, "");
}
var me = arguments.callee;
var fmt = MochiKit.Format.formatLocale(locale);
if (isPercent) {
num = num * 100.0;
curfooter = fmt.percent + curfooter;
}
num = MochiKit.Format.roundToFixed(num, precision);
var parts = num.split(/\./);
var whole = parts[0];
var frac = (parts.length == 1) ? "" : parts[1];
var res = "";
while (whole.length < leadingZeros) {
whole = "0" + whole;
}
if (separatorAt) {
while (whole.length > separatorAt) {
var i = whole.length - separatorAt;
//res = res + fmt.separator + whole.substring(i, whole.length);
res = fmt.separator + whole.substring(i, whole.length) + res;
whole = whole.substring(0, i);
}
}
res = whole + res;
if (precision > 0) {
while (frac.length < trailingZeros) {
frac = frac + "0";
}
res = res + fmt.decimal + frac;
}
return curheader + res + curfooter;
};
};
/** @id MochiKit.Format.numberFormatter */
MochiKit.Format.numberFormatter = function (pattern, placeholder/* = "" */, locale/* = "default" */) {
// http://java.sun.com/docs/books/tutorial/i18n/format/numberpattern.html
// | 0 | leading or trailing zeros
// | # | just the number
// | , | separator
// | . | decimal separator
// | % | Multiply by 100 and format as percent
if (typeof(placeholder) == "undefined") {
placeholder = "";
}
var match = pattern.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/);
if (!match) {
throw TypeError("Invalid pattern");
}
var header = pattern.substr(0, match.index);
var footer = pattern.substr(match.index + match[0].length);
if (header.search(/-/) == -1) {
header = header + "-";
}
var whole = match[1];
var frac = (typeof(match[2]) == "string" && match[2] != "") ? match[2] : "";
var isPercent = (typeof(match[3]) == "string" && match[3] != "");
var tmp = whole.split(/,/);
var separatorAt;
if (typeof(locale) == "undefined") {
locale = "default";
}
if (tmp.length == 1) {
separatorAt = null;
} else {
separatorAt = tmp[1].length;
}
var leadingZeros = whole.length - whole.replace(/0/g, "").length;
var trailingZeros = frac.length - frac.replace(/0/g, "").length;
var precision = frac.length;
var rval = MochiKit.Format._numberFormatter(
placeholder, header, footer, locale, isPercent, precision,
leadingZeros, separatorAt, trailingZeros
);
var m = MochiKit.Base;
if (m) {
var fn = arguments.callee;
var args = m.concat(arguments);
rval.repr = function () {
return [
self.NAME,
"(",
map(m.repr, args).join(", "),
")"
].join("");
};
}
return rval;
};
/** @id MochiKit.Format.formatLocale */
MochiKit.Format.formatLocale = function (locale) {
if (typeof(locale) == "undefined" || locale === null) {
locale = "default";
}
if (typeof(locale) == "string") {
var rval = MochiKit.Format.LOCALE[locale];
if (typeof(rval) == "string") {
rval = arguments.callee(rval);
MochiKit.Format.LOCALE[locale] = rval;
}
return rval;
} else {
return locale;
}
};
/** @id MochiKit.Format.twoDigitAverage */
MochiKit.Format.twoDigitAverage = function (numerator, denominator) {
if (denominator) {
var res = numerator / denominator;
if (!isNaN(res)) {
return MochiKit.Format.twoDigitFloat(res);
}
}
return "0";
};
/** @id MochiKit.Format.twoDigitFloat */
MochiKit.Format.twoDigitFloat = function (aNumber) {
var res = roundToFixed(aNumber, 2);
if (res.indexOf(".00") > 0) {
return res.substring(0, res.length - 3);
} else if (res.charAt(res.length - 1) == "0") {
return res.substring(0, res.length - 1);
} else {
return res;
}
};
/** @id MochiKit.Format.lstrip */
MochiKit.Format.lstrip = function (str, /* optional */chars) {
str = str + "";
if (typeof(str) != "string") {
return null;
}
if (!chars) {
return str.replace(/^\s+/, "");
} else {
return str.replace(new RegExp("^[" + chars + "]+"), "");
}
};
/** @id MochiKit.Format.rstrip */
MochiKit.Format.rstrip = function (str, /* optional */chars) {
str = str + "";
if (typeof(str) != "string") {
return null;
}
if (!chars) {
return str.replace(/\s+$/, "");
} else {
return str.replace(new RegExp("[" + chars + "]+$"), "");
}
};
/** @id MochiKit.Format.strip */
MochiKit.Format.strip = function (str, /* optional */chars) {
var self = MochiKit.Format;
return self.rstrip(self.lstrip(str, chars), chars);
};
/** @id MochiKit.Format.truncToFixed */
MochiKit.Format.truncToFixed = function (aNumber, precision) {
var res = Math.floor(aNumber).toFixed(0);
if (aNumber < 0) {
res = Math.ceil(aNumber).toFixed(0);
if (res.charAt(0) != "-" && precision > 0) {
res = "-" + res;
}
}
if (res.indexOf("e") < 0 && precision > 0) {
var tail = aNumber.toString();
if (tail.indexOf("e") > 0) {
tail = ".";
} else if (tail.indexOf(".") < 0) {
tail = ".";
} else {
tail = tail.substring(tail.indexOf("."));
}
if (tail.length - 1 > precision) {
tail = tail.substring(0, precision + 1);
}
while (tail.length - 1 < precision) {
tail += "0";
}
res += tail;
}
return res;
};
/** @id MochiKit.Format.roundToFixed */
MochiKit.Format.roundToFixed = function (aNumber, precision) {
var upper = Math.abs(aNumber) + 0.5 * Math.pow(10, -precision);
var res = MochiKit.Format.truncToFixed(upper, precision);
if (aNumber < 0) {
res = "-" + res;
}
return res;
};
/** @id MochiKit.Format.percentFormat */
MochiKit.Format.percentFormat = function (aNumber) {
return MochiKit.Format.twoDigitFloat(100 * aNumber) + '%';
};
MochiKit.Format.EXPORT = [
"truncToFixed",
"roundToFixed",
"numberFormatter",
"formatLocale",
"twoDigitAverage",
"twoDigitFloat",
"percentFormat",
"lstrip",
"rstrip",
"strip"
];
MochiKit.Format.LOCALE = {
en_US: {separator: ",", decimal: ".", percent: "%"},
de_DE: {separator: ".", decimal: ",", percent: "%"},
pt_BR: {separator: ".", decimal: ",", percent: "%"},
fr_FR: {separator: " ", decimal: ",", percent: "%"},
"default": "en_US"
};
MochiKit.Format.EXPORT_OK = [];
MochiKit.Format.EXPORT_TAGS = {
':all': MochiKit.Format.EXPORT,
':common': MochiKit.Format.EXPORT
};
MochiKit.Format.__new__ = function () {
// MochiKit.Base.nameFunctions(this);
var base = this.NAME + ".";
var k, v, o;
for (k in this.LOCALE) {
o = this.LOCALE[k];
if (typeof(o) == "object") {
o.repr = function () { return this.NAME; };
o.NAME = base + "LOCALE." + k;
}
}
for (k in this) {
o = this[k];
if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') {
try {
o.NAME = base + k;
} catch (e) {
// pass
}
}
}
};
MochiKit.Format.__new__();
if (typeof(MochiKit.Base) != "undefined") {
MochiKit.Base._exportSymbols(this, MochiKit.Format);
} else {
(function (globals, module) {
if ((typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined')
|| (MochiKit.__export__ === false)) {
var all = module.EXPORT_TAGS[":all"];
for (var i = 0; i < all.length; i++) {
globals[all[i]] = module[all[i]];
}
}
})(this, MochiKit.Format);
}
-844
View File
@@ -1,844 +0,0 @@
/***
MochiKit.Iter 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('Iter', ['Base']);
MochiKit.Iter.NAME = "MochiKit.Iter";
MochiKit.Iter.VERSION = "1.4.2";
MochiKit.Base.update(MochiKit.Iter, {
__repr__: function () {
return "[" + this.NAME + " " + this.VERSION + "]";
},
toString: function () {
return this.__repr__();
},
/** @id MochiKit.Iter.registerIteratorFactory */
registerIteratorFactory: function (name, check, iterfactory, /* optional */ override) {
MochiKit.Iter.iteratorRegistry.register(name, check, iterfactory, override);
},
/** @id MochiKit.Iter.isIterable */
isIterable: function(o) {
return o != null &&
(typeof(o.next) == "function" || typeof(o.iter) == "function");
},
/** @id MochiKit.Iter.iter */
iter: function (iterable, /* optional */ sentinel) {
var self = MochiKit.Iter;
if (arguments.length == 2) {
return self.takewhile(
function (a) { return a != sentinel; },
iterable
);
}
if (typeof(iterable.next) == 'function') {
return iterable;
} else if (typeof(iterable.iter) == 'function') {
return iterable.iter();
/*
} else if (typeof(iterable.__iterator__) == 'function') {
//
// XXX: We can't support JavaScript 1.7 __iterator__ directly
// because of Object.prototype.__iterator__
//
return iterable.__iterator__();
*/
}
try {
return self.iteratorRegistry.match(iterable);
} catch (e) {
var m = MochiKit.Base;
if (e == m.NotFound) {
e = new TypeError(typeof(iterable) + ": " + m.repr(iterable) + " is not iterable");
}
throw e;
}
},
/** @id MochiKit.Iter.count */
count: function (n) {
if (!n) {
n = 0;
}
var m = MochiKit.Base;
return {
repr: function () { return "count(" + n + ")"; },
toString: m.forwardCall("repr"),
next: m.counter(n)
};
},
/** @id MochiKit.Iter.cycle */
cycle: function (p) {
var self = MochiKit.Iter;
var m = MochiKit.Base;
var lst = [];
var iterator = self.iter(p);
return {
repr: function () { return "cycle(...)"; },
toString: m.forwardCall("repr"),
next: function () {
try {
var rval = iterator.next();
lst.push(rval);
return rval;
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
if (lst.length === 0) {
this.next = function () {
throw self.StopIteration;
};
} else {
var i = -1;
this.next = function () {
i = (i + 1) % lst.length;
return lst[i];
};
}
return this.next();
}
}
};
},
/** @id MochiKit.Iter.repeat */
repeat: function (elem, /* optional */n) {
var m = MochiKit.Base;
if (typeof(n) == 'undefined') {
return {
repr: function () {
return "repeat(" + m.repr(elem) + ")";
},
toString: m.forwardCall("repr"),
next: function () {
return elem;
}
};
}
return {
repr: function () {
return "repeat(" + m.repr(elem) + ", " + n + ")";
},
toString: m.forwardCall("repr"),
next: function () {
if (n <= 0) {
throw MochiKit.Iter.StopIteration;
}
n -= 1;
return elem;
}
};
},
/** @id MochiKit.Iter.next */
next: function (iterator) {
return iterator.next();
},
/** @id MochiKit.Iter.izip */
izip: function (p, q/*, ...*/) {
var m = MochiKit.Base;
var self = MochiKit.Iter;
var next = self.next;
var iterables = m.map(self.iter, arguments);
return {
repr: function () { return "izip(...)"; },
toString: m.forwardCall("repr"),
next: function () { return m.map(next, iterables); }
};
},
/** @id MochiKit.Iter.ifilter */
ifilter: function (pred, seq) {
var m = MochiKit.Base;
seq = MochiKit.Iter.iter(seq);
if (pred === null) {
pred = m.operator.truth;
}
return {
repr: function () { return "ifilter(...)"; },
toString: m.forwardCall("repr"),
next: function () {
while (true) {
var rval = seq.next();
if (pred(rval)) {
return rval;
}
}
// mozilla warnings aren't too bright
return undefined;
}
};
},
/** @id MochiKit.Iter.ifilterfalse */
ifilterfalse: function (pred, seq) {
var m = MochiKit.Base;
seq = MochiKit.Iter.iter(seq);
if (pred === null) {
pred = m.operator.truth;
}
return {
repr: function () { return "ifilterfalse(...)"; },
toString: m.forwardCall("repr"),
next: function () {
while (true) {
var rval = seq.next();
if (!pred(rval)) {
return rval;
}
}
// mozilla warnings aren't too bright
return undefined;
}
};
},
/** @id MochiKit.Iter.islice */
islice: function (seq/*, [start,] stop[, step] */) {
var self = MochiKit.Iter;
var m = MochiKit.Base;
seq = self.iter(seq);
var start = 0;
var stop = 0;
var step = 1;
var i = -1;
if (arguments.length == 2) {
stop = arguments[1];
} else if (arguments.length == 3) {
start = arguments[1];
stop = arguments[2];
} else {
start = arguments[1];
stop = arguments[2];
step = arguments[3];
}
return {
repr: function () {
return "islice(" + ["...", start, stop, step].join(", ") + ")";
},
toString: m.forwardCall("repr"),
next: function () {
var rval;
while (i < start) {
rval = seq.next();
i++;
}
if (start >= stop) {
throw self.StopIteration;
}
start += step;
return rval;
}
};
},
/** @id MochiKit.Iter.imap */
imap: function (fun, p, q/*, ...*/) {
var m = MochiKit.Base;
var self = MochiKit.Iter;
var iterables = m.map(self.iter, m.extend(null, arguments, 1));
var map = m.map;
var next = self.next;
return {
repr: function () { return "imap(...)"; },
toString: m.forwardCall("repr"),
next: function () {
return fun.apply(this, map(next, iterables));
}
};
},
/** @id MochiKit.Iter.applymap */
applymap: function (fun, seq, self) {
seq = MochiKit.Iter.iter(seq);
var m = MochiKit.Base;
return {
repr: function () { return "applymap(...)"; },
toString: m.forwardCall("repr"),
next: function () {
return fun.apply(self, seq.next());
}
};
},
/** @id MochiKit.Iter.chain */
chain: function (p, q/*, ...*/) {
// dumb fast path
var self = MochiKit.Iter;
var m = MochiKit.Base;
if (arguments.length == 1) {
return self.iter(arguments[0]);
}
var argiter = m.map(self.iter, arguments);
return {
repr: function () { return "chain(...)"; },
toString: m.forwardCall("repr"),
next: function () {
while (argiter.length > 1) {
try {
var result = argiter[0].next();
return result;
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
argiter.shift();
var result = argiter[0].next();
return result;
}
}
if (argiter.length == 1) {
// optimize last element
var arg = argiter.shift();
this.next = m.bind("next", arg);
return this.next();
}
throw self.StopIteration;
}
};
},
/** @id MochiKit.Iter.takewhile */
takewhile: function (pred, seq) {
var self = MochiKit.Iter;
seq = self.iter(seq);
return {
repr: function () { return "takewhile(...)"; },
toString: MochiKit.Base.forwardCall("repr"),
next: function () {
var rval = seq.next();
if (!pred(rval)) {
this.next = function () {
throw self.StopIteration;
};
this.next();
}
return rval;
}
};
},
/** @id MochiKit.Iter.dropwhile */
dropwhile: function (pred, seq) {
seq = MochiKit.Iter.iter(seq);
var m = MochiKit.Base;
var bind = m.bind;
return {
"repr": function () { return "dropwhile(...)"; },
"toString": m.forwardCall("repr"),
"next": function () {
while (true) {
var rval = seq.next();
if (!pred(rval)) {
break;
}
}
this.next = bind("next", seq);
return rval;
}
};
},
_tee: function (ident, sync, iterable) {
sync.pos[ident] = -1;
var m = MochiKit.Base;
var listMin = m.listMin;
return {
repr: function () { return "tee(" + ident + ", ...)"; },
toString: m.forwardCall("repr"),
next: function () {
var rval;
var i = sync.pos[ident];
if (i == sync.max) {
rval = iterable.next();
sync.deque.push(rval);
sync.max += 1;
sync.pos[ident] += 1;
} else {
rval = sync.deque[i - sync.min];
sync.pos[ident] += 1;
if (i == sync.min && listMin(sync.pos) != sync.min) {
sync.min += 1;
sync.deque.shift();
}
}
return rval;
}
};
},
/** @id MochiKit.Iter.tee */
tee: function (iterable, n/* = 2 */) {
var rval = [];
var sync = {
"pos": [],
"deque": [],
"max": -1,
"min": -1
};
if (arguments.length == 1 || typeof(n) == "undefined" || n === null) {
n = 2;
}
var self = MochiKit.Iter;
iterable = self.iter(iterable);
var _tee = self._tee;
for (var i = 0; i < n; i++) {
rval.push(_tee(i, sync, iterable));
}
return rval;
},
/** @id MochiKit.Iter.list */
list: function (iterable) {
// Fast-path for Array and Array-like
var rval;
if (iterable instanceof Array) {
return iterable.slice();
}
// this is necessary to avoid a Safari crash
if (typeof(iterable) == "function" &&
!(iterable instanceof Function) &&
typeof(iterable.length) == 'number') {
rval = [];
for (var i = 0; i < iterable.length; i++) {
rval.push(iterable[i]);
}
return rval;
}
var self = MochiKit.Iter;
iterable = self.iter(iterable);
var rval = [];
var a_val;
try {
while (true) {
a_val = iterable.next();
rval.push(a_val);
}
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
return rval;
}
// mozilla warnings aren't too bright
return undefined;
},
/** @id MochiKit.Iter.reduce */
reduce: function (fn, iterable, /* optional */initial) {
var i = 0;
var x = initial;
var self = MochiKit.Iter;
iterable = self.iter(iterable);
if (arguments.length < 3) {
try {
x = iterable.next();
} catch (e) {
if (e == self.StopIteration) {
e = new TypeError("reduce() of empty sequence with no initial value");
}
throw e;
}
i++;
}
try {
while (true) {
x = fn(x, iterable.next());
}
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
}
return x;
},
/** @id MochiKit.Iter.range */
range: function (/* [start,] stop[, step] */) {
var start = 0;
var stop = 0;
var step = 1;
if (arguments.length == 1) {
stop = arguments[0];
} else if (arguments.length == 2) {
start = arguments[0];
stop = arguments[1];
} else if (arguments.length == 3) {
start = arguments[0];
stop = arguments[1];
step = arguments[2];
} else {
throw new TypeError("range() takes 1, 2, or 3 arguments!");
}
if (step === 0) {
throw new TypeError("range() step must not be 0");
}
return {
next: function () {
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) {
throw MochiKit.Iter.StopIteration;
}
var rval = start;
start += step;
return rval;
},
repr: function () {
return "range(" + [start, stop, step].join(", ") + ")";
},
toString: MochiKit.Base.forwardCall("repr")
};
},
/** @id MochiKit.Iter.sum */
sum: function (iterable, start/* = 0 */) {
if (typeof(start) == "undefined" || start === null) {
start = 0;
}
var x = start;
var self = MochiKit.Iter;
iterable = self.iter(iterable);
try {
while (true) {
x += iterable.next();
}
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
}
return x;
},
/** @id MochiKit.Iter.exhaust */
exhaust: function (iterable) {
var self = MochiKit.Iter;
iterable = self.iter(iterable);
try {
while (true) {
iterable.next();
}
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
}
},
/** @id MochiKit.Iter.forEach */
forEach: function (iterable, func, /* optional */obj) {
var m = MochiKit.Base;
var self = MochiKit.Iter;
if (arguments.length > 2) {
func = m.bind(func, obj);
}
// fast path for array
if (m.isArrayLike(iterable) && !self.isIterable(iterable)) {
try {
for (var i = 0; i < iterable.length; i++) {
func(iterable[i]);
}
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
}
} else {
self.exhaust(self.imap(func, iterable));
}
},
/** @id MochiKit.Iter.every */
every: function (iterable, func) {
var self = MochiKit.Iter;
try {
self.ifilterfalse(func, iterable).next();
return false;
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
return true;
}
},
/** @id MochiKit.Iter.sorted */
sorted: function (iterable, /* optional */cmp) {
var rval = MochiKit.Iter.list(iterable);
if (arguments.length == 1) {
cmp = MochiKit.Base.compare;
}
rval.sort(cmp);
return rval;
},
/** @id MochiKit.Iter.reversed */
reversed: function (iterable) {
var rval = MochiKit.Iter.list(iterable);
rval.reverse();
return rval;
},
/** @id MochiKit.Iter.some */
some: function (iterable, func) {
var self = MochiKit.Iter;
try {
self.ifilter(func, iterable).next();
return true;
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
return false;
}
},
/** @id MochiKit.Iter.iextend */
iextend: function (lst, iterable) {
var m = MochiKit.Base;
var self = MochiKit.Iter;
if (m.isArrayLike(iterable) && !self.isIterable(iterable)) {
// fast-path for array-like
for (var i = 0; i < iterable.length; i++) {
lst.push(iterable[i]);
}
} else {
iterable = self.iter(iterable);
try {
while (true) {
lst.push(iterable.next());
}
} catch (e) {
if (e != self.StopIteration) {
throw e;
}
}
}
return lst;
},
/** @id MochiKit.Iter.groupby */
groupby: function(iterable, /* optional */ keyfunc) {
var m = MochiKit.Base;
var self = MochiKit.Iter;
if (arguments.length < 2) {
keyfunc = m.operator.identity;
}
iterable = self.iter(iterable);
// shared
var pk = undefined;
var k = undefined;
var v;
function fetch() {
v = iterable.next();
k = keyfunc(v);
};
function eat() {
var ret = v;
v = undefined;
return ret;
};
var first = true;
var compare = m.compare;
return {
repr: function () { return "groupby(...)"; },
next: function() {
// iterator-next
// iterate until meet next group
while (compare(k, pk) === 0) {
fetch();
if (first) {
first = false;
break;
}
}
pk = k;
return [k, {
next: function() {
// subiterator-next
if (v == undefined) { // Is there something to eat?
fetch();
}
if (compare(k, pk) !== 0) {
throw self.StopIteration;
}
return eat();
}
}];
}
};
},
/** @id MochiKit.Iter.groupby_as_array */
groupby_as_array: function (iterable, /* optional */ keyfunc) {
var m = MochiKit.Base;
var self = MochiKit.Iter;
if (arguments.length < 2) {
keyfunc = m.operator.identity;
}
iterable = self.iter(iterable);
var result = [];
var first = true;
var prev_key;
var compare = m.compare;
while (true) {
try {
var value = iterable.next();
var key = keyfunc(value);
} catch (e) {
if (e == self.StopIteration) {
break;
}
throw e;
}
if (first || compare(key, prev_key) !== 0) {
var values = [];
result.push([key, values]);
}
values.push(value);
first = false;
prev_key = key;
}
return result;
},
/** @id MochiKit.Iter.arrayLikeIter */
arrayLikeIter: function (iterable) {
var i = 0;
return {
repr: function () { return "arrayLikeIter(...)"; },
toString: MochiKit.Base.forwardCall("repr"),
next: function () {
if (i >= iterable.length) {
throw MochiKit.Iter.StopIteration;
}
return iterable[i++];
}
};
},
/** @id MochiKit.Iter.hasIterateNext */
hasIterateNext: function (iterable) {
return (iterable && typeof(iterable.iterateNext) == "function");
},
/** @id MochiKit.Iter.iterateNextIter */
iterateNextIter: function (iterable) {
return {
repr: function () { return "iterateNextIter(...)"; },
toString: MochiKit.Base.forwardCall("repr"),
next: function () {
var rval = iterable.iterateNext();
if (rval === null || rval === undefined) {
throw MochiKit.Iter.StopIteration;
}
return rval;
}
};
}
});
MochiKit.Iter.EXPORT_OK = [
"iteratorRegistry",
"arrayLikeIter",
"hasIterateNext",
"iterateNextIter"
];
MochiKit.Iter.EXPORT = [
"StopIteration",
"registerIteratorFactory",
"iter",
"count",
"cycle",
"repeat",
"next",
"izip",
"ifilter",
"ifilterfalse",
"islice",
"imap",
"applymap",
"chain",
"takewhile",
"dropwhile",
"tee",
"list",
"reduce",
"range",
"sum",
"exhaust",
"forEach",
"every",
"sorted",
"reversed",
"some",
"iextend",
"groupby",
"groupby_as_array"
];
MochiKit.Iter.__new__ = function () {
var m = MochiKit.Base;
// Re-use StopIteration if exists (e.g. SpiderMonkey)
if (typeof(StopIteration) != "undefined") {
this.StopIteration = StopIteration;
} else {
/** @id MochiKit.Iter.StopIteration */
this.StopIteration = new m.NamedError("StopIteration");
}
this.iteratorRegistry = new m.AdapterRegistry();
// Register the iterator factory for arrays
this.registerIteratorFactory(
"arrayLike",
m.isArrayLike,
this.arrayLikeIter
);
this.registerIteratorFactory(
"iterateNext",
this.hasIterateNext,
this.iterateNextIter
);
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
};
MochiKit.Iter.__new__();
//
// XXX: Internet Explorer blows
//
if (MochiKit.__export__) {
reduce = MochiKit.Iter.reduce;
}
MochiKit.Base._exportSymbols(this, MochiKit.Iter);
-315
View File
@@ -1,315 +0,0 @@
/***
MochiKit.Logging 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('Logging', ['Base']);
MochiKit.Logging.NAME = "MochiKit.Logging";
MochiKit.Logging.VERSION = "1.4.2";
MochiKit.Logging.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.Logging.toString = function () {
return this.__repr__();
};
MochiKit.Logging.EXPORT = [
"LogLevel",
"LogMessage",
"Logger",
"alertListener",
"logger",
"log",
"logError",
"logDebug",
"logFatal",
"logWarning"
];
MochiKit.Logging.EXPORT_OK = [
"logLevelAtLeast",
"isLogMessage",
"compareLogMessage"
];
/** @id MochiKit.Logging.LogMessage */
MochiKit.Logging.LogMessage = function (num, level, info) {
this.num = num;
this.level = level;
this.info = info;
this.timestamp = new Date();
};
MochiKit.Logging.LogMessage.prototype = {
/** @id MochiKit.Logging.LogMessage.prototype.repr */
repr: function () {
var m = MochiKit.Base;
return 'LogMessage(' +
m.map(
m.repr,
[this.num, this.level, this.info]
).join(', ') + ')';
},
/** @id MochiKit.Logging.LogMessage.prototype.toString */
toString: MochiKit.Base.forwardCall("repr")
};
MochiKit.Base.update(MochiKit.Logging, {
/** @id MochiKit.Logging.logLevelAtLeast */
logLevelAtLeast: function (minLevel) {
var self = MochiKit.Logging;
if (typeof(minLevel) == 'string') {
minLevel = self.LogLevel[minLevel];
}
return function (msg) {
var msgLevel = msg.level;
if (typeof(msgLevel) == 'string') {
msgLevel = self.LogLevel[msgLevel];
}
return msgLevel >= minLevel;
};
},
/** @id MochiKit.Logging.isLogMessage */
isLogMessage: function (/* ... */) {
var LogMessage = MochiKit.Logging.LogMessage;
for (var i = 0; i < arguments.length; i++) {
if (!(arguments[i] instanceof LogMessage)) {
return false;
}
}
return true;
},
/** @id MochiKit.Logging.compareLogMessage */
compareLogMessage: function (a, b) {
return MochiKit.Base.compare([a.level, a.info], [b.level, b.info]);
},
/** @id MochiKit.Logging.alertListener */
alertListener: function (msg) {
alert(
"num: " + msg.num +
"\nlevel: " + msg.level +
"\ninfo: " + msg.info.join(" ")
);
}
});
/** @id MochiKit.Logging.Logger */
MochiKit.Logging.Logger = function (/* optional */maxSize) {
this.counter = 0;
if (typeof(maxSize) == 'undefined' || maxSize === null) {
maxSize = -1;
}
this.maxSize = maxSize;
this._messages = [];
this.listeners = {};
this.useNativeConsole = false;
};
MochiKit.Logging.Logger.prototype = {
/** @id MochiKit.Logging.Logger.prototype.clear */
clear: function () {
this._messages.splice(0, this._messages.length);
},
/** @id MochiKit.Logging.Logger.prototype.logToConsole */
logToConsole: function (msg) {
if (typeof(window) != "undefined" && window.console
&& window.console.log) {
// Safari and FireBug 0.4
// Percent replacement is a workaround for cute Safari crashing bug
window.console.log(msg.replace(/%/g, '\uFF05'));
} else if (typeof(opera) != "undefined" && opera.postError) {
// Opera
opera.postError(msg);
} else if (typeof(printfire) == "function") {
// FireBug 0.3 and earlier
printfire(msg);
} else if (typeof(Debug) != "undefined" && Debug.writeln) {
// IE Web Development Helper (?)
// http://www.nikhilk.net/Entry.aspx?id=93
Debug.writeln(msg);
} else if (typeof(debug) != "undefined" && debug.trace) {
// Atlas framework (?)
// http://www.nikhilk.net/Entry.aspx?id=93
debug.trace(msg);
}
},
/** @id MochiKit.Logging.Logger.prototype.dispatchListeners */
dispatchListeners: function (msg) {
for (var k in this.listeners) {
var pair = this.listeners[k];
if (pair.ident != k || (pair[0] && !pair[0](msg))) {
continue;
}
pair[1](msg);
}
},
/** @id MochiKit.Logging.Logger.prototype.addListener */
addListener: function (ident, filter, listener) {
if (typeof(filter) == 'string') {
filter = MochiKit.Logging.logLevelAtLeast(filter);
}
var entry = [filter, listener];
entry.ident = ident;
this.listeners[ident] = entry;
},
/** @id MochiKit.Logging.Logger.prototype.removeListener */
removeListener: function (ident) {
delete this.listeners[ident];
},
/** @id MochiKit.Logging.Logger.prototype.baseLog */
baseLog: function (level, message/*, ...*/) {
if (typeof(level) == "number") {
if (level >= MochiKit.Logging.LogLevel.FATAL) {
level = 'FATAL';
} else if (level >= MochiKit.Logging.LogLevel.ERROR) {
level = 'ERROR';
} else if (level >= MochiKit.Logging.LogLevel.WARNING) {
level = 'WARNING';
} else if (level >= MochiKit.Logging.LogLevel.INFO) {
level = 'INFO';
} else {
level = 'DEBUG';
}
}
var msg = new MochiKit.Logging.LogMessage(
this.counter,
level,
MochiKit.Base.extend(null, arguments, 1)
);
this._messages.push(msg);
this.dispatchListeners(msg);
if (this.useNativeConsole) {
this.logToConsole(msg.level + ": " + msg.info.join(" "));
}
this.counter += 1;
while (this.maxSize >= 0 && this._messages.length > this.maxSize) {
this._messages.shift();
}
},
/** @id MochiKit.Logging.Logger.prototype.getMessages */
getMessages: function (howMany) {
var firstMsg = 0;
if (!(typeof(howMany) == 'undefined' || howMany === null)) {
firstMsg = Math.max(0, this._messages.length - howMany);
}
return this._messages.slice(firstMsg);
},
/** @id MochiKit.Logging.Logger.prototype.getMessageText */
getMessageText: function (howMany) {
if (typeof(howMany) == 'undefined' || howMany === null) {
howMany = 30;
}
var messages = this.getMessages(howMany);
if (messages.length) {
var lst = map(function (m) {
return '\n [' + m.num + '] ' + m.level + ': ' + m.info.join(' ');
}, messages);
lst.unshift('LAST ' + messages.length + ' MESSAGES:');
return lst.join('');
}
return '';
},
/** @id MochiKit.Logging.Logger.prototype.debuggingBookmarklet */
debuggingBookmarklet: function (inline) {
if (typeof(MochiKit.LoggingPane) == "undefined") {
alert(this.getMessageText());
} else {
MochiKit.LoggingPane.createLoggingPane(inline || false);
}
}
};
MochiKit.Logging.__new__ = function () {
this.LogLevel = {
ERROR: 40,
FATAL: 50,
WARNING: 30,
INFO: 20,
DEBUG: 10
};
var m = MochiKit.Base;
m.registerComparator("LogMessage",
this.isLogMessage,
this.compareLogMessage
);
var partial = m.partial;
var Logger = this.Logger;
var baseLog = Logger.prototype.baseLog;
m.update(this.Logger.prototype, {
debug: partial(baseLog, 'DEBUG'),
log: partial(baseLog, 'INFO'),
error: partial(baseLog, 'ERROR'),
fatal: partial(baseLog, 'FATAL'),
warning: partial(baseLog, 'WARNING')
});
// indirectly find logger so it can be replaced
var self = this;
var connectLog = function (name) {
return function () {
self.logger[name].apply(self.logger, arguments);
};
};
/** @id MochiKit.Logging.log */
this.log = connectLog('log');
/** @id MochiKit.Logging.logError */
this.logError = connectLog('error');
/** @id MochiKit.Logging.logDebug */
this.logDebug = connectLog('debug');
/** @id MochiKit.Logging.logFatal */
this.logFatal = connectLog('fatal');
/** @id MochiKit.Logging.logWarning */
this.logWarning = connectLog('warning');
this.logger = new Logger();
this.logger.useNativeConsole = true;
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
};
if (typeof(printfire) == "undefined" &&
typeof(document) != "undefined" && document.createEvent &&
typeof(dispatchEvent) != "undefined") {
// FireBug really should be less lame about this global function
printfire = function () {
printfire.args = arguments;
var ev = document.createEvent("Events");
ev.initEvent("printfire", false, true);
dispatchEvent(ev);
};
}
MochiKit.Logging.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Logging);
-353
View File
@@ -1,353 +0,0 @@
/***
MochiKit.LoggingPane 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('LoggingPane', ['Base', 'Logging']);
MochiKit.LoggingPane.NAME = "MochiKit.LoggingPane";
MochiKit.LoggingPane.VERSION = "1.4.2";
MochiKit.LoggingPane.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.LoggingPane.toString = function () {
return this.__repr__();
};
/** @id MochiKit.LoggingPane.createLoggingPane */
MochiKit.LoggingPane.createLoggingPane = function (inline/* = false */) {
var m = MochiKit.LoggingPane;
inline = !(!inline);
if (m._loggingPane && m._loggingPane.inline != inline) {
m._loggingPane.closePane();
m._loggingPane = null;
}
if (!m._loggingPane || m._loggingPane.closed) {
m._loggingPane = new m.LoggingPane(inline, MochiKit.Logging.logger);
}
return m._loggingPane;
};
/** @id MochiKit.LoggingPane.LoggingPane */
MochiKit.LoggingPane.LoggingPane = function (inline/* = false */, logger/* = MochiKit.Logging.logger */) {
/* Use a div if inline, pop up a window if not */
/* Create the elements */
if (typeof(logger) == "undefined" || logger === null) {
logger = MochiKit.Logging.logger;
}
this.logger = logger;
var update = MochiKit.Base.update;
var updatetree = MochiKit.Base.updatetree;
var bind = MochiKit.Base.bind;
var clone = MochiKit.Base.clone;
var win = window;
var uid = "_MochiKit_LoggingPane";
if (typeof(MochiKit.DOM) != "undefined") {
win = MochiKit.DOM.currentWindow();
}
if (!inline) {
// name the popup with the base URL for uniqueness
var url = win.location.href.split("?")[0].replace(/[#:\/.><&%-]/g, "_");
var name = uid + "_" + url;
var nwin = win.open("", name, "dependent,resizable,height=200");
if (!nwin) {
alert("Not able to open debugging window due to pop-up blocking.");
return undefined;
}
nwin.document.write(
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
+ '"http://www.w3.org/TR/html4/loose.dtd">'
+ '<html><head><title>[MochiKit.LoggingPane]</title></head>'
+ '<body></body></html>'
);
nwin.document.close();
nwin.document.title += ' ' + win.document.title;
win = nwin;
}
var doc = win.document;
this.doc = doc;
// Connect to the debug pane if it already exists (i.e. in a window orphaned by the page being refreshed)
var debugPane = doc.getElementById(uid);
var existing_pane = !!debugPane;
if (debugPane && typeof(debugPane.loggingPane) != "undefined") {
debugPane.loggingPane.logger = this.logger;
debugPane.loggingPane.buildAndApplyFilter();
return debugPane.loggingPane;
}
if (existing_pane) {
// clear any existing contents
var child;
while ((child = debugPane.firstChild)) {
debugPane.removeChild(child);
}
} else {
debugPane = doc.createElement("div");
debugPane.id = uid;
}
debugPane.loggingPane = this;
var levelFilterField = doc.createElement("input");
var infoFilterField = doc.createElement("input");
var filterButton = doc.createElement("button");
var loadButton = doc.createElement("button");
var clearButton = doc.createElement("button");
var closeButton = doc.createElement("button");
var logPaneArea = doc.createElement("div");
var logPane = doc.createElement("div");
/* Set up the functions */
var listenerId = uid + "_Listener";
this.colorTable = clone(this.colorTable);
var messages = [];
var messageFilter = null;
/** @id MochiKit.LoggingPane.messageLevel */
var messageLevel = function (msg) {
var level = msg.level;
if (typeof(level) == "number") {
level = MochiKit.Logging.LogLevel[level];
}
return level;
};
/** @id MochiKit.LoggingPane.messageText */
var messageText = function (msg) {
return msg.info.join(" ");
};
/** @id MochiKit.LoggingPane.addMessageText */
var addMessageText = bind(function (msg) {
var level = messageLevel(msg);
var text = messageText(msg);
var c = this.colorTable[level];
var p = doc.createElement("span");
p.className = "MochiKit-LogMessage MochiKit-LogLevel-" + level;
p.style.cssText = "margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: " + c;
p.appendChild(doc.createTextNode(level + ": " + text));
logPane.appendChild(p);
logPane.appendChild(doc.createElement("br"));
if (logPaneArea.offsetHeight > logPaneArea.scrollHeight) {
logPaneArea.scrollTop = 0;
} else {
logPaneArea.scrollTop = logPaneArea.scrollHeight;
}
}, this);
/** @id MochiKit.LoggingPane.addMessage */
var addMessage = function (msg) {
messages[messages.length] = msg;
addMessageText(msg);
};
/** @id MochiKit.LoggingPane.buildMessageFilter */
var buildMessageFilter = function () {
var levelre, infore;
try {
/* Catch any exceptions that might arise due to invalid regexes */
levelre = new RegExp(levelFilterField.value);
infore = new RegExp(infoFilterField.value);
} catch(e) {
/* If there was an error with the regexes, do no filtering */
logDebug("Error in filter regex: " + e.message);
return null;
}
return function (msg) {
return (
levelre.test(messageLevel(msg)) &&
infore.test(messageText(msg))
);
};
};
/** @id MochiKit.LoggingPane.clearMessagePane */
var clearMessagePane = function () {
while (logPane.firstChild) {
logPane.removeChild(logPane.firstChild);
}
};
/** @id MochiKit.LoggingPane.clearMessages */
var clearMessages = function () {
messages = [];
clearMessagePane();
};
/** @id MochiKit.LoggingPane.closePane */
var closePane = bind(function () {
if (this.closed) {
return;
}
this.closed = true;
if (MochiKit.LoggingPane._loggingPane == this) {
MochiKit.LoggingPane._loggingPane = null;
}
this.logger.removeListener(listenerId);
try {
try {
debugPane.loggingPane = null;
} catch(e) { logFatal("Bookmarklet was closed incorrectly."); }
if (inline) {
debugPane.parentNode.removeChild(debugPane);
} else {
this.win.close();
}
} catch(e) {}
}, this);
/** @id MochiKit.LoggingPane.filterMessages */
var filterMessages = function () {
clearMessagePane();
for (var i = 0; i < messages.length; i++) {
var msg = messages[i];
if (messageFilter === null || messageFilter(msg)) {
addMessageText(msg);
}
}
};
this.buildAndApplyFilter = function () {
messageFilter = buildMessageFilter();
filterMessages();
this.logger.removeListener(listenerId);
this.logger.addListener(listenerId, messageFilter, addMessage);
};
/** @id MochiKit.LoggingPane.loadMessages */
var loadMessages = bind(function () {
messages = this.logger.getMessages();
filterMessages();
}, this);
/** @id MochiKit.LoggingPane.filterOnEnter */
var filterOnEnter = bind(function (event) {
event = event || window.event;
key = event.which || event.keyCode;
if (key == 13) {
this.buildAndApplyFilter();
}
}, this);
/* Create the debug pane */
var style = "display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: " + this.logFont;
if (inline) {
style += "; height: 10em; border-top: 2px solid black";
} else {
style += "; height: 100%;";
}
debugPane.style.cssText = style;
if (!existing_pane) {
doc.body.appendChild(debugPane);
}
/* Create the filter fields */
style = {"cssText": "width: 33%; display: inline; font: " + this.logFont};
updatetree(levelFilterField, {
"value": "FATAL|ERROR|WARNING|INFO|DEBUG",
"onkeypress": filterOnEnter,
"style": style
});
debugPane.appendChild(levelFilterField);
updatetree(infoFilterField, {
"value": ".*",
"onkeypress": filterOnEnter,
"style": style
});
debugPane.appendChild(infoFilterField);
/* Create the buttons */
style = "width: 8%; display:inline; font: " + this.logFont;
filterButton.appendChild(doc.createTextNode("Filter"));
filterButton.onclick = bind("buildAndApplyFilter", this);
filterButton.style.cssText = style;
debugPane.appendChild(filterButton);
loadButton.appendChild(doc.createTextNode("Load"));
loadButton.onclick = loadMessages;
loadButton.style.cssText = style;
debugPane.appendChild(loadButton);
clearButton.appendChild(doc.createTextNode("Clear"));
clearButton.onclick = clearMessages;
clearButton.style.cssText = style;
debugPane.appendChild(clearButton);
closeButton.appendChild(doc.createTextNode("Close"));
closeButton.onclick = closePane;
closeButton.style.cssText = style;
debugPane.appendChild(closeButton);
/* Create the logging pane */
logPaneArea.style.cssText = "overflow: auto; width: 100%";
logPane.style.cssText = "width: 100%; height: " + (inline ? "8em" : "100%");
logPaneArea.appendChild(logPane);
debugPane.appendChild(logPaneArea);
this.buildAndApplyFilter();
loadMessages();
if (inline) {
this.win = undefined;
} else {
this.win = win;
}
this.inline = inline;
this.closePane = closePane;
this.closed = false;
return this;
};
MochiKit.LoggingPane.LoggingPane.prototype = {
"logFont": "8pt Verdana,sans-serif",
"colorTable": {
"ERROR": "red",
"FATAL": "darkred",
"WARNING": "blue",
"INFO": "black",
"DEBUG": "green"
}
};
MochiKit.LoggingPane.EXPORT_OK = [
"LoggingPane"
];
MochiKit.LoggingPane.EXPORT = [
"createLoggingPane"
];
MochiKit.LoggingPane.__new__ = function () {
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
};
MochiKit.Base.nameFunctions(this);
MochiKit.LoggingPane._loggingPane = null;
};
MochiKit.LoggingPane.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.LoggingPane);
-188
View File
@@ -1,188 +0,0 @@
/***
MochiKit.MochiKit 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
if (typeof(MochiKit) == 'undefined') {
MochiKit = {};
}
if (typeof(MochiKit.MochiKit) == 'undefined') {
/** @id MochiKit.MochiKit */
MochiKit.MochiKit = {};
}
MochiKit.MochiKit.NAME = "MochiKit.MochiKit";
MochiKit.MochiKit.VERSION = "1.4.2";
MochiKit.MochiKit.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
/** @id MochiKit.MochiKit.toString */
MochiKit.MochiKit.toString = function () {
return this.__repr__();
};
/** @id MochiKit.MochiKit.SUBMODULES */
MochiKit.MochiKit.SUBMODULES = [
"Base",
"Iter",
"Logging",
"DateTime",
"Format",
"Async",
"DOM",
"Selector",
"Style",
"LoggingPane",
"Color",
"Signal",
"Position",
"Visual",
"DragAndDrop",
"Sortable"
];
if (typeof(JSAN) != 'undefined' || typeof(dojo) != 'undefined') {
if (typeof(dojo) != 'undefined') {
dojo.provide('MochiKit.MochiKit');
(function (lst) {
for (var i = 0; i < lst.length; i++) {
dojo.require("MochiKit." + lst[i]);
}
})(MochiKit.MochiKit.SUBMODULES);
}
if (typeof(JSAN) != 'undefined') {
(function (lst) {
for (var i = 0; i < lst.length; i++) {
JSAN.use("MochiKit." + lst[i], []);
}
})(MochiKit.MochiKit.SUBMODULES);
}
(function () {
var extend = MochiKit.Base.extend;
var self = MochiKit.MochiKit;
var modules = self.SUBMODULES;
var EXPORT = [];
var EXPORT_OK = [];
var EXPORT_TAGS = {};
var i, k, m, all;
for (i = 0; i < modules.length; i++) {
m = MochiKit[modules[i]];
extend(EXPORT, m.EXPORT);
extend(EXPORT_OK, m.EXPORT_OK);
for (k in m.EXPORT_TAGS) {
EXPORT_TAGS[k] = extend(EXPORT_TAGS[k], m.EXPORT_TAGS[k]);
}
all = m.EXPORT_TAGS[":all"];
if (!all) {
all = extend(null, m.EXPORT, m.EXPORT_OK);
}
var j;
for (j = 0; j < all.length; j++) {
k = all[j];
self[k] = m[k];
}
}
self.EXPORT = EXPORT;
self.EXPORT_OK = EXPORT_OK;
self.EXPORT_TAGS = EXPORT_TAGS;
}());
} else {
if (typeof(MochiKit.__compat__) == 'undefined') {
MochiKit.__compat__ = true;
}
(function () {
if (typeof(document) == "undefined") {
return;
}
var scripts = document.getElementsByTagName("script");
var kXHTMLNSURI = "http://www.w3.org/1999/xhtml";
var kSVGNSURI = "http://www.w3.org/2000/svg";
var kXLINKNSURI = "http://www.w3.org/1999/xlink";
var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var base = null;
var baseElem = null;
var allScripts = {};
var i;
var src;
for (i = 0; i < scripts.length; i++) {
src = null;
switch (scripts[i].namespaceURI) {
case kSVGNSURI:
src = scripts[i].getAttributeNS(kXLINKNSURI, "href");
break;
/*
case null: // HTML
case '': // HTML
case kXHTMLNSURI:
case kXULNSURI:
*/
default:
src = scripts[i].getAttribute("src");
break;
}
if (!src) {
continue;
}
allScripts[src] = true;
if (src.match(/MochiKit.js(\?.*)?$/)) {
base = src.substring(0, src.lastIndexOf('MochiKit.js'));
baseElem = scripts[i];
}
}
if (base === null) {
return;
}
var modules = MochiKit.MochiKit.SUBMODULES;
for (var i = 0; i < modules.length; i++) {
if (MochiKit[modules[i]]) {
continue;
}
var uri = base + modules[i] + '.js';
if (uri in allScripts) {
continue;
}
if (baseElem.namespaceURI == kSVGNSURI ||
baseElem.namespaceURI == kXULNSURI) {
// SVG, XUL
/*
SVG does not support document.write, so if Safari wants to
support SVG tests it should fix its deferred loading bug
(see following below).
*/
var s = document.createElementNS(baseElem.namespaceURI, 'script');
s.setAttribute("id", "MochiKit_" + base + modules[i]);
if (baseElem.namespaceURI == kSVGNSURI) {
s.setAttributeNS(kXLINKNSURI, 'href', uri);
} else {
s.setAttribute('src', uri);
}
s.setAttribute("type", "application/x-javascript");
baseElem.parentNode.appendChild(s);
} else {
// HTML, XHTML
/*
DOM can not be used here because Safari does
deferred loading of scripts unless they are
in the document or inserted with document.write
This is not XHTML compliant. If you want XHTML
compliance then you must use the packed version of MochiKit
or include each script individually (basically unroll
these document.write calls into your XHTML source)
*/
document.write('<' + baseElem.nodeName + ' src="' + uri +
'" type="text/javascript"></script>');
}
};
})();
}
-115
View File
@@ -1,115 +0,0 @@
/***
MochiKit.MockDOM 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
if (typeof(MochiKit) == "undefined") {
MochiKit = {};
}
if (typeof(MochiKit.MockDOM) == "undefined") {
MochiKit.MockDOM = {};
}
MochiKit.MockDOM.NAME = "MochiKit.MockDOM";
MochiKit.MockDOM.VERSION = "1.4.2";
MochiKit.MockDOM.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
/** @id MochiKit.MockDOM.toString */
MochiKit.MockDOM.toString = function () {
return this.__repr__();
};
/** @id MochiKit.MockDOM.createDocument */
MochiKit.MockDOM.createDocument = function () {
var doc = new MochiKit.MockDOM.MockElement("DOCUMENT");
doc.body = doc.createElement("BODY");
doc.appendChild(doc.body);
return doc;
};
/** @id MochiKit.MockDOM.MockElement */
MochiKit.MockDOM.MockElement = function (name, data, ownerDocument) {
this.tagName = this.nodeName = name.toUpperCase();
this.ownerDocument = ownerDocument || null;
if (name == "DOCUMENT") {
this.nodeType = 9;
this.childNodes = [];
} else if (typeof(data) == "string") {
this.nodeValue = data;
this.nodeType = 3;
} else {
this.nodeType = 1;
this.childNodes = [];
}
if (name.substring(0, 1) == "<") {
var nameattr = name.substring(
name.indexOf('"') + 1, name.lastIndexOf('"'));
name = name.substring(1, name.indexOf(" "));
this.tagName = this.nodeName = name.toUpperCase();
this.setAttribute("name", nameattr);
}
};
MochiKit.MockDOM.MockElement.prototype = {
/** @id MochiKit.MockDOM.MockElement.prototype.createElement */
createElement: function (tagName) {
return new MochiKit.MockDOM.MockElement(tagName, null, this.nodeType == 9 ? this : this.ownerDocument);
},
/** @id MochiKit.MockDOM.MockElement.prototype.createTextNode */
createTextNode: function (text) {
return new MochiKit.MockDOM.MockElement("text", text, this.nodeType == 9 ? this : this.ownerDocument);
},
/** @id MochiKit.MockDOM.MockElement.prototype.setAttribute */
setAttribute: function (name, value) {
this[name] = value;
},
/** @id MochiKit.MockDOM.MockElement.prototype.getAttribute */
getAttribute: function (name) {
return this[name];
},
/** @id MochiKit.MockDOM.MockElement.prototype.appendChild */
appendChild: function (child) {
this.childNodes.push(child);
},
/** @id MochiKit.MockDOM.MockElement.prototype.toString */
toString: function () {
return "MockElement(" + this.tagName + ")";
},
/** @id MochiKit.MockDOM.MockElement.prototype.getElementsByTagName */
getElementsByTagName: function (tagName) {
var foundElements = [];
MochiKit.Base.nodeWalk(this, function(node){
if (tagName == '*' || tagName == node.tagName) {
foundElements.push(node);
return node.childNodes;
}
});
return foundElements;
}
};
/** @id MochiKit.MockDOM.EXPORT_OK */
MochiKit.MockDOM.EXPORT_OK = [
"mockElement",
"createDocument"
];
/** @id MochiKit.MockDOM.EXPORT */
MochiKit.MockDOM.EXPORT = [
"document"
];
MochiKit.MockDOM.__new__ = function () {
this.document = this.createDocument();
};
MochiKit.MockDOM.__new__();
-236
View File
@@ -1,236 +0,0 @@
/***
MochiKit.Position 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005-2006 Bob Ippolito and others. All rights Reserved.
***/
MochiKit.Base._deps('Position', ['Base', 'DOM', 'Style']);
MochiKit.Position.NAME = 'MochiKit.Position';
MochiKit.Position.VERSION = '1.4.2';
MochiKit.Position.__repr__ = function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
};
MochiKit.Position.toString = function () {
return this.__repr__();
};
MochiKit.Position.EXPORT_OK = [];
MochiKit.Position.EXPORT = [
];
MochiKit.Base.update(MochiKit.Position, {
// set to true if needed, warning: firefox performance problems
// NOT neeeded for page scrolling, only if draggable contained in
// scrollable elements
includeScrollOffsets: false,
/** @id MochiKit.Position.prepare */
prepare: function () {
var deltaX = window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft
|| 0;
var deltaY = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
this.windowOffset = new MochiKit.Style.Coordinates(deltaX, deltaY);
},
/** @id MochiKit.Position.cumulativeOffset */
cumulativeOffset: function (element) {
var valueT = 0;
var valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
} while (element);
return new MochiKit.Style.Coordinates(valueL, valueT);
},
/** @id MochiKit.Position.realOffset */
realOffset: function (element) {
var valueT = 0;
var valueL = 0;
do {
valueT += element.scrollTop || 0;
valueL += element.scrollLeft || 0;
element = element.parentNode;
} while (element);
return new MochiKit.Style.Coordinates(valueL, valueT);
},
/** @id MochiKit.Position.within */
within: function (element, x, y) {
if (this.includeScrollOffsets) {
return this.withinIncludingScrolloffsets(element, x, y);
}
this.xcomp = x;
this.ycomp = y;
this.offset = this.cumulativeOffset(element);
if (element.style.position == "fixed") {
this.offset.x += this.windowOffset.x;
this.offset.y += this.windowOffset.y;
}
return (y >= this.offset.y &&
y < this.offset.y + element.offsetHeight &&
x >= this.offset.x &&
x < this.offset.x + element.offsetWidth);
},
/** @id MochiKit.Position.withinIncludingScrolloffsets */
withinIncludingScrolloffsets: function (element, x, y) {
var offsetcache = this.realOffset(element);
this.xcomp = x + offsetcache.x - this.windowOffset.x;
this.ycomp = y + offsetcache.y - this.windowOffset.y;
this.offset = this.cumulativeOffset(element);
return (this.ycomp >= this.offset.y &&
this.ycomp < this.offset.y + element.offsetHeight &&
this.xcomp >= this.offset.x &&
this.xcomp < this.offset.x + element.offsetWidth);
},
// within must be called directly before
/** @id MochiKit.Position.overlap */
overlap: function (mode, element) {
if (!mode) {
return 0;
}
if (mode == 'vertical') {
return ((this.offset.y + element.offsetHeight) - this.ycomp) /
element.offsetHeight;
}
if (mode == 'horizontal') {
return ((this.offset.x + element.offsetWidth) - this.xcomp) /
element.offsetWidth;
}
},
/** @id MochiKit.Position.absolutize */
absolutize: function (element) {
element = MochiKit.DOM.getElement(element);
if (element.style.position == 'absolute') {
return;
}
MochiKit.Position.prepare();
var offsets = MochiKit.Position.positionedOffset(element);
var width = element.clientWidth;
var height = element.clientHeight;
var oldStyle = {
'position': element.style.position,
'left': offsets.x - parseFloat(element.style.left || 0),
'top': offsets.y - parseFloat(element.style.top || 0),
'width': element.style.width,
'height': element.style.height
};
element.style.position = 'absolute';
element.style.top = offsets.y + 'px';
element.style.left = offsets.x + 'px';
element.style.width = width + 'px';
element.style.height = height + 'px';
return oldStyle;
},
/** @id MochiKit.Position.positionedOffset */
positionedOffset: function (element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
if (element) {
p = MochiKit.Style.getStyle(element, 'position');
if (p == 'relative' || p == 'absolute') {
break;
}
}
} while (element);
return new MochiKit.Style.Coordinates(valueL, valueT);
},
/** @id MochiKit.Position.relativize */
relativize: function (element, oldPos) {
element = MochiKit.DOM.getElement(element);
if (element.style.position == 'relative') {
return;
}
MochiKit.Position.prepare();
var top = parseFloat(element.style.top || 0) -
(oldPos['top'] || 0);
var left = parseFloat(element.style.left || 0) -
(oldPos['left'] || 0);
element.style.position = oldPos['position'];
element.style.top = top + 'px';
element.style.left = left + 'px';
element.style.width = oldPos['width'];
element.style.height = oldPos['height'];
},
/** @id MochiKit.Position.clone */
clone: function (source, target) {
source = MochiKit.DOM.getElement(source);
target = MochiKit.DOM.getElement(target);
target.style.position = 'absolute';
var offsets = this.cumulativeOffset(source);
target.style.top = offsets.y + 'px';
target.style.left = offsets.x + 'px';
target.style.width = source.offsetWidth + 'px';
target.style.height = source.offsetHeight + 'px';
},
/** @id MochiKit.Position.page */
page: function (forElement) {
var valueT = 0;
var valueL = 0;
var element = forElement;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
// Safari fix
if (element.offsetParent == document.body && MochiKit.Style.getStyle(element, 'position') == 'absolute') {
break;
}
} while (element = element.offsetParent);
element = forElement;
do {
valueT -= element.scrollTop || 0;
valueL -= element.scrollLeft || 0;
} while (element = element.parentNode);
return new MochiKit.Style.Coordinates(valueL, valueT);
}
});
MochiKit.Position.__new__ = function (win) {
var m = MochiKit.Base;
this.EXPORT_TAGS = {
':common': this.EXPORT,
':all': m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
};
MochiKit.Position.__new__(this);
MochiKit.Base._exportSymbols(this, MochiKit.Position);
-415
View File
@@ -1,415 +0,0 @@
/***
MochiKit.Selector 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito and others. All rights Reserved.
***/
MochiKit.Base._deps('Selector', ['Base', 'DOM', 'Iter']);
MochiKit.Selector.NAME = "MochiKit.Selector";
MochiKit.Selector.VERSION = "1.4.2";
MochiKit.Selector.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.Selector.toString = function () {
return this.__repr__();
};
MochiKit.Selector.EXPORT = [
"Selector",
"findChildElements",
"findDocElements",
"$$"
];
MochiKit.Selector.EXPORT_OK = [
];
MochiKit.Selector.Selector = function (expression) {
this.params = {classNames: [], pseudoClassNames: []};
this.expression = expression.toString().replace(/(^\s+|\s+$)/g, '');
this.parseExpression();
this.compileMatcher();
};
MochiKit.Selector.Selector.prototype = {
/***
Selector class: convenient object to make CSS selections.
***/
__class__: MochiKit.Selector.Selector,
/** @id MochiKit.Selector.Selector.prototype.parseExpression */
parseExpression: function () {
function abort(message) {
throw 'Parse error in selector: ' + message;
}
if (this.expression == '') {
abort('empty expression');
}
var repr = MochiKit.Base.repr;
var params = this.params;
var expr = this.expression;
var match, modifier, clause, rest;
while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!^$*]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
params.attributes = params.attributes || [];
params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
expr = match[1];
}
if (expr == '*') {
return this.params.wildcard = true;
}
while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+(?:\([^)]*\))?)(.*)/i)) {
modifier = match[1];
clause = match[2];
rest = match[3];
switch (modifier) {
case '#':
params.id = clause;
break;
case '.':
params.classNames.push(clause);
break;
case ':':
params.pseudoClassNames.push(clause);
break;
case '':
case undefined:
params.tagName = clause.toUpperCase();
break;
default:
abort(repr(expr));
}
expr = rest;
}
if (expr.length > 0) {
abort(repr(expr));
}
},
/** @id MochiKit.Selector.Selector.prototype.buildMatchExpression */
buildMatchExpression: function () {
var repr = MochiKit.Base.repr;
var params = this.params;
var conditions = [];
var clause, i;
function childElements(element) {
return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)";
}
if (params.wildcard) {
conditions.push('true');
}
if (clause = params.id) {
conditions.push('element.id == ' + repr(clause));
}
if (clause = params.tagName) {
conditions.push('element.tagName.toUpperCase() == ' + repr(clause));
}
if ((clause = params.classNames).length > 0) {
for (i = 0; i < clause.length; i++) {
conditions.push('MochiKit.DOM.hasElementClass(element, ' + repr(clause[i]) + ')');
}
}
if ((clause = params.pseudoClassNames).length > 0) {
for (i = 0; i < clause.length; i++) {
var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/);
var pseudoClass = match[1];
var pseudoClassArgument = match[2];
switch (pseudoClass) {
case 'root':
conditions.push('element.nodeType == 9 || element === element.ownerDocument.documentElement'); break;
case 'nth-child':
case 'nth-last-child':
case 'nth-of-type':
case 'nth-last-of-type':
match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/);
if (!match) {
throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument;
}
var a, b;
if (match[0] == 'odd') {
a = 2;
b = 1;
} else if (match[0] == 'even') {
a = 2;
b = 0;
} else {
a = match[2] && parseInt(match) || null;
b = parseInt(match[3]);
}
conditions.push('this.nthChild(element,' + a + ',' + b
+ ',' + !!pseudoClass.match('^nth-last') // Reverse
+ ',' + !!pseudoClass.match('of-type$') // Restrict to same tagName
+ ')');
break;
case 'first-child':
conditions.push('this.nthChild(element, null, 1)');
break;
case 'last-child':
conditions.push('this.nthChild(element, null, 1, true)');
break;
case 'first-of-type':
conditions.push('this.nthChild(element, null, 1, false, true)');
break;
case 'last-of-type':
conditions.push('this.nthChild(element, null, 1, true, true)');
break;
case 'only-child':
conditions.push(childElements('element.parentNode') + '.length == 1');
break;
case 'only-of-type':
conditions.push('MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, ' + childElements('element.parentNode') + ').length == 1');
break;
case 'empty':
conditions.push('element.childNodes.length == 0');
break;
case 'enabled':
conditions.push('(this.isUIElement(element) && element.disabled === false)');
break;
case 'disabled':
conditions.push('(this.isUIElement(element) && element.disabled === true)');
break;
case 'checked':
conditions.push('(this.isUIElement(element) && element.checked === true)');
break;
case 'not':
var subselector = new MochiKit.Selector.Selector(pseudoClassArgument);
conditions.push('!( ' + subselector.buildMatchExpression() + ')')
break;
}
}
}
if (clause = params.attributes) {
MochiKit.Base.map(function (attribute) {
var value = 'MochiKit.DOM.getNodeAttribute(element, ' + repr(attribute.name) + ')';
var splitValueBy = function (delimiter) {
return value + '.split(' + repr(delimiter) + ')';
}
conditions.push(value + ' != null');
switch (attribute.operator) {
case '=':
conditions.push(value + ' == ' + repr(attribute.value));
break;
case '~=':
conditions.push('MochiKit.Base.findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1');
break;
case '^=':
conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value));
break;
case '$=':
conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value));
break;
case '*=':
conditions.push(value + '.match(' + repr(attribute.value) + ')');
break;
case '|=':
conditions.push(splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase()));
break;
case '!=':
conditions.push(value + ' != ' + repr(attribute.value));
break;
case '':
case undefined:
// Condition already added above
break;
default:
throw 'Unknown operator ' + attribute.operator + ' in selector';
}
}, clause);
}
return conditions.join(' && ');
},
/** @id MochiKit.Selector.Selector.prototype.compileMatcher */
compileMatcher: function () {
var code = 'return (!element.tagName) ? false : ' +
this.buildMatchExpression() + ';';
this.match = new Function('element', code);
},
/** @id MochiKit.Selector.Selector.prototype.nthChild */
nthChild: function (element, a, b, reverse, sametag){
var siblings = MochiKit.Base.filter(function (node) {
return node.nodeType == 1;
}, element.parentNode.childNodes);
if (sametag) {
siblings = MochiKit.Base.filter(function (node) {
return node.tagName == element.tagName;
}, siblings);
}
if (reverse) {
siblings = MochiKit.Iter.reversed(siblings);
}
if (a) {
var actualIndex = MochiKit.Base.findIdentical(siblings, element);
return ((actualIndex + 1 - b) / a) % 1 == 0;
} else {
return b == MochiKit.Base.findIdentical(siblings, element) + 1;
}
},
/** @id MochiKit.Selector.Selector.prototype.isUIElement */
isUIElement: function (element) {
return MochiKit.Base.findValue(['input', 'button', 'select', 'option', 'textarea', 'object'],
element.tagName.toLowerCase()) > -1;
},
/** @id MochiKit.Selector.Selector.prototype.findElements */
findElements: function (scope, axis) {
var element;
if (axis == undefined) {
axis = "";
}
function inScope(element, scope) {
if (axis == "") {
return MochiKit.DOM.isChildNode(element, scope);
} else if (axis == ">") {
return element.parentNode === scope;
} else if (axis == "+") {
return element === nextSiblingElement(scope);
} else if (axis == "~") {
var sibling = scope;
while (sibling = nextSiblingElement(sibling)) {
if (element === sibling) {
return true;
}
}
return false;
} else {
throw "Invalid axis: " + axis;
}
}
if (element = MochiKit.DOM.getElement(this.params.id)) {
if (this.match(element)) {
if (!scope || inScope(element, scope)) {
return [element];
}
}
}
function nextSiblingElement(node) {
node = node.nextSibling;
while (node && node.nodeType != 1) {
node = node.nextSibling;
}
return node;
}
if (axis == "") {
scope = (scope || MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName || '*');
} else if (axis == ">") {
if (!scope) {
throw "> combinator not allowed without preceeding expression";
}
scope = MochiKit.Base.filter(function (node) {
return node.nodeType == 1;
}, scope.childNodes);
} else if (axis == "+") {
if (!scope) {
throw "+ combinator not allowed without preceeding expression";
}
scope = nextSiblingElement(scope) && [nextSiblingElement(scope)];
} else if (axis == "~") {
if (!scope) {
throw "~ combinator not allowed without preceeding expression";
}
var newscope = [];
while (nextSiblingElement(scope)) {
scope = nextSiblingElement(scope);
newscope.push(scope);
}
scope = newscope;
}
if (!scope) {
return [];
}
var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) {
return this.match(scopeElt);
}, this), scope);
return results;
},
/** @id MochiKit.Selector.Selector.prototype.repr */
repr: function () {
return 'Selector(' + this.expression + ')';
},
toString: MochiKit.Base.forwardCall("repr")
};
MochiKit.Base.update(MochiKit.Selector, {
/** @id MochiKit.Selector.findChildElements */
findChildElements: function (element, expressions) {
var uniq = function(arr) {
var res = [];
for (var i = 0; i < arr.length; i++) {
if (MochiKit.Base.findIdentical(res, arr[i]) < 0) {
res.push(arr[i]);
}
}
return res;
};
return MochiKit.Base.flattenArray(MochiKit.Base.map(function (expression) {
var nextScope = "";
var reducer = function (results, expr) {
if (match = expr.match(/^[>+~]$/)) {
nextScope = match[0];
return results;
} else {
var selector = new MochiKit.Selector.Selector(expr);
var elements = MochiKit.Iter.reduce(function (elements, result) {
return MochiKit.Base.extend(elements, selector.findElements(result || element, nextScope));
}, results, []);
nextScope = "";
return elements;
}
};
var exprs = expression.replace(/(^\s+|\s+$)/g, '').split(/\s+/);
return uniq(MochiKit.Iter.reduce(reducer, exprs, [null]));
}, expressions));
},
findDocElements: function () {
return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments);
},
__new__: function () {
var m = MochiKit.Base;
this.$$ = this.findDocElements;
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
}
});
MochiKit.Selector.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Selector);
-897
View File
@@ -1,897 +0,0 @@
/***
MochiKit.Signal 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('Signal', ['Base', 'DOM', 'Style']);
MochiKit.Signal.NAME = 'MochiKit.Signal';
MochiKit.Signal.VERSION = '1.4.2';
MochiKit.Signal._observers = [];
/** @id MochiKit.Signal.Event */
MochiKit.Signal.Event = function (src, e) {
this._event = e || window.event;
this._src = src;
};
MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
__repr__: function () {
var repr = MochiKit.Base.repr;
var str = '{event(): ' + repr(this.event()) +
', src(): ' + repr(this.src()) +
', type(): ' + repr(this.type()) +
', target(): ' + repr(this.target());
if (this.type() &&
this.type().indexOf('key') === 0 ||
this.type().indexOf('mouse') === 0 ||
this.type().indexOf('click') != -1 ||
this.type() == 'contextmenu') {
str += ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) +
', ctrl: ' + repr(this.modifier().ctrl) +
', meta: ' + repr(this.modifier().meta) +
', shift: ' + repr(this.modifier().shift) +
', any: ' + repr(this.modifier().any) + '}';
}
if (this.type() && this.type().indexOf('key') === 0) {
str += ', key(): {code: ' + repr(this.key().code) +
', string: ' + repr(this.key().string) + '}';
}
if (this.type() && (
this.type().indexOf('mouse') === 0 ||
this.type().indexOf('click') != -1 ||
this.type() == 'contextmenu')) {
str += ', mouse(): {page: ' + repr(this.mouse().page) +
', client: ' + repr(this.mouse().client);
if (this.type() != 'mousemove' && this.type() != 'mousewheel') {
str += ', button: {left: ' + repr(this.mouse().button.left) +
', middle: ' + repr(this.mouse().button.middle) +
', right: ' + repr(this.mouse().button.right) + '}';
}
if (this.type() == 'mousewheel') {
str += ', wheel: ' + repr(this.mouse().wheel);
}
str += '}';
}
if (this.type() == 'mouseover' || this.type() == 'mouseout' ||
this.type() == 'mouseenter' || this.type() == 'mouseleave') {
str += ', relatedTarget(): ' + repr(this.relatedTarget());
}
str += '}';
return str;
},
/** @id MochiKit.Signal.Event.prototype.toString */
toString: function () {
return this.__repr__();
},
/** @id MochiKit.Signal.Event.prototype.src */
src: function () {
return this._src;
},
/** @id MochiKit.Signal.Event.prototype.event */
event: function () {
return this._event;
},
/** @id MochiKit.Signal.Event.prototype.type */
type: function () {
if (this._event.type === "DOMMouseScroll") {
return "mousewheel";
} else {
return this._event.type || undefined;
}
},
/** @id MochiKit.Signal.Event.prototype.target */
target: function () {
return this._event.target || this._event.srcElement;
},
_relatedTarget: null,
/** @id MochiKit.Signal.Event.prototype.relatedTarget */
relatedTarget: function () {
if (this._relatedTarget !== null) {
return this._relatedTarget;
}
var elem = null;
if (this.type() == 'mouseover' || this.type() == 'mouseenter') {
elem = (this._event.relatedTarget ||
this._event.fromElement);
} else if (this.type() == 'mouseout' || this.type() == 'mouseleave') {
elem = (this._event.relatedTarget ||
this._event.toElement);
}
try {
if (elem !== null && elem.nodeType !== null) {
this._relatedTarget = elem;
return elem;
}
} catch (ignore) {
// Firefox 3 throws a permission denied error when accessing
// any property on XUL elements (e.g. scrollbars)...
}
return undefined;
},
_modifier: null,
/** @id MochiKit.Signal.Event.prototype.modifier */
modifier: function () {
if (this._modifier !== null) {
return this._modifier;
}
var m = {};
m.alt = this._event.altKey;
m.ctrl = this._event.ctrlKey;
m.meta = this._event.metaKey || false; // IE and Opera punt here
m.shift = this._event.shiftKey;
m.any = m.alt || m.ctrl || m.shift || m.meta;
this._modifier = m;
return m;
},
_key: null,
/** @id MochiKit.Signal.Event.prototype.key */
key: function () {
if (this._key !== null) {
return this._key;
}
var k = {};
if (this.type() && this.type().indexOf('key') === 0) {
/*
If you're looking for a special key, look for it in keydown or
keyup, but never keypress. If you're looking for a Unicode
chracter, look for it with keypress, but never keyup or
keydown.
Notes:
FF key event behavior:
key event charCode keyCode
DOWN ku,kd 0 40
DOWN kp 0 40
ESC ku,kd 0 27
ESC kp 0 27
a ku,kd 0 65
a kp 97 0
shift+a ku,kd 0 65
shift+a kp 65 0
1 ku,kd 0 49
1 kp 49 0
shift+1 ku,kd 0 0
shift+1 kp 33 0
IE key event behavior:
(IE doesn't fire keypress events for special keys.)
key event keyCode
DOWN ku,kd 40
DOWN kp undefined
ESC ku,kd 27
ESC kp 27
a ku,kd 65
a kp 97
shift+a ku,kd 65
shift+a kp 65
1 ku,kd 49
1 kp 49
shift+1 ku,kd 49
shift+1 kp 33
Safari key event behavior:
(Safari sets charCode and keyCode to something crazy for
special keys.)
key event charCode keyCode
DOWN ku,kd 63233 40
DOWN kp 63233 63233
ESC ku,kd 27 27
ESC kp 27 27
a ku,kd 97 65
a kp 97 97
shift+a ku,kd 65 65
shift+a kp 65 65
1 ku,kd 49 49
1 kp 49 49
shift+1 ku,kd 33 49
shift+1 kp 33 33
*/
/* look for special keys here */
if (this.type() == 'keydown' || this.type() == 'keyup') {
k.code = this._event.keyCode;
k.string = (MochiKit.Signal._specialKeys[k.code] ||
'KEY_UNKNOWN');
this._key = k;
return k;
/* look for characters here */
} else if (this.type() == 'keypress') {
/*
Special key behavior:
IE: does not fire keypress events for special keys
FF: sets charCode to 0, and sets the correct keyCode
Safari: sets keyCode and charCode to something stupid
*/
k.code = 0;
k.string = '';
if (typeof(this._event.charCode) != 'undefined' &&
this._event.charCode !== 0 &&
!MochiKit.Signal._specialMacKeys[this._event.charCode]) {
k.code = this._event.charCode;
k.string = String.fromCharCode(k.code);
} else if (this._event.keyCode &&
typeof(this._event.charCode) == 'undefined') { // IE
k.code = this._event.keyCode;
k.string = String.fromCharCode(k.code);
}
this._key = k;
return k;
}
}
return undefined;
},
_mouse: null,
/** @id MochiKit.Signal.Event.prototype.mouse */
mouse: function () {
if (this._mouse !== null) {
return this._mouse;
}
var m = {};
var e = this._event;
if (this.type() && (
this.type().indexOf('mouse') === 0 ||
this.type().indexOf('click') != -1 ||
this.type() == 'contextmenu')) {
m.client = new MochiKit.Style.Coordinates(0, 0);
if (e.clientX || e.clientY) {
m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX;
m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY;
}
m.page = new MochiKit.Style.Coordinates(0, 0);
if (e.pageX || e.pageY) {
m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
} else {
/*
The IE shortcut can be off by two. We fix it. See:
http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
This is similar to the method used in
MochiKit.Style.getElementPosition().
*/
var de = MochiKit.DOM._document.documentElement;
var b = MochiKit.DOM._document.body;
m.page.x = e.clientX +
(de.scrollLeft || b.scrollLeft) -
(de.clientLeft || 0);
m.page.y = e.clientY +
(de.scrollTop || b.scrollTop) -
(de.clientTop || 0);
}
if (this.type() != 'mousemove' && this.type() != 'mousewheel') {
m.button = {};
m.button.left = false;
m.button.right = false;
m.button.middle = false;
/* we could check e.button, but which is more consistent */
if (e.which) {
m.button.left = (e.which == 1);
m.button.middle = (e.which == 2);
m.button.right = (e.which == 3);
/*
Mac browsers and right click:
- Safari doesn't fire any click events on a right
click:
http://bugs.webkit.org/show_bug.cgi?id=6595
- Firefox fires the event, and sets ctrlKey = true
- Opera fires the event, and sets metaKey = true
oncontextmenu is fired on right clicks between
browsers and across platforms.
*/
} else {
m.button.left = !!(e.button & 1);
m.button.right = !!(e.button & 2);
m.button.middle = !!(e.button & 4);
}
}
if (this.type() == 'mousewheel') {
m.wheel = new MochiKit.Style.Coordinates(0, 0);
if (e.wheelDeltaX || e.wheelDeltaY) {
m.wheel.x = e.wheelDeltaX / -40 || 0;
m.wheel.y = e.wheelDeltaY / -40 || 0;
} else if (e.wheelDelta) {
m.wheel.y = e.wheelDelta / -40;
} else {
m.wheel.y = e.detail || 0;
}
}
this._mouse = m;
return m;
}
return undefined;
},
/** @id MochiKit.Signal.Event.prototype.stop */
stop: function () {
this.stopPropagation();
this.preventDefault();
},
/** @id MochiKit.Signal.Event.prototype.stopPropagation */
stopPropagation: function () {
if (this._event.stopPropagation) {
this._event.stopPropagation();
} else {
this._event.cancelBubble = true;
}
},
/** @id MochiKit.Signal.Event.prototype.preventDefault */
preventDefault: function () {
if (this._event.preventDefault) {
this._event.preventDefault();
} else if (this._confirmUnload === null) {
this._event.returnValue = false;
}
},
_confirmUnload: null,
/** @id MochiKit.Signal.Event.prototype.confirmUnload */
confirmUnload: function (msg) {
if (this.type() == 'beforeunload') {
this._confirmUnload = msg;
this._event.returnValue = msg;
}
}
});
/* Safari sets keyCode to these special values onkeypress. */
MochiKit.Signal._specialMacKeys = {
3: 'KEY_ENTER',
63289: 'KEY_NUM_PAD_CLEAR',
63276: 'KEY_PAGE_UP',
63277: 'KEY_PAGE_DOWN',
63275: 'KEY_END',
63273: 'KEY_HOME',
63234: 'KEY_ARROW_LEFT',
63232: 'KEY_ARROW_UP',
63235: 'KEY_ARROW_RIGHT',
63233: 'KEY_ARROW_DOWN',
63302: 'KEY_INSERT',
63272: 'KEY_DELETE'
};
/* for KEY_F1 - KEY_F12 */
(function () {
var _specialMacKeys = MochiKit.Signal._specialMacKeys;
for (i = 63236; i <= 63242; i++) {
// no F0
_specialMacKeys[i] = 'KEY_F' + (i - 63236 + 1);
}
})();
/* Standard keyboard key codes. */
MochiKit.Signal._specialKeys = {
8: 'KEY_BACKSPACE',
9: 'KEY_TAB',
12: 'KEY_NUM_PAD_CLEAR', // weird, for Safari and Mac FF only
13: 'KEY_ENTER',
16: 'KEY_SHIFT',
17: 'KEY_CTRL',
18: 'KEY_ALT',
19: 'KEY_PAUSE',
20: 'KEY_CAPS_LOCK',
27: 'KEY_ESCAPE',
32: 'KEY_SPACEBAR',
33: 'KEY_PAGE_UP',
34: 'KEY_PAGE_DOWN',
35: 'KEY_END',
36: 'KEY_HOME',
37: 'KEY_ARROW_LEFT',
38: 'KEY_ARROW_UP',
39: 'KEY_ARROW_RIGHT',
40: 'KEY_ARROW_DOWN',
44: 'KEY_PRINT_SCREEN',
45: 'KEY_INSERT',
46: 'KEY_DELETE',
59: 'KEY_SEMICOLON', // weird, for Safari and IE only
91: 'KEY_WINDOWS_LEFT',
92: 'KEY_WINDOWS_RIGHT',
93: 'KEY_SELECT',
106: 'KEY_NUM_PAD_ASTERISK',
107: 'KEY_NUM_PAD_PLUS_SIGN',
109: 'KEY_NUM_PAD_HYPHEN-MINUS',
110: 'KEY_NUM_PAD_FULL_STOP',
111: 'KEY_NUM_PAD_SOLIDUS',
144: 'KEY_NUM_LOCK',
145: 'KEY_SCROLL_LOCK',
186: 'KEY_SEMICOLON',
187: 'KEY_EQUALS_SIGN',
188: 'KEY_COMMA',
189: 'KEY_HYPHEN-MINUS',
190: 'KEY_FULL_STOP',
191: 'KEY_SOLIDUS',
192: 'KEY_GRAVE_ACCENT',
219: 'KEY_LEFT_SQUARE_BRACKET',
220: 'KEY_REVERSE_SOLIDUS',
221: 'KEY_RIGHT_SQUARE_BRACKET',
222: 'KEY_APOSTROPHE'
// undefined: 'KEY_UNKNOWN'
};
(function () {
/* for KEY_0 - KEY_9 */
var _specialKeys = MochiKit.Signal._specialKeys;
for (var i = 48; i <= 57; i++) {
_specialKeys[i] = 'KEY_' + (i - 48);
}
/* for KEY_A - KEY_Z */
for (i = 65; i <= 90; i++) {
_specialKeys[i] = 'KEY_' + String.fromCharCode(i);
}
/* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */
for (i = 96; i <= 105; i++) {
_specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96);
}
/* for KEY_F1 - KEY_F12 */
for (i = 112; i <= 123; i++) {
// no F0
_specialKeys[i] = 'KEY_F' + (i - 112 + 1);
}
})();
/* Internal object to keep track of created signals. */
MochiKit.Signal.Ident = function (ident) {
this.source = ident.source;
this.signal = ident.signal;
this.listener = ident.listener;
this.isDOM = ident.isDOM;
this.objOrFunc = ident.objOrFunc;
this.funcOrStr = ident.funcOrStr;
this.connected = ident.connected;
};
MochiKit.Signal.Ident.prototype = {};
MochiKit.Base.update(MochiKit.Signal, {
__repr__: function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
},
toString: function () {
return this.__repr__();
},
_unloadCache: function () {
var self = MochiKit.Signal;
var observers = self._observers;
for (var i = 0; i < observers.length; i++) {
if (observers[i].signal !== 'onload' && observers[i].signal !== 'onunload') {
self._disconnect(observers[i]);
}
}
},
_listener: function (src, sig, func, obj, isDOM) {
var self = MochiKit.Signal;
var E = self.Event;
if (!isDOM) {
/* We don't want to re-bind already bound methods */
if (typeof(func.im_self) == 'undefined') {
return MochiKit.Base.bindLate(func, obj);
} else {
return func;
}
}
obj = obj || src;
if (typeof(func) == "string") {
if (sig === 'onload' || sig === 'onunload') {
return function (nativeEvent) {
obj[func].apply(obj, [new E(src, nativeEvent)]);
var ident = new MochiKit.Signal.Ident({
source: src, signal: sig, objOrFunc: obj, funcOrStr: func});
MochiKit.Signal._disconnect(ident);
};
} else {
return function (nativeEvent) {
obj[func].apply(obj, [new E(src, nativeEvent)]);
};
}
} else {
if (sig === 'onload' || sig === 'onunload') {
return function (nativeEvent) {
func.apply(obj, [new E(src, nativeEvent)]);
var ident = new MochiKit.Signal.Ident({
source: src, signal: sig, objOrFunc: func});
MochiKit.Signal._disconnect(ident);
};
} else {
return function (nativeEvent) {
func.apply(obj, [new E(src, nativeEvent)]);
};
}
}
},
_browserAlreadyHasMouseEnterAndLeave: function () {
return /MSIE/.test(navigator.userAgent);
},
_browserLacksMouseWheelEvent: function () {
return /Gecko\//.test(navigator.userAgent);
},
_mouseEnterListener: function (src, sig, func, obj) {
var E = MochiKit.Signal.Event;
return function (nativeEvent) {
var e = new E(src, nativeEvent);
try {
e.relatedTarget().nodeName;
} catch (err) {
/* probably hit a permission denied error; possibly one of
* firefox's screwy anonymous DIVs inside an input element.
* Allow this event to propogate up.
*/
return;
}
e.stop();
if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) {
/* We've moved between our node and a child. Ignore. */
return;
}
e.type = function () { return sig; };
if (typeof(func) == "string") {
return obj[func].apply(obj, [e]);
} else {
return func.apply(obj, [e]);
}
};
},
_getDestPair: function (objOrFunc, funcOrStr) {
var obj = null;
var func = null;
if (typeof(funcOrStr) != 'undefined') {
obj = objOrFunc;
func = funcOrStr;
if (typeof(funcOrStr) == 'string') {
if (typeof(objOrFunc[funcOrStr]) != "function") {
throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
}
} else if (typeof(funcOrStr) != 'function') {
throw new Error("'funcOrStr' must be a function or string");
}
} else if (typeof(objOrFunc) != "function") {
throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
} else {
func = objOrFunc;
}
return [obj, func];
},
/** @id MochiKit.Signal.connect */
connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
src = MochiKit.DOM.getElement(src);
var self = MochiKit.Signal;
if (typeof(sig) != 'string') {
throw new Error("'sig' must be a string");
}
var destPair = self._getDestPair(objOrFunc, funcOrStr);
var obj = destPair[0];
var func = destPair[1];
if (typeof(obj) == 'undefined' || obj === null) {
obj = src;
}
var isDOM = !!(src.addEventListener || src.attachEvent);
if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave")
&& !self._browserAlreadyHasMouseEnterAndLeave()) {
var listener = self._mouseEnterListener(src, sig.substr(2), func, obj);
if (sig === "onmouseenter") {
sig = "onmouseover";
} else {
sig = "onmouseout";
}
} else if (isDOM && sig == "onmousewheel" && self._browserLacksMouseWheelEvent()) {
var listener = self._listener(src, sig, func, obj, isDOM);
sig = "onDOMMouseScroll";
} else {
var listener = self._listener(src, sig, func, obj, isDOM);
}
if (src.addEventListener) {
src.addEventListener(sig.substr(2), listener, false);
} else if (src.attachEvent) {
src.attachEvent(sig, listener); // useCapture unsupported
}
var ident = new MochiKit.Signal.Ident({
source: src,
signal: sig,
listener: listener,
isDOM: isDOM,
objOrFunc: objOrFunc,
funcOrStr: funcOrStr,
connected: true
});
self._observers.push(ident);
if (!isDOM && typeof(src.__connect__) == 'function') {
var args = MochiKit.Base.extend([ident], arguments, 1);
src.__connect__.apply(src, args);
}
return ident;
},
_disconnect: function (ident) {
// already disconnected
if (!ident.connected) {
return;
}
ident.connected = false;
var src = ident.source;
var sig = ident.signal;
var listener = ident.listener;
// check isDOM
if (!ident.isDOM) {
if (typeof(src.__disconnect__) == 'function') {
src.__disconnect__(ident, sig, ident.objOrFunc, ident.funcOrStr);
}
return;
}
if (src.removeEventListener) {
src.removeEventListener(sig.substr(2), listener, false);
} else if (src.detachEvent) {
src.detachEvent(sig, listener); // useCapture unsupported
} else {
throw new Error("'src' must be a DOM element");
}
},
/** @id MochiKit.Signal.disconnect */
disconnect: function (ident) {
var self = MochiKit.Signal;
var observers = self._observers;
var m = MochiKit.Base;
if (arguments.length > 1) {
// compatibility API
var src = MochiKit.DOM.getElement(arguments[0]);
var sig = arguments[1];
var obj = arguments[2];
var func = arguments[3];
for (var i = observers.length - 1; i >= 0; i--) {
var o = observers[i];
if (o.source === src && o.signal === sig && o.objOrFunc === obj && o.funcOrStr === func) {
self._disconnect(o);
if (!self._lock) {
observers.splice(i, 1);
} else {
self._dirty = true;
}
return true;
}
}
} else {
var idx = m.findIdentical(observers, ident);
if (idx >= 0) {
self._disconnect(ident);
if (!self._lock) {
observers.splice(idx, 1);
} else {
self._dirty = true;
}
return true;
}
}
return false;
},
/** @id MochiKit.Signal.disconnectAllTo */
disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) {
var self = MochiKit.Signal;
var observers = self._observers;
var disconnect = self._disconnect;
var locked = self._lock;
var dirty = self._dirty;
if (typeof(funcOrStr) === 'undefined') {
funcOrStr = null;
}
for (var i = observers.length - 1; i >= 0; i--) {
var ident = observers[i];
if (ident.objOrFunc === objOrFunc &&
(funcOrStr === null || ident.funcOrStr === funcOrStr)) {
disconnect(ident);
if (locked) {
dirty = true;
} else {
observers.splice(i, 1);
}
}
}
self._dirty = dirty;
},
/** @id MochiKit.Signal.disconnectAll */
disconnectAll: function (src/* optional */, sig) {
src = MochiKit.DOM.getElement(src);
var m = MochiKit.Base;
var signals = m.flattenArguments(m.extend(null, arguments, 1));
var self = MochiKit.Signal;
var disconnect = self._disconnect;
var observers = self._observers;
var i, ident;
var locked = self._lock;
var dirty = self._dirty;
if (signals.length === 0) {
// disconnect all
for (i = observers.length - 1; i >= 0; i--) {
ident = observers[i];
if (ident.source === src) {
disconnect(ident);
if (!locked) {
observers.splice(i, 1);
} else {
dirty = true;
}
}
}
} else {
var sigs = {};
for (i = 0; i < signals.length; i++) {
sigs[signals[i]] = true;
}
for (i = observers.length - 1; i >= 0; i--) {
ident = observers[i];
if (ident.source === src && ident.signal in sigs) {
disconnect(ident);
if (!locked) {
observers.splice(i, 1);
} else {
dirty = true;
}
}
}
}
self._dirty = dirty;
},
/** @id MochiKit.Signal.signal */
signal: function (src, sig) {
var self = MochiKit.Signal;
var observers = self._observers;
src = MochiKit.DOM.getElement(src);
var args = MochiKit.Base.extend(null, arguments, 2);
var errors = [];
self._lock = true;
for (var i = 0; i < observers.length; i++) {
var ident = observers[i];
if (ident.source === src && ident.signal === sig &&
ident.connected) {
try {
ident.listener.apply(src, args);
} catch (e) {
errors.push(e);
}
}
}
self._lock = false;
if (self._dirty) {
self._dirty = false;
for (var i = observers.length - 1; i >= 0; i--) {
if (!observers[i].connected) {
observers.splice(i, 1);
}
}
}
if (errors.length == 1) {
throw errors[0];
} else if (errors.length > 1) {
var e = new Error("Multiple errors thrown in handling 'sig', see errors property");
e.errors = errors;
throw e;
}
}
});
MochiKit.Signal.EXPORT_OK = [];
MochiKit.Signal.EXPORT = [
'connect',
'disconnect',
'signal',
'disconnectAll',
'disconnectAllTo'
];
MochiKit.Signal.__new__ = function (win) {
var m = MochiKit.Base;
this._document = document;
this._window = win;
this._lock = false;
this._dirty = false;
try {
this.connect(window, 'onunload', this._unloadCache);
} catch (e) {
// pass: might not be a browser
}
this.EXPORT_TAGS = {
':common': this.EXPORT,
':all': m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
};
MochiKit.Signal.__new__(this);
//
// XXX: Internet Explorer blows
//
if (MochiKit.__export__) {
connect = MochiKit.Signal.connect;
disconnect = MochiKit.Signal.disconnect;
disconnectAll = MochiKit.Signal.disconnectAll;
signal = MochiKit.Signal.signal;
}
MochiKit.Base._exportSymbols(this, MochiKit.Signal);
-589
View File
@@ -1,589 +0,0 @@
/***
Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
Mochi-ized By Thomas Herve (_firstname_@nimail.org)
See scriptaculous.js for full license.
***/
MochiKit.Base._deps('Sortable', ['Base', 'Iter', 'DOM', 'Position', 'DragAndDrop']);
MochiKit.Sortable.NAME = 'MochiKit.Sortable';
MochiKit.Sortable.VERSION = '1.4.2';
MochiKit.Sortable.__repr__ = function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
};
MochiKit.Sortable.toString = function () {
return this.__repr__();
};
MochiKit.Sortable.EXPORT = [
];
MochiKit.Sortable.EXPORT_OK = [
];
MochiKit.Base.update(MochiKit.Sortable, {
/***
Manage sortables. Mainly use the create function to add a sortable.
***/
sortables: {},
_findRootElement: function (element) {
while (element.tagName.toUpperCase() != "BODY") {
if (element.id && MochiKit.Sortable.sortables[element.id]) {
return element;
}
element = element.parentNode;
}
},
_createElementId: function(element) {
if (element.id == null || element.id == "") {
var d = MochiKit.DOM;
var id;
var count = 1;
while (d.getElement(id = "sortable" + count) != null) {
count += 1;
}
d.setNodeAttribute(element, "id", id);
}
},
/** @id MochiKit.Sortable.options */
options: function (element) {
element = MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(element));
if (!element) {
return;
}
return MochiKit.Sortable.sortables[element.id];
},
/** @id MochiKit.Sortable.destroy */
destroy: function (element){
var s = MochiKit.Sortable.options(element);
var b = MochiKit.Base;
var d = MochiKit.DragAndDrop;
if (s) {
MochiKit.Signal.disconnect(s.startHandle);
MochiKit.Signal.disconnect(s.endHandle);
b.map(function (dr) {
d.Droppables.remove(dr);
}, s.droppables);
b.map(function (dr) {
dr.destroy();
}, s.draggables);
delete MochiKit.Sortable.sortables[s.element.id];
}
},
/** @id MochiKit.Sortable.create */
create: function (element, options) {
element = MochiKit.DOM.getElement(element);
var self = MochiKit.Sortable;
self._createElementId(element);
/** @id MochiKit.Sortable.options */
options = MochiKit.Base.update({
/** @id MochiKit.Sortable.element */
element: element,
/** @id MochiKit.Sortable.tag */
tag: 'li', // assumes li children, override with tag: 'tagname'
/** @id MochiKit.Sortable.dropOnEmpty */
dropOnEmpty: false,
/** @id MochiKit.Sortable.tree */
tree: false,
/** @id MochiKit.Sortable.treeTag */
treeTag: 'ul',
/** @id MochiKit.Sortable.overlap */
overlap: 'vertical', // one of 'vertical', 'horizontal'
/** @id MochiKit.Sortable.constraint */
constraint: 'vertical', // one of 'vertical', 'horizontal', false
// also takes array of elements (or ids); or false
/** @id MochiKit.Sortable.containment */
containment: [element],
/** @id MochiKit.Sortable.handle */
handle: false, // or a CSS class
/** @id MochiKit.Sortable.only */
only: false,
/** @id MochiKit.Sortable.hoverclass */
hoverclass: null,
/** @id MochiKit.Sortable.ghosting */
ghosting: false,
/** @id MochiKit.Sortable.scroll */
scroll: false,
/** @id MochiKit.Sortable.scrollSensitivity */
scrollSensitivity: 20,
/** @id MochiKit.Sortable.scrollSpeed */
scrollSpeed: 15,
/** @id MochiKit.Sortable.format */
format: /^[^_]*_(.*)$/,
/** @id MochiKit.Sortable.onChange */
onChange: MochiKit.Base.noop,
/** @id MochiKit.Sortable.onUpdate */
onUpdate: MochiKit.Base.noop,
/** @id MochiKit.Sortable.accept */
accept: null
}, options);
// clear any old sortable with same element
self.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
ghosting: options.ghosting,
scroll: options.scroll,
scrollSensitivity: options.scrollSensitivity,
scrollSpeed: options.scrollSpeed,
constraint: options.constraint,
handle: options.handle
};
if (options.starteffect) {
options_for_draggable.starteffect = options.starteffect;
}
if (options.reverteffect) {
options_for_draggable.reverteffect = options.reverteffect;
} else if (options.ghosting) {
options_for_draggable.reverteffect = function (innerelement) {
innerelement.style.top = 0;
innerelement.style.left = 0;
};
}
if (options.endeffect) {
options_for_draggable.endeffect = options.endeffect;
}
if (options.zindex) {
options_for_draggable.zindex = options.zindex;
}
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass,
onhover: self.onHover,
tree: options.tree,
accept: options.accept
}
var options_for_tree = {
onhover: self.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass,
accept: options.accept
}
// fix for gecko engine
MochiKit.DOM.removeEmptyTextNodes(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if (options.dropOnEmpty || options.tree) {
new MochiKit.DragAndDrop.Droppable(element, options_for_tree);
options.droppables.push(element);
}
MochiKit.Base.map(function (e) {
// handles are per-draggable
var handle = options.handle ?
MochiKit.DOM.getFirstElementByTagAndClassName(null,
options.handle, e) : e;
options.draggables.push(
new MochiKit.DragAndDrop.Draggable(e,
MochiKit.Base.update(options_for_draggable,
{handle: handle})));
new MochiKit.DragAndDrop.Droppable(e, options_for_droppable);
if (options.tree) {
e.treeNode = element;
}
options.droppables.push(e);
}, (self.findElements(element, options) || []));
if (options.tree) {
MochiKit.Base.map(function (e) {
new MochiKit.DragAndDrop.Droppable(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
}, (self.findTreeElements(element, options) || []));
}
// keep reference
self.sortables[element.id] = options;
options.lastValue = self.serialize(element);
options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start',
MochiKit.Base.partial(self.onStart, element));
options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end',
MochiKit.Base.partial(self.onEnd, element));
},
/** @id MochiKit.Sortable.onStart */
onStart: function (element, draggable) {
var self = MochiKit.Sortable;
var options = self.options(element);
options.lastValue = self.serialize(options.element);
},
/** @id MochiKit.Sortable.onEnd */
onEnd: function (element, draggable) {
var self = MochiKit.Sortable;
self.unmark();
var options = self.options(element);
if (options.lastValue != self.serialize(options.element)) {
options.onUpdate(options.element);
}
},
// return all suitable-for-sortable elements in a guaranteed order
/** @id MochiKit.Sortable.findElements */
findElements: function (element, options) {
return MochiKit.Sortable.findChildren(element, options.only, options.tree, options.tag);
},
/** @id MochiKit.Sortable.findTreeElements */
findTreeElements: function (element, options) {
return MochiKit.Sortable.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
/** @id MochiKit.Sortable.findChildren */
findChildren: function (element, only, recursive, tagName) {
if (!element.hasChildNodes()) {
return null;
}
tagName = tagName.toUpperCase();
if (only) {
only = MochiKit.Base.flattenArray([only]);
}
var elements = [];
MochiKit.Base.map(function (e) {
if (e.tagName &&
e.tagName.toUpperCase() == tagName &&
(!only ||
MochiKit.Iter.some(only, function (c) {
return MochiKit.DOM.hasElementClass(e, c);
}))) {
elements.push(e);
}
if (recursive) {
var grandchildren = MochiKit.Sortable.findChildren(e, only, recursive, tagName);
if (grandchildren && grandchildren.length > 0) {
elements = elements.concat(grandchildren);
}
}
}, element.childNodes);
return elements;
},
/** @id MochiKit.Sortable.onHover */
onHover: function (element, dropon, overlap) {
if (MochiKit.DOM.isChildNode(dropon, element)) {
return;
}
var self = MochiKit.Sortable;
if (overlap > .33 && overlap < .66 && self.options(dropon).tree) {
return;
} else if (overlap > 0.5) {
self.mark(dropon, 'before');
if (dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = 'hidden'; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if (dropon.parentNode != oldParentNode) {
self.options(oldParentNode).onChange(element);
}
self.options(dropon.parentNode).onChange(element);
}
} else {
self.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if (nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = 'hidden'; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if (dropon.parentNode != oldParentNode) {
self.options(oldParentNode).onChange(element);
}
self.options(dropon.parentNode).onChange(element);
}
}
},
_offsetSize: function (element, type) {
if (type == 'vertical' || type == 'height') {
return element.offsetHeight;
} else {
return element.offsetWidth;
}
},
/** @id MochiKit.Sortable.onEmptyHover */
onEmptyHover: function (element, dropon, overlap) {
var oldParentNode = element.parentNode;
var self = MochiKit.Sortable;
var droponOptions = self.options(dropon);
if (!MochiKit.DOM.isChildNode(dropon, element)) {
var index;
var children = self.findElements(dropon, {tag: droponOptions.tag,
only: droponOptions.only});
var child = null;
if (children) {
var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) {
offset -= self._offsetSize(children[index], droponOptions.overlap);
} else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
self.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
/** @id MochiKit.Sortable.unmark */
unmark: function () {
var m = MochiKit.Sortable._marker;
if (m) {
MochiKit.Style.hideElement(m);
}
},
/** @id MochiKit.Sortable.mark */
mark: function (dropon, position) {
// mark on ghosting only
var d = MochiKit.DOM;
var self = MochiKit.Sortable;
var sortable = self.options(dropon.parentNode);
if (sortable && !sortable.ghosting) {
return;
}
if (!self._marker) {
self._marker = d.getElement('dropmarker') ||
document.createElement('DIV');
MochiKit.Style.hideElement(self._marker);
d.addElementClass(self._marker, 'dropmarker');
self._marker.style.position = 'absolute';
document.getElementsByTagName('body').item(0).appendChild(self._marker);
}
var offsets = MochiKit.Position.cumulativeOffset(dropon);
self._marker.style.left = offsets.x + 'px';
self._marker.style.top = offsets.y + 'px';
if (position == 'after') {
if (sortable.overlap == 'horizontal') {
self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px';
} else {
self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px';
}
}
MochiKit.Style.showElement(self._marker);
},
_tree: function (element, options, parent) {
var self = MochiKit.Sortable;
var children = self.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) {
continue;
}
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: self._findChildrenElement(children[i], options.treeTag.toUpperCase())
}
/* Get the element containing the children and recurse over it */
if (child.container) {
self._tree(child.container, options, child)
}
parent.children.push (child);
}
return parent;
},
/* Finds the first element of the given tag type within a parent element.
Used for finding the first LI[ST] within a L[IST]I[TEM].*/
_findChildrenElement: function (element, containerTag) {
if (element && element.hasChildNodes) {
containerTag = containerTag.toUpperCase();
for (var i = 0; i < element.childNodes.length; ++i) {
if (element.childNodes[i].tagName.toUpperCase() == containerTag) {
return element.childNodes[i];
}
}
}
return null;
},
/** @id MochiKit.Sortable.tree */
tree: function (element, options) {
element = MochiKit.DOM.getElement(element);
var sortableOptions = MochiKit.Sortable.options(element);
options = MochiKit.Base.update({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, options || {});
var root = {
id: null,
parent: null,
children: new Array,
container: element,
position: 0
}
return MochiKit.Sortable._tree(element, options, root);
},
/**
* Specifies the sequence for the Sortable.
* @param {Node} element Element to use as the Sortable.
* @param {Object} newSequence New sequence to use.
* @param {Object} options Options to use fro the Sortable.
*/
setSequence: function (element, newSequence, options) {
var self = MochiKit.Sortable;
var b = MochiKit.Base;
element = MochiKit.DOM.getElement(element);
options = b.update(self.options(element), options || {});
var nodeMap = {};
b.map(function (n) {
var m = n.id.match(options.format);
if (m) {
nodeMap[m[1]] = [n, n.parentNode];
}
n.parentNode.removeChild(n);
}, self.findElements(element, options));
b.map(function (ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
}, newSequence);
},
/* Construct a [i] index for a particular node */
_constructIndex: function (node) {
var index = '';
do {
if (node.id) {
index = '[' + node.position + ']' + index;
}
} while ((node = node.parent) != null);
return index;
},
/** @id MochiKit.Sortable.sequence */
sequence: function (element, options) {
element = MochiKit.DOM.getElement(element);
var self = MochiKit.Sortable;
var options = MochiKit.Base.update(self.options(element), options || {});
return MochiKit.Base.map(function (item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
}, MochiKit.DOM.getElement(self.findElements(element, options) || []));
},
/**
* Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest.
* These options override the Sortable options for the serialization only.
* @param {Node} element Element to serialize.
* @param {Object} options Serialization options.
*/
serialize: function (element, options) {
element = MochiKit.DOM.getElement(element);
var self = MochiKit.Sortable;
options = MochiKit.Base.update(self.options(element), options || {});
var name = encodeURIComponent(options.name || element.id);
if (options.tree) {
return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) {
return [name + self._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}, self.tree(element, options).children)).join('&');
} else {
return MochiKit.Base.map(function (item) {
return name + "[]=" + encodeURIComponent(item);
}, self.sequence(element, options)).join('&');
}
}
});
// trunk compatibility
MochiKit.Sortable.Sortable = MochiKit.Sortable;
MochiKit.Sortable.__new__ = function () {
MochiKit.Base.nameFunctions(this);
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
};
};
MochiKit.Sortable.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Sortable);
-594
View File
@@ -1,594 +0,0 @@
/***
MochiKit.Style 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005-2006 Bob Ippolito, Beau Hartshorne. All rights Reserved.
***/
MochiKit.Base._deps('Style', ['Base', 'DOM']);
MochiKit.Style.NAME = 'MochiKit.Style';
MochiKit.Style.VERSION = '1.4.2';
MochiKit.Style.__repr__ = function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
};
MochiKit.Style.toString = function () {
return this.__repr__();
};
MochiKit.Style.EXPORT_OK = [];
MochiKit.Style.EXPORT = [
'setStyle',
'setOpacity',
'getStyle',
'getElementDimensions',
'elementDimensions', // deprecated
'setElementDimensions',
'getElementPosition',
'elementPosition', // deprecated
'setElementPosition',
"makePositioned",
"undoPositioned",
"makeClipping",
"undoClipping",
'setDisplayForElement',
'hideElement',
'showElement',
'getViewportDimensions',
'getViewportPosition',
'Dimensions',
'Coordinates'
];
/*
Dimensions
*/
/** @id MochiKit.Style.Dimensions */
MochiKit.Style.Dimensions = function (w, h) {
this.w = w;
this.h = h;
};
MochiKit.Style.Dimensions.prototype.__repr__ = function () {
var repr = MochiKit.Base.repr;
return '{w: ' + repr(this.w) + ', h: ' + repr(this.h) + '}';
};
MochiKit.Style.Dimensions.prototype.toString = function () {
return this.__repr__();
};
/*
Coordinates
*/
/** @id MochiKit.Style.Coordinates */
MochiKit.Style.Coordinates = function (x, y) {
this.x = x;
this.y = y;
};
MochiKit.Style.Coordinates.prototype.__repr__ = function () {
var repr = MochiKit.Base.repr;
return '{x: ' + repr(this.x) + ', y: ' + repr(this.y) + '}';
};
MochiKit.Style.Coordinates.prototype.toString = function () {
return this.__repr__();
};
MochiKit.Base.update(MochiKit.Style, {
/** @id MochiKit.Style.getStyle */
getStyle: function (elem, cssProperty) {
var dom = MochiKit.DOM;
var d = dom._document;
elem = dom.getElement(elem);
cssProperty = MochiKit.Base.camelize(cssProperty);
if (!elem || elem == d) {
return undefined;
}
if (cssProperty == 'opacity' && typeof(elem.filters) != 'undefined') {
var opacity = (MochiKit.Style.getStyle(elem, 'filter') || '').match(/alpha\(opacity=(.*)\)/);
if (opacity && opacity[1]) {
return parseFloat(opacity[1]) / 100;
}
return 1.0;
}
if (cssProperty == 'float' || cssProperty == 'cssFloat' || cssProperty == 'styleFloat') {
if (elem.style["float"]) {
return elem.style["float"];
} else if (elem.style.cssFloat) {
return elem.style.cssFloat;
} else if (elem.style.styleFloat) {
return elem.style.styleFloat;
} else {
return "none";
}
}
var value = elem.style ? elem.style[cssProperty] : null;
if (!value) {
if (d.defaultView && d.defaultView.getComputedStyle) {
var css = d.defaultView.getComputedStyle(elem, null);
cssProperty = cssProperty.replace(/([A-Z])/g, '-$1'
).toLowerCase(); // from dojo.style.toSelectorCase
value = css ? css.getPropertyValue(cssProperty) : null;
} else if (elem.currentStyle) {
value = elem.currentStyle[cssProperty];
if (/^\d/.test(value) && !/px$/.test(value) && cssProperty != 'fontWeight') {
/* Convert to px using an hack from Dean Edwards */
var left = elem.style.left;
var rsLeft = elem.runtimeStyle.left;
elem.runtimeStyle.left = elem.currentStyle.left;
elem.style.left = value || 0;
value = elem.style.pixelLeft + "px";
elem.style.left = left;
elem.runtimeStyle.left = rsLeft;
}
}
}
if (cssProperty == 'opacity') {
value = parseFloat(value);
}
if (/Opera/.test(navigator.userAgent) && (MochiKit.Base.findValue(['left', 'top', 'right', 'bottom'], cssProperty) != -1)) {
if (MochiKit.Style.getStyle(elem, 'position') == 'static') {
value = 'auto';
}
}
return value == 'auto' ? null : value;
},
/** @id MochiKit.Style.setStyle */
setStyle: function (elem, style) {
elem = MochiKit.DOM.getElement(elem);
for (var name in style) {
switch (name) {
case 'opacity':
MochiKit.Style.setOpacity(elem, style[name]);
break;
case 'float':
case 'cssFloat':
case 'styleFloat':
if (typeof(elem.style["float"]) != "undefined") {
elem.style["float"] = style[name];
} else if (typeof(elem.style.cssFloat) != "undefined") {
elem.style.cssFloat = style[name];
} else {
elem.style.styleFloat = style[name];
}
break;
default:
elem.style[MochiKit.Base.camelize(name)] = style[name];
}
}
},
/** @id MochiKit.Style.setOpacity */
setOpacity: function (elem, o) {
elem = MochiKit.DOM.getElement(elem);
var self = MochiKit.Style;
if (o == 1) {
var toSet = /Gecko/.test(navigator.userAgent) && !(/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent));
elem.style["opacity"] = toSet ? 0.999999 : 1.0;
if (/MSIE/.test(navigator.userAgent)) {
elem.style['filter'] =
self.getStyle(elem, 'filter').replace(/alpha\([^\)]*\)/gi, '');
}
} else {
if (o < 0.00001) {
o = 0;
}
elem.style["opacity"] = o;
if (/MSIE/.test(navigator.userAgent)) {
elem.style['filter'] =
self.getStyle(elem, 'filter').replace(/alpha\([^\)]*\)/gi, '') + 'alpha(opacity=' + o * 100 + ')';
}
}
},
/*
getElementPosition is adapted from YAHOO.util.Dom.getXY v0.9.0.
Copyright: Copyright (c) 2006, Yahoo! Inc. All rights reserved.
License: BSD, http://developer.yahoo.net/yui/license.txt
*/
/** @id MochiKit.Style.getElementPosition */
getElementPosition: function (elem, /* optional */relativeTo) {
var self = MochiKit.Style;
var dom = MochiKit.DOM;
elem = dom.getElement(elem);
if (!elem ||
(!(elem.x && elem.y) &&
(!elem.parentNode === null ||
self.getStyle(elem, 'display') == 'none'))) {
return undefined;
}
var c = new self.Coordinates(0, 0);
var box = null;
var parent = null;
var d = MochiKit.DOM._document;
var de = d.documentElement;
var b = d.body;
if (!elem.parentNode && elem.x && elem.y) {
/* it's just a MochiKit.Style.Coordinates object */
c.x += elem.x || 0;
c.y += elem.y || 0;
} else if (elem.getBoundingClientRect) { // IE shortcut
/*
The IE shortcut can be off by two. We fix it. See:
http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
This is similar to the method used in
MochiKit.Signal.Event.mouse().
*/
box = elem.getBoundingClientRect();
c.x += box.left +
(de.scrollLeft || b.scrollLeft) -
(de.clientLeft || 0);
c.y += box.top +
(de.scrollTop || b.scrollTop) -
(de.clientTop || 0);
} else if (elem.offsetParent) {
c.x += elem.offsetLeft;
c.y += elem.offsetTop;
parent = elem.offsetParent;
if (parent != elem) {
while (parent) {
c.x += parseInt(parent.style.borderLeftWidth) || 0;
c.y += parseInt(parent.style.borderTopWidth) || 0;
c.x += parent.offsetLeft;
c.y += parent.offsetTop;
parent = parent.offsetParent;
}
}
/*
Opera < 9 and old Safari (absolute) incorrectly account for
body offsetTop and offsetLeft.
*/
var ua = navigator.userAgent.toLowerCase();
if ((typeof(opera) != 'undefined' &&
parseFloat(opera.version()) < 9) ||
(ua.indexOf('AppleWebKit') != -1 &&
self.getStyle(elem, 'position') == 'absolute')) {
c.x -= b.offsetLeft;
c.y -= b.offsetTop;
}
// Adjust position for strange Opera scroll bug
if (elem.parentNode) {
parent = elem.parentNode;
} else {
parent = null;
}
while (parent) {
var tagName = parent.tagName.toUpperCase();
if (tagName === 'BODY' || tagName === 'HTML') {
break;
}
var disp = self.getStyle(parent, 'display');
// Handle strange Opera bug for some display
if (disp.search(/^inline|table-row.*$/i)) {
c.x -= parent.scrollLeft;
c.y -= parent.scrollTop;
}
if (parent.parentNode) {
parent = parent.parentNode;
} else {
parent = null;
}
}
}
if (typeof(relativeTo) != 'undefined') {
relativeTo = arguments.callee(relativeTo);
if (relativeTo) {
c.x -= (relativeTo.x || 0);
c.y -= (relativeTo.y || 0);
}
}
return c;
},
/** @id MochiKit.Style.setElementPosition */
setElementPosition: function (elem, newPos/* optional */, units) {
elem = MochiKit.DOM.getElement(elem);
if (typeof(units) == 'undefined') {
units = 'px';
}
var newStyle = {};
var isUndefNull = MochiKit.Base.isUndefinedOrNull;
if (!isUndefNull(newPos.x)) {
newStyle['left'] = newPos.x + units;
}
if (!isUndefNull(newPos.y)) {
newStyle['top'] = newPos.y + units;
}
MochiKit.DOM.updateNodeAttributes(elem, {'style': newStyle});
},
/** @id MochiKit.Style.makePositioned */
makePositioned: function (element) {
element = MochiKit.DOM.getElement(element);
var pos = MochiKit.Style.getStyle(element, 'position');
if (pos == 'static' || !pos) {
element.style.position = 'relative';
// Opera returns the offset relative to the positioning context,
// when an element is position relative but top and left have
// not been defined
if (/Opera/.test(navigator.userAgent)) {
element.style.top = 0;
element.style.left = 0;
}
}
},
/** @id MochiKit.Style.undoPositioned */
undoPositioned: function (element) {
element = MochiKit.DOM.getElement(element);
if (element.style.position == 'relative') {
element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = '';
}
},
/** @id MochiKit.Style.makeClipping */
makeClipping: function (element) {
element = MochiKit.DOM.getElement(element);
var s = element.style;
var oldOverflow = { 'overflow': s.overflow,
'overflow-x': s.overflowX,
'overflow-y': s.overflowY };
if ((MochiKit.Style.getStyle(element, 'overflow') || 'visible') != 'hidden') {
element.style.overflow = 'hidden';
element.style.overflowX = 'hidden';
element.style.overflowY = 'hidden';
}
return oldOverflow;
},
/** @id MochiKit.Style.undoClipping */
undoClipping: function (element, overflow) {
element = MochiKit.DOM.getElement(element);
if (typeof(overflow) == 'string') {
element.style.overflow = overflow;
} else if (overflow != null) {
element.style.overflow = overflow['overflow'];
element.style.overflowX = overflow['overflow-x'];
element.style.overflowY = overflow['overflow-y'];
}
},
/** @id MochiKit.Style.getElementDimensions */
getElementDimensions: function (elem, contentSize/*optional*/) {
var self = MochiKit.Style;
var dom = MochiKit.DOM;
if (typeof(elem.w) == 'number' || typeof(elem.h) == 'number') {
return new self.Dimensions(elem.w || 0, elem.h || 0);
}
elem = dom.getElement(elem);
if (!elem) {
return undefined;
}
var disp = self.getStyle(elem, 'display');
// display can be empty/undefined on WebKit/KHTML
if (disp == 'none' || disp == '' || typeof(disp) == 'undefined') {
var s = elem.style;
var originalVisibility = s.visibility;
var originalPosition = s.position;
var originalDisplay = s.display;
s.visibility = 'hidden';
s.position = 'absolute';
s.display = self._getDefaultDisplay(elem);
var originalWidth = elem.offsetWidth;
var originalHeight = elem.offsetHeight;
s.display = originalDisplay;
s.position = originalPosition;
s.visibility = originalVisibility;
} else {
originalWidth = elem.offsetWidth || 0;
originalHeight = elem.offsetHeight || 0;
}
if (contentSize) {
var tableCell = 'colSpan' in elem && 'rowSpan' in elem;
var collapse = (tableCell && elem.parentNode && self.getStyle(
elem.parentNode, 'borderCollapse') == 'collapse')
if (collapse) {
if (/MSIE/.test(navigator.userAgent)) {
var borderLeftQuota = elem.previousSibling? 0.5 : 1;
var borderRightQuota = elem.nextSibling? 0.5 : 1;
}
else {
var borderLeftQuota = 0.5;
var borderRightQuota = 0.5;
}
} else {
var borderLeftQuota = 1;
var borderRightQuota = 1;
}
originalWidth -= Math.round(
(parseFloat(self.getStyle(elem, 'paddingLeft')) || 0)
+ (parseFloat(self.getStyle(elem, 'paddingRight')) || 0)
+ borderLeftQuota *
(parseFloat(self.getStyle(elem, 'borderLeftWidth')) || 0)
+ borderRightQuota *
(parseFloat(self.getStyle(elem, 'borderRightWidth')) || 0)
);
if (tableCell) {
if (/Gecko|Opera/.test(navigator.userAgent)
&& !/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)) {
var borderHeightQuota = 0;
} else if (/MSIE/.test(navigator.userAgent)) {
var borderHeightQuota = 1;
} else {
var borderHeightQuota = collapse? 0.5 : 1;
}
} else {
var borderHeightQuota = 1;
}
originalHeight -= Math.round(
(parseFloat(self.getStyle(elem, 'paddingTop')) || 0)
+ (parseFloat(self.getStyle(elem, 'paddingBottom')) || 0)
+ borderHeightQuota * (
(parseFloat(self.getStyle(elem, 'borderTopWidth')) || 0)
+ (parseFloat(self.getStyle(elem, 'borderBottomWidth')) || 0))
);
}
return new self.Dimensions(originalWidth, originalHeight);
},
/** @id MochiKit.Style.setElementDimensions */
setElementDimensions: function (elem, newSize/* optional */, units) {
elem = MochiKit.DOM.getElement(elem);
if (typeof(units) == 'undefined') {
units = 'px';
}
var newStyle = {};
var isUndefNull = MochiKit.Base.isUndefinedOrNull;
if (!isUndefNull(newSize.w)) {
newStyle['width'] = newSize.w + units;
}
if (!isUndefNull(newSize.h)) {
newStyle['height'] = newSize.h + units;
}
MochiKit.DOM.updateNodeAttributes(elem, {'style': newStyle});
},
_getDefaultDisplay: function (elem) {
var self = MochiKit.Style;
var dom = MochiKit.DOM;
elem = dom.getElement(elem);
if (!elem) {
return undefined;
}
var tagName = elem.tagName.toUpperCase();
return self._defaultDisplay[tagName] || 'block';
},
/** @id MochiKit.Style.setDisplayForElement */
setDisplayForElement: function (display, element/*, ...*/) {
var elements = MochiKit.Base.extend(null, arguments, 1);
var getElement = MochiKit.DOM.getElement;
for (var i = 0; i < elements.length; i++) {
element = getElement(elements[i]);
if (element) {
element.style.display = display;
}
}
},
/** @id MochiKit.Style.getViewportDimensions */
getViewportDimensions: function () {
var d = new MochiKit.Style.Dimensions();
var w = MochiKit.DOM._window;
var b = MochiKit.DOM._document.body;
if (w.innerWidth) {
d.w = w.innerWidth;
d.h = w.innerHeight;
} else if (b && b.parentElement && b.parentElement.clientWidth) {
d.w = b.parentElement.clientWidth;
d.h = b.parentElement.clientHeight;
} else if (b && b.clientWidth) {
d.w = b.clientWidth;
d.h = b.clientHeight;
}
return d;
},
/** @id MochiKit.Style.getViewportPosition */
getViewportPosition: function () {
var c = new MochiKit.Style.Coordinates(0, 0);
var d = MochiKit.DOM._document;
var de = d.documentElement;
var db = d.body;
if (de && (de.scrollTop || de.scrollLeft)) {
c.x = de.scrollLeft;
c.y = de.scrollTop;
} else if (db) {
c.x = db.scrollLeft;
c.y = db.scrollTop;
}
return c;
},
__new__: function () {
var m = MochiKit.Base;
var inlines = ['A','ABBR','ACRONYM','B','BASEFONT','BDO','BIG','BR',
'CITE','CODE','DFN','EM','FONT','I','IMG','KBD','LABEL',
'Q','S','SAMP','SMALL','SPAN','STRIKE','STRONG','SUB',
'SUP','TEXTAREA','TT','U','VAR'];
this._defaultDisplay = { 'TABLE': 'table',
'THEAD': 'table-header-group',
'TBODY': 'table-row-group',
'TFOOT': 'table-footer-group',
'COLGROUP': 'table-column-group',
'COL': 'table-column',
'TR': 'table-row',
'TD': 'table-cell',
'TH': 'table-cell',
'CAPTION': 'table-caption',
'LI': 'list-item',
'INPUT': 'inline-block',
'SELECT': 'inline-block' };
// CSS 'display' support in IE6/7 is just broken...
if (/MSIE/.test(navigator.userAgent)) {
for (var k in this._defaultDisplay) {
var v = this._defaultDisplay[k];
if (v.indexOf('table') == 0) {
this._defaultDisplay[k] = 'block';
}
}
}
for (var i = 0; i < inlines.length; i++) {
this._defaultDisplay[inlines[i]] = 'inline';
}
this.elementPosition = this.getElementPosition;
this.elementDimensions = this.getElementDimensions;
this.hideElement = m.partial(this.setDisplayForElement, 'none');
// TODO: showElement could be improved by using getDefaultDisplay.
this.showElement = m.partial(this.setDisplayForElement, 'block');
this.EXPORT_TAGS = {
':common': this.EXPORT,
':all': m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
}
});
MochiKit.Style.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Style);
-162
View File
@@ -1,162 +0,0 @@
/***
MochiKit.Test 1.4.2
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base._deps('Test', ['Base']);
MochiKit.Test.NAME = "MochiKit.Test";
MochiKit.Test.VERSION = "1.4.2";
MochiKit.Test.__repr__ = function () {
return "[" + this.NAME + " " + this.VERSION + "]";
};
MochiKit.Test.toString = function () {
return this.__repr__();
};
MochiKit.Test.EXPORT = ["runTests"];
MochiKit.Test.EXPORT_OK = [];
MochiKit.Test.runTests = function (obj) {
if (typeof(obj) == "string") {
obj = JSAN.use(obj);
}
var suite = new MochiKit.Test.Suite();
suite.run(obj);
};
MochiKit.Test.Suite = function () {
this.testIndex = 0;
MochiKit.Base.bindMethods(this);
};
MochiKit.Test.Suite.prototype = {
run: function (obj) {
try {
obj(this);
} catch (e) {
this.traceback(e);
}
},
traceback: function (e) {
var items = MochiKit.Iter.sorted(MochiKit.Base.items(e));
print("not ok " + this.testIndex + " - Error thrown");
for (var i = 0; i < items.length; i++) {
var kv = items[i];
if (kv[0] == "stack") {
kv[1] = kv[1].split(/\n/)[0];
}
this.print("# " + kv.join(": "));
}
},
print: function (s) {
print(s);
},
is: function (got, expected, /* optional */message) {
var res = 1;
var msg = null;
try {
res = MochiKit.Base.compare(got, expected);
} catch (e) {
msg = "Can not compare " + typeof(got) + ":" + typeof(expected);
}
if (res) {
msg = "Expected value did not compare equal";
}
if (!res) {
return this.testResult(true, message);
}
return this.testResult(false, message,
[[msg], ["got:", got], ["expected:", expected]]);
},
testResult: function (pass, msg, failures) {
this.testIndex += 1;
if (pass) {
this.print("ok " + this.testIndex + " - " + msg);
return;
}
this.print("not ok " + this.testIndex + " - " + msg);
if (failures) {
for (var i = 0; i < failures.length; i++) {
this.print("# " + failures[i].join(" "));
}
}
},
isDeeply: function (got, expected, /* optional */message) {
var m = MochiKit.Base;
var res = 1;
try {
res = m.compare(got, expected);
} catch (e) {
// pass
}
if (res === 0) {
return this.ok(true, message);
}
var gk = m.keys(got);
var ek = m.keys(expected);
gk.sort();
ek.sort();
if (m.compare(gk, ek)) {
// differing keys
var cmp = {};
var i;
for (i = 0; i < gk.length; i++) {
cmp[gk[i]] = "got";
}
for (i = 0; i < ek.length; i++) {
if (ek[i] in cmp) {
delete cmp[ek[i]];
} else {
cmp[ek[i]] = "expected";
}
}
var diffkeys = m.keys(cmp);
diffkeys.sort();
var gotkeys = [];
var expkeys = [];
while (diffkeys.length) {
var k = diffkeys.shift();
if (k in Object.prototype) {
continue;
}
(cmp[k] == "got" ? gotkeys : expkeys).push(k);
}
}
return this.testResult((!res), msg,
(msg ? [["got:", got], ["expected:", expected]] : undefined)
);
},
ok: function (res, message) {
return this.testResult(res, message);
}
};
MochiKit.Test.__new__ = function () {
var m = MochiKit.Base;
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": m.concat(this.EXPORT, this.EXPORT_OK)
};
m.nameFunctions(this);
};
MochiKit.Test.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Test);
File diff suppressed because it is too large Load Diff
-18
View File
@@ -1,18 +0,0 @@
dojo.kwCompoundRequire({
"common": [
"MochiKit.Base",
"MochiKit.Iter",
"MochiKit.Logging",
"MochiKit.DateTime",
"MochiKit.Format",
"MochiKit.Async",
"MochiKit.DOM",
"MochiKit.Style",
"MochiKit.LoggingPane",
"MochiKit.Color",
"MochiKit.Signal",
"MochiKit.Position",
"MochiKit.Visual"
]
});
dojo.provide("MochiKit.*");
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

+130 -22
View File
@@ -1,31 +1,139 @@
tr.flowerlist:hover td {
background:#eee;
cursor: pointer;
:root {
--bg: #0b1010;
--panel: #131b1a;
--panel-2: #182321;
--line: #293735;
--text: #f3f4ec;
--muted: #98a6a1;
--accent: #e5ff78;
--accent-ink: #172000;
--danger: #ff8e85;
--radius: 18px;
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
color: var(--text);
background: var(--bg);
font-synthesis: none;
}
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; background: radial-gradient(circle at 12% 0%, #172522 0, transparent 35rem), var(--bg); }
button, input { font: inherit; }
a { color: inherit; }
.flowerlist {
width: 100%;
}
.site-header { min-height: 76px; padding: 16px clamp(20px, 5vw, 72px); display: flex; align-items: center; justify-content: space-between; gap: 24px; border-bottom: 1px solid var(--line); }
.brand { display: inline-flex; align-items: center; gap: 10px; font: 600 1.35rem Georgia, serif; text-decoration: none; }
.brand-logo { display: block; width: 34px; height: 34px; border-radius: 9px; }
.nav-actions { display: flex; align-items: center; gap: 8px; }
.nav-actions form { margin: 0; }
.text-link { color: var(--muted); text-decoration: none; font-size: .92rem; }
.text-link:hover { color: var(--text); }
tr.flowerlist a {
color: inherit; /* blue colors for links too */
text-decoration: inherit; /* no underline */
}
.page-shell { width: min(1040px, calc(100% - 40px)); margin: 0 auto; padding: clamp(44px, 7vw, 80px) 0; min-height: calc(100vh - 150px); }
footer { padding: 24px; color: #65716e; text-align: center; font-size: .8rem; }
h1, h2, p { margin-top: 0; }
h1 { margin-bottom: 18px; font: 600 clamp(1.125rem, 2.5vw, 2rem)/1.1 Inter, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; letter-spacing: -.025em; }
h2 { font: 600 1.55rem Georgia, serif; }
.eyebrow { margin-bottom: 10px; color: var(--muted); font-size: .82rem; font-weight: 600; letter-spacing: .01em; }
.muted, .intro p { color: var(--muted); line-height: 1.7; }
td.white {
color: #eee
}
.auth-layout { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(320px, 430px); align-items: center; gap: clamp(40px, 8vw, 96px); }
.intro { max-width: 620px; }
.intro p:not(.eyebrow) { max-width: 520px; font-size: 1.05rem; }
.card { background: linear-gradient(145deg, rgba(27, 39, 37, .94), rgba(16, 23, 22, .98)); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 26px 80px rgba(0, 0, 0, .25); }
.form-card { display: flex; flex-direction: column; padding: clamp(28px, 5vw, 44px); }
.form-card label { margin: 18px 0 8px; color: #c8cfcb; font-size: .82rem; font-weight: 600; }
.form-card label span { color: var(--muted); font-weight: 400; }
input { width: 100%; padding: 14px 15px; color: var(--text); background: #0d1413; border: 1px solid #344340; border-radius: 10px; outline: none; }
input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(229, 255, 120, .1); }
input::placeholder { color: #61706c; }
.form-card .button { margin-top: 26px; }
.field-help { margin: 8px 0 0; color: var(--muted); font-size: .76rem; line-height: 1.5; }
.field-error { padding: 10px 12px; color: #ffd0cc; background: #3b1d1b; border-radius: 8px; font-size: .85rem; }
td.left {
text-align: left;
}
.button, .icon-button { border: 0; cursor: pointer; }
.button { display: inline-flex; justify-content: center; align-items: center; min-height: 46px; padding: 0 19px; border-radius: 10px; font-weight: 700; }
.button.primary { color: var(--accent-ink); background: var(--accent); }
.button.primary:hover { background: #efffa9; transform: translateY(-1px); }
.button.ghost { color: var(--text); background: transparent; border: 1px solid var(--line); }
.button.ghost:hover { background: var(--panel-2); }
.button.compact { min-height: 38px; padding: 0 14px; font-size: .82rem; }
.button.danger { color: #2c0906; background: var(--danger); }
td.right {
text-align: right;
}
.notices { position: fixed; z-index: 10; top: 88px; right: 24px; display: grid; gap: 8px; }
.notice { max-width: 380px; padding: 12px 16px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 12px 35px #0008; font-size: .86rem; }
.notice-success { border-color: #53642f; }
.notice-error { border-color: #713b36; }
td.blue {
color: #208dd6;
.vault-header { display: flex; justify-content: space-between; align-items: end; gap: 32px; margin-bottom: 38px; }
.vault-header h1 { margin: 0; font-size: clamp(1.1rem, 2.5vw, 1.75rem); }
.search-field { width: min(100%, 360px); }
.entry-list { display: grid; gap: 10px; }
.entry-row { margin: 0; }
.entry-button { width: 100%; display: grid; grid-template-columns: 44px minmax(0, 1fr) auto 20px; align-items: center; gap: 15px; padding: 17px 18px; color: var(--text); text-align: left; background: rgba(19, 27, 26, .78); border: 1px solid var(--line); border-radius: 13px; cursor: pointer; }
.entry-button:hover { background: var(--panel-2); border-color: #42524f; transform: translateX(2px); }
.entry-icon { display: grid; place-items: center; width: 44px; height: 44px; color: var(--accent); background: #263020; border-radius: 12px; font: 600 1.15rem Georgia, serif; }
.entry-icon.large { width: 64px; height: 64px; font-size: 1.55rem; border-radius: 16px; }
.entry-copy { min-width: 0; display: grid; gap: 4px; }
.entry-copy strong, .entry-copy span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.entry-copy span, .entry-button time { color: var(--muted); font-size: .82rem; }
.chevron { color: #6e7d78; font-size: 1.6rem; }
.empty-state { padding: 72px 20px; color: var(--muted); text-align: center; border: 1px dashed var(--line); border-radius: var(--radius); }
.empty-state > span { color: var(--accent); font-size: 2rem; }
.empty-state h2 { margin: 12px 0 6px; color: var(--text); }
.detail-card { max-width: 760px; margin: 0 auto; padding: clamp(28px, 6vw, 58px); }
.detail-heading { display: flex; align-items: center; gap: 20px; padding-bottom: 32px; border-bottom: 1px solid var(--line); }
.detail-heading h1 { margin: 0 0 5px; font-size: clamp(.95rem, 2vw, 1.4rem); overflow-wrap: anywhere; }
.detail-heading p { margin-bottom: 6px; }
.details { margin: 0; }
.details > div { display: grid; grid-template-columns: 130px 1fr; gap: 20px; padding: 24px 0; border-bottom: 1px solid var(--line); }
.details dt { color: var(--muted); font-size: .8rem; }
.details dd { margin: 0; overflow-wrap: anywhere; }
.secret-line { display: flex; align-items: center; gap: 10px; }
.secret-line > span { flex: 1; font-family: ui-monospace, monospace; }
.icon-button { padding: 7px 10px; color: var(--accent); background: #263020; border-radius: 7px; font-size: .76rem; }
.detail-actions { display: flex; justify-content: flex-end; padding-top: 30px; }
.editor-layout { max-width: 760px; margin: 0 auto; }
.editor-layout > div:first-child { margin-bottom: 34px; }
.editor-layout h1 { font-size: clamp(1.1rem, 2.5vw, 1.75rem); }
.form-card.wide { padding: clamp(25px, 5vw, 44px); }
.danger-zone { display: flex; justify-content: flex-end; margin-top: 18px; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
[hidden] { display: none !important; }
@media (max-width: 760px) {
body { padding-bottom: 68px; }
.site-header { min-height: 64px; padding: 14px 16px; }
.brand { font-size: 1.15rem; }
.brand-logo { width: 32px; height: 32px; }
.nav-actions { position: fixed; z-index: 20; right: 0; bottom: 0; left: 0; min-height: 60px; padding: 8px 12px max(8px, env(safe-area-inset-bottom)); justify-content: center; gap: 6px; background: rgba(11, 16, 16, .97); border-top: 1px solid var(--line); }
.nav-actions .button { min-height: 42px; padding: 0 13px; }
.nav-actions .text-link { display: inline-flex; min-height: 42px; padding: 0 18px; align-items: center; color: var(--text); border: 1px solid var(--line); border-radius: 9px; }
.auth-layout { grid-template-columns: 1fr; }
.intro { max-width: 34rem; }
.intro h1 { font-size: 1.175rem; }
.intro p:not(.eyebrow) { font-size: .95rem; }
.page-shell { width: min(calc(100% - 28px), 680px); padding: 38px 0; min-height: auto; }
.form-card { padding: 24px 20px; }
input { min-height: 48px; font-size: 16px; }
.notices { top: 74px; right: 14px; left: 14px; }
.notice { max-width: none; }
.vault-header { align-items: stretch; flex-direction: column; }
.vault-header h1 { font-size: 1.175rem; }
.search-field { width: 100%; }
.entry-button { min-height: 72px; grid-template-columns: 40px minmax(0, 1fr) 14px; gap: 12px; padding: 14px 12px; }
.entry-icon { width: 40px; height: 40px; }
.entry-button time { display: none; }
.detail-card { padding: 24px 18px; }
.detail-heading { align-items: flex-start; gap: 14px; padding-bottom: 24px; }
.entry-icon.large { width: 48px; height: 48px; border-radius: 12px; font-size: 1.2rem; }
.detail-heading h1 { font-size: 1rem; }
.details > div { grid-template-columns: 1fr; gap: 8px; }
.secret-line { flex-wrap: wrap; }
.secret-line > span { flex-basis: 100%; min-height: 28px; }
.icon-button { min-height: 42px; padding: 8px 14px; }
.detail-actions .button, .danger-zone .button { min-height: 48px; width: 100%; }
.detail-actions, .detail-actions form, .danger-zone { width: 100%; }
footer { display: none; }
}
-1338
View File
File diff suppressed because it is too large Load Diff
-22
View File
@@ -1,22 +0,0 @@
/**
* Created by ignace on 29-9-15.
*/
function addMarker(map, latlong, text) {
var ll = latlong.split(',');
var myLatlng = new google.maps.LatLng(ll[0],ll[1]);
var marker = new google.maps.Marker({
position: myLatlng,
title: text
});
marker.setMap(map);
}
function makeGoogleMap() {
var mapOptions = {
center: new google.maps.LatLng(50,0),
zoom: 1,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
addAllMarkers(map);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 B

-4
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

-281
View File
@@ -1,281 +0,0 @@
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/*
* -- BASE STYLES --
* Most of these are inherited from Base, but I want to change a few.
*/
body {
line-height: 1.7em;
color: #7f8c8d;
font-size: 13px;
}
h1,
h2,
h3,
h4,
h5,
h6,
label {
color: #34495e;
}
.pure-img-responsive {
max-width: 100%;
height: auto;
}
/*
* -- LAYOUT STYLES --
* These are some useful classes which I will need
*/
.l-box {
padding: 1em;
}
.l-box-lrg {
padding: 2em;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.is-center {
text-align: center;
}
/*
* -- PURE FORM STYLES --
* Style the form inputs and labels
*/
.pure-form label {
margin: 1em 0 0;
font-weight: bold;
font-size: 100%;
}
.pure-form input[type] {
border: 2px solid #ddd;
box-shadow: none;
font-size: 100%;
width: 100%;
margin-bottom: 1em;
}
/*
* -- PURE BUTTON STYLES --
* I want my pure-button elements to look a little different
*/
.pure-button {
background-color: #1f8dd6;
color: white;
padding: 0.5em 2em;
border-radius: 5px;
}
a.pure-button-primary {
background: white;
color: #1f8dd6;
border-radius: 5px;
font-size: 120%;
}
/*
* -- MENU STYLES --
* I want to customize how my .pure-menu looks at the top of the page
*/
.home-menu {
padding: 0.5em;
text-align: center;
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
}
.home-menu {
background: #2d3e50;
}
.pure-menu.pure-menu-fixed {
/* Fixed menus normally have a border at the bottom. */
border-bottom: none;
/* I need a higher z-index here because of the scroll-over effect. */
z-index: 4;
}
.home-menu .pure-menu-heading {
color: white;
font-weight: 400;
font-size: 120%;
}
.home-menu .pure-menu-selected a {
color: white;
}
.home-menu a {
color: #6FBEF3;
}
.home-menu li a:hover,
.home-menu li a:focus {
background: none;
border: none;
color: #AECFE5;
}
/*
* -- SPLASH STYLES --
* This is the blue top section that appears on the page.
*/
.splash-container {
background: #1f8dd6;
z-index: 1;
overflow: hidden;
/* The following styles are required for the "scroll-over" effect */
width: 100%;
height: 88%;
top: 0;
left: 0;
position: fixed !important;
}
.splash {
/* absolute center .splash within .splash-container */
width: 80%;
height: 50%;
margin: auto;
position: absolute;
top: 100px; left: 0; bottom: 0; right: 0;
text-align: center;
}
/* This is the main heading that appears on the blue section */
.splash-head {
font-size: 20px;
font-weight: bold;
color: white;
border: 3px solid white;
padding: 1em 1.6em;
font-weight: 100;
border-radius: 5px;
line-height: 1em;
}
/* This is the subheading that appears on the blue section */
.splash-subhead {
color: white;
letter-spacing: 0.05em;
opacity: 0.8;
}
/*
* -- CONTENT STYLES --
* This represents the content area (everything below the blue section)
*/
.content-wrapper {
/* These styles are required for the "scroll-over" effect */
position: absolute;
top: 20%;
width: 100%;
min-height: 12%;
z-index: 2;
background: white;
}
/* This is the class used for the main content headers (<h2>) */
.content-head {
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.1em;
margin: 2em 0 1em;
}
/* This is a modifier class used when the content-head is inside a ribbon */
.content-head-ribbon {
color: white;
}
/* This is the class used for the content sub-headers (<h3>) */
.content-subhead {
color: #1f8dd6;
}
.content-subhead i {
margin-right: 7px;
}
/* This is the class used for the dark-background areas. */
.ribbon {
background: #2d3e50;
color: #aaa;
}
/* This is the class used for the footer */
.footer {
background: #111;
}
/*
* -- TABLET (AND UP) MEDIA QUERIES --
* On tablets and other medium-sized devices, we want to customize some
* of the mobile styles.
*/
@media (min-width: 48em) {
/* We increase the body font size */
body {
font-size: 16px;
}
/* We want to give the content area some more padding */
.content {
padding: 1em;
}
/* We can align the menu header to the left, but float the
menu items to the right. */
.home-menu {
text-align: left;
}
.home-menu ul {
float: right;
}
/* We increase the height of the splash-container */
/* .splash-container {
height: 500px;
}*/
/* We decrease the width of the .splash, since we have more width
to work with */
.splash {
width: 50%;
height: 50%;
}
.splash-head {
font-size: 250%;
}
/* We remove the border-separator assigned to .l-box-lrg */
.l-box-lrg {
border: none;
}
}
/*
* -- DESKTOP (AND UP) MEDIA QUERIES --
* On desktops and other large devices, we want to over-ride some
* of the mobile and tablet styles.
*/
@media (min-width: 78em) {
/* We increase the header font size even more */
.splash-head {
font-size: 300%;
}
}
-224
View File
@@ -1,224 +0,0 @@
ul#css3menu1,ul#css3menu1 ul{
margin:0;
list-style:none;
background-color:#C0C0C0;
background-image:url("mainbk.png");
background-repeat:repeat;
border-width:0px;
border-style:solid;
border-color:#999999;
-moz-border-radius:4px;
-webkit-border-radius:4px;
border-radius:4px;
}
ul#css3menu1 ul{
display:none;
position:absolute;
left:0;
top:100%;
-moz-box-shadow:0.7px 0.7px 1px #777777;
-webkit-box-shadow:0.7px 0.7px 1px #777777;
box-shadow:0.7px 0.7px 1px #777777;
padding:0 9px 9px;
background-color:#FFF;
background-image:none;
border-width:1px;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
border-style:solid;
border-color:#d8d9da;
}
ul#css3menu1 li:hover>*{
display:block;
}
ul#css3menu1 li:hover{
position:relative;
}
ul#css3menu1 ul ul{
position:absolute;
left:100%;
top:0;
}
ul#css3menu1{
padding:1px 1px 1px 0;
display:block;
font-size:0;
float:left;
}
ul#css3menu1 li{
display:block;
white-space:nowrap;
font-size:0;
float:left;
}
ul#css3menu1>li,ul#css3menu1 li{
margin:0 0 0 1px;
}
ul#css3menu1 ul>li{
margin:1px 0 0;
}
ul#css3menu1 a:active, ul#css3menu1 a:focus{
outline-style:none;
}
ul#css3menu1 a{
display:block;
vertical-align:middle;
text-align:left;
text-decoration:none;
font:bold 12px Arial,sans-serif;
color:#262626;
cursor:default;
padding:6px;
background-color:#C0C0C0;
background-image:url("mainbk.png");
background-repeat:repeat;
background-position:0 200px;
border-width:0px;
border-style:none;
border-color:;
}
ul#css3menu1 ul li{
float:none;
margin:4px 0 0;
}
ul#css3menu1 ul a{
text-align:left;
padding:4px 0 0 0;
background-color:#FFF;
background-image:none;
border-width:1px 0 0 0;
border-style:solid;
border-color:#D9D9D9;
border-radius:0px;
-moz-border-radius:0px;
-webkit-border-radius:0px;
font:12px Arial,sans-serif;
color:#333333;
text-decoration:none;
}
ul#css3menu1 li:hover>a{
background-color:#C0C0C0;
border-style:none;
font:bold 12px Arial,sans-serif;
color:#efefef;
text-decoration:none;
background-image:url("mainbk.png");
background-position:0 100px;
}
ul#css3menu1 img{
border:none;
vertical-align:middle;
margin-right:10px;
}
ul#css3menu1 img.over{
display:none;
}
ul#css3menu1 li:hover > a img.def{
display:none;
}
ul#css3menu1 li:hover > a img.over{
display:inline;
}
ul#css3menu1 span{
display:block;
overflow:visible;
background-position:right center;
background-repeat:no-repeat;
padding-right:0px;
}
ul#css3menu1 ul span{
background-image:url("arrowsub.gif");
padding-right:27px;
}
ul#css3menu1 ul li:hover>a{
background-color:#FFF;
background-image:none;
border-style:solid;
border-color:#D9D9D9;
font:12px Arial,sans-serif;
color:#55de3d;
text-decoration:none;
}
ul#css3menu1 li.topfirst>a{
height:14px;
line-height:14px;
border-radius:4px 0 0 4px;
-moz-border-radius:4px 0 0 4px;
-webkit-border-radius:4px;
-webkit-border-top-right-radius:0;
-webkit-border-bottom-right-radius:0;
text-shadow:#d8d8d8 0px 1px 1px;
}
ul#css3menu1 li.topfirst:hover>a{
line-height:14px;
text-shadow:#3d3d3d 0px -1px 1px;
}
ul#css3menu1 li.topmenu>a{
height:14px;
line-height:14px;
text-shadow:#d8d8d8 0px 1px 1px;
}
ul#css3menu1 li.topmenu:hover>a{
line-height:14px;
text-shadow:#3d3d3d 0px -1px 1px;
}
ul#css3menu1 li.toplast>a{
height:14px;
line-height:14px;
border-radius:0 4px 4px 0;
-moz-border-radius:0 4px 4px 0;
-webkit-border-radius:0;
-webkit-border-top-right-radius:4px;
-webkit-border-bottom-right-radius:4px;
text-shadow:#d8d8d8 0px 1px 1px;
}
ul#css3menu1 li.toplast:hover>a{
line-height:14px;
text-shadow:#3d3d3d 0px -1px 1px;
}
ul#css3menu1 ._>li>a{
padding:0;
}
ul#css3menu1 li.subfirst>a{
border-width:0;
border-style:none;
padding:0;
}
ul#css3menu1 li.subfirst:hover>a{
border-style:none;
}
img.ico{
height: 1em;
width: 1em;
-384
View File
@@ -1,384 +0,0 @@
/* COOKIES */
var editModus = '';
var Plugins = new Array();
var PageAge = -1;
var PageTimer;
var PlugDispState=[]; // state 1=visible, 0=hidden
var blockStyle = "block"
var alias = '/dashboard'
var Cookies = {
init: function () {
var allCookies = document.cookie.split('; ');
for (var i=0;i<allCookies.length;i++) {
var cookiePair = allCookies[i].split('=');
this[cookiePair[0]] = cookiePair[1];
}
},
create: function (name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
this[name] = value;
},
erase: function (name) {
this.create(name,'',-1);
this[name] = undefined;
}
};
Cookies.init();
function notXMLHttpRequest(x) {
//log('notXMLHttpRequest 1');
r = x
if (x instanceof XMLHttpRequest) {
r = evalJSONRequest(x);
}
return r;
}
function hide(element) {
getElement(element).style.display = 'None';
}
function unhide(element) {
getElement(element).style.display = blockStyle;
}
var Session = {
init: function(hostName) {
this.menuserver = hostName;
this.username = this.getUserId();
},
userid: function() {
return this.username+'@'+this.menuserver;
},
saveUserId: function(value) {
Cookies.create('userId', value, 300);
},
getUserId: function() {
return Cookies['userId'];
},
loadDispState: function() {
PlugDispState = [];
var states = Cookies['dispState'];
if (states) {
var slist = states.split('&');
for (i in slist) {
kv = slist[i].split(':');
PlugDispState[kv[0]]=kv[1];
}
}
},
saveDispState: function() {
var state = '';
for (k in PlugDispState) {
var v = PlugDispState[k];
state = state + k + ':' + v + '&';
}
Cookies.create('dispState', state, 300);
},
getDispState: function(plugin) {
return PlugDispState[plugin]+0;
},
setDispStateOff: function(plugin) {
PlugDispState[plugin]=0;
Session.saveDispState();
},
setDispStateOn: function(plugin) {
PlugDispState[plugin]=1;
Session.saveDispState();
}
}
var Ajax = {
ajaxFailed: function(err) {
// alert('Cannot fetch data from server, try again...');
Page.showServerError();
},
getConfig: function(userId, callback) {
// do a call to the server for the config for username@menuserver
//log('ajax: get menu as html');
var d = loadJSONDoc(alias+'/loadconfig?mode=html&userId=' + userId);
d.addCallbacks(callback, this.ajaxFailed);
},
getConfigAsText: function(userId, callback) {
// do a call to the server for the config for username@menuserver
var d = loadJSONDoc(alias+'/loadconfig?mode=text&userId=' + userId);
d.addCallbacks(callback, this.ajaxFailed);
},
saveMenuFromText: function(userId, menu, callback) {
var content = queryString({mode:'text',userId:userId, menu:menu})
var d = doXHR(alias+'/savemenu', {method:'POST', sendContent:content, headers:{"Content-Type":"application/x-www-form-urlencoded"}});
d.addCallbacks(callback, this.ajaxFailed);
},
savePlugsFromText: function(userId, plugs, callback) {
//log('Ajax.savePlugsFromText')
var content = queryString({mode:'text',userId:userId, plugs:plugs})
var d = doXHR(alias+'/saveplugs', {method:'POST', sendContent:content, headers:{"Content-Type":"application/x-www-form-urlencoded"}});
d.addCallbacks(callback, this.ajaxFailed);
},
loadPlugin: function(userId, pluginName, parameters, callbackFunction) {
// do a call to the server for the config for username@menuserver
//log('ajax: get pluginbody for ' + pluginName);
var d = loadJSONDoc(alias+'/loadPlugin?plugin='+pluginName+'&parameters='+urlEncode(parameters)+'&userId=' + Session.userid());
d.addCallbacks(callbackFunction, this.ajaxFailed);
//log(' cbtostr: '+callback.toString());
}
}
var Page = {
start: function(hostName) {
Session.init(hostName);
connect('username', 'onchange', this, 'userNameChanged');
//log('Page.start');
EditArea.init(this);
getElement('username').value = Session.username;
Session.loadDispState();
Ajax.getConfig(Session.userid(), this.redrawPage);
},
userNameChanged: function() {
Session.saveUserId(getElement('username').value); // stored in cookie
Ajax.getConfig(Session.userid(), Page.redrawPage);
},
redrawPage: function(transport) {
Page.redrawMenu(transport);
Page.redrawPlugs(transport);
},
redrawMenu: function(transport) {
Page.hideServerError();
//log('Page.redrawMenu ');
transport = notXMLHttpRequest(transport);
//getElement('menu3').innerHTML = transport.menu;
},
redrawPlugs: function(transport) {
Page.hideServerError();
transport = notXMLHttpRequest(transport);
log('Page.redrawPlugs'+transport.plugs);
Plugins = [];
var html = '';
var column = 1;
var plist = transport.plugs.split('\n');
var nr = 0;
for (i in plist) {
var plug = plist[i].replace(/^\s+|\s+$/g,"");
log('running along plist ' + plug);
if (plug.length<10) {
getElement('column'+column).innerHTML = html;
html = '';
column = column + 1;
} else {
if (plug[0]!='#') {
p = new Plugin(nr, plug);
Plugins[nr]=p;
html = html + '<div id="plugin'+nr+'" class="plugindiv"></div>';
nr += 1;
}
}
}
//log('Page - end of redraw');
// reset
Page.refreshPlugins();
},
refreshPlugins: function() {
//log('Page.refreshPlugins age='+PageAge);
PageAge += 1;
PageTimer = callLater(60, Page.refreshPlugins);
//log('triggered ....' + PageAge);
for (p in Plugins) {
Plugins[p].refresh(PageAge);
}
},
editMenu: function() {
EditArea.edit('m');
},
editMenuItem: function() {
EditArea.edit();
},
editPlugins: function() {
EditArea.edit('p');
},
showServerError: function() {
getElement('servererror').style.display = blockStyle;
},
hideServerError: function() {
getElement('servererror').style.display = 'None';
}
};
var EditArea = {
init: function(caller) {
this.page=caller
connect('savemenu', 'onclick', EditArea, 'save');
connect('saveexitmenu', 'onclick', EditArea, 'saveExit');
connect('closemenu', 'onclick', EditArea, 'hide');
},
edit: function(modus) {
editModus = modus
getElement('editarea').style.display = blockStyle;
if (editModus=='m') {
Ajax.getConfigAsText(Session.userid(), EditArea.redrawMenu);
}
if (editModus=='p') {
Ajax.getConfigAsText(Session.userid(), EditArea.redrawPlugs);
}
},
save: function() {
menu = getElement('menulines').value;
getElement('editarea').style.display = blockStyle;
if (editModus=='m') {
//log('Editarea.save: saving menu');
Ajax.saveMenuFromText(Session.userid(), getElement('menulines').value, Page.redrawMenu);
}
if (editModus=='p') {
//log('Editarea.save: saving plugins');
if (PageTimer) {
PageTimer.cancel;
}
PageAge=-1;
Ajax.savePlugsFromText(Session.userid(), getElement('menulines').value, Page.redrawPlugs);
}
},
saveExit: function() {
this.save();
this.hide();
},
hide: function() {
getElement('editarea').style.display = 'None';
},
redrawMenu: function(transport) {
Page.hideServerError();
//Page.redrawMenu(transport);
getElement('menulines').value = transport.menu;
},
redrawPlugs: function(transport) {
Page.hideServerError();
//Page.redrawPlugs(transport);
getElement('menulines').value = transport.plugs;
}
};
var EditPage = {
start: function(hostName) {
Session.init(hostName);
//log('start page');
EditArea.init(this);
Ajax.getConfig(Session.userid(), this.redrawPage);
},
redrawPage: function(transport) {
Page.hideServerError();
//log('ep: redraw page');
transport = notXMLHttpRequest(transport);
EditPage.redrawMenu(transport);
EditArea.edit();
},
redrawMenu: function(transport) {
Page.hideServerError();
//log('ep: redraw menu');
transport = notXMLHttpRequest(transport);
//log('redrawPage: ' + transport.menu);
//alert(transport);
getElement('menu3').innerHTML = transport.menu;
}
}
/********************* plugin object *********************/
function Plugin(nr, initstring) { // Define super class
// set divname, name, serverquery and timer
this.nr = nr;
this.divname = 'plugin' + nr;
// the initstrig should look like:
// <name>:<functionname>([<param1>[, param2 ..]]),<refreshrate in minutes>
var objRegExp = /^(\w+):.*$/;
this.title = initstring.replace(objRegExp, "$1");
objRegExp = /^\w*:(\w+)\(.*$/;
this.func = initstring.replace(objRegExp, "$1");
objRegExp = /^[^(]+\(([^)]*).*$/;
this.params = initstring.replace(objRegExp, "$1");
//log(this.params);
objRegExp = /.+\),(\d+)$/;
this.refreshrate = parseInt(initstring.replace(objRegExp, "$1"));
this.nextrefreshage = 0;
//log('created plugin '+this.title + ', '+this.func);
}
Plugin.prototype.refresh = function(age) { // Define Method
//log(' Refresh ' + this.divname );
if (age >= this.nextrefreshage) {
//log(' Refresh 2 ' + this.divname );
this.nextrefreshage += this.refreshrate;
Ajax.loadPlugin(Session.userid, this.func, this.params, bind(this.redraw, this))
}
}
Plugin.prototype.redraw = function(transport) { // Define Method
//log('redraw '+this.nr);
Page.hideServerError();
var body = '<div id="plugbody'+this.nr+'" class="plugbody">'+transport.plugin+'</div>';
var header = '<a class="plugheader" href="javascript:Plugins['+this.nr+'].toggle();"><img id="toggle'+this.nr+'" src=alias+"/static/icon_minus.gif"/></a>'
var html = '<table class="big"><tr><td class="plugtitle">'+this.title+'</td><td class="plugheader">'+header+'</td></tr><td class="plugbody" colspan="2">'+body+'</td></table>';
getElement(this.divname).innerHTML = html;
this.setDispState(Session.getDispState(this.title));
//log('REDRAW');
}
Plugin.prototype.toggle = function() { // Define Method
var button = getElement('toggle'+this.nr).src;
// could be a plus or a minus button
if (button.indexOf('minus')>=0) {
this.setDispState(0);
} else {
this.setDispState(1);
}
}
Plugin.prototype.setDispState = function(state) { // Define Method
var div = 'plugbody'+this.nr;
if (state == 0) {
hide(div);
getElement('toggle'+this.nr).src=alias+"/static/icon_plus.gif";
Session.setDispStateOff(this.title);
} else {
unhide(div);
getElement('toggle'+this.nr).src=alias+"/static/icon_minus.gif";
Session.setDispStateOn(this.title);
}
}
-11
View File
File diff suppressed because one or more lines are too long
-113
View File
@@ -1,113 +0,0 @@
body {
background-color: #f6f6f6;
background-image: url("logo.png");
background-repeat: no-repeat;
}
#namespace {
top: 15px;
left: 90%;
width: 10%;
height: 20px;
position: absolute;
}
#servererror {
top: 15px;
left: 75%;
width: 10%;
position: absolute;
color: orange;
font-family: Verdana;
font-size: 0.7em;
border: 1px solid red;
padding: 4px;
}
input#username {
width: 80%;
border: none;
border-bottom: 1px solid #999;
color: #999;
background-color: #fefefe;
}
.icon {
width: 12px;
height: 12px;
}
#pluginArea {
padding-top: 50px;
}
#intern1 {
width: 100%;
background-color: #fefefe;
}
table.big {
width: 100%;
}
td.column {
width: 33.33%;
vertical-align: top;
}
#editarea {
display: none;
top: 100px;
left: 30px;
width: 80%;
position: absolute;
}
textarea {
width: 70em;
height: 40em;
}
.plugtitle, .plugheader{
background-color: #ccc;
font-family: Verdana;
font-size: 0.9em;
}
.plugheader {
text-align: right;
}
div.plugbody {
width: 100%;
background-color: #fefefe;
font-family: Helvetica;
font-size: 0.8em;
}
div.textblock {
background-color: #fefefe;
width: 100%;
}
a.rss {
text-decoration: none;
color: #222;
}
a.rss:hover {
text-decoration: none;
color: green;
}
img {
border-style: none;
}
img.plugbody {
width: 100%;
}
-135
View File
@@ -1,135 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flowers</title>
<!-- link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/grids-responsive-min.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">
<link rel=stylesheet type=text/css href="/static/marketing.css">
<link rel=stylesheet type=text/css href="/static/flower.css">
<!-- script type="text/javascript" src="/static/jquery-min.js"></script -->
</head>
<body>
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
<ul class="pure-menu-list">
<input type="text" id="search" placeholder="search..." value="">
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('new')" class="pure-menu-link">New</a></li>
<li class="pure-menu-item"><a href="javascript:DoSubmit('logout')" class="pure-menu-link">Logout</a></li>
</ul>
</div>
</div>
<div class="content-wrapper">
<div class="content">
<div class="pure-g">
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
</div>
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-3-5">
<table id="flowerlist" class="flowerlist pure-table pure-table-horizontal">
<tr class="flowerlist">
<td class="flowerlist">
<a href='www.google.com'>
org2, login2
</a>
</td>
<td class="right"><span style="white-space: nowrap;">2016-11-30</span></td>
</tr>
<tr class="flowerlist">
<td class="flowerlist>
<a href="javascript:DoSubmit('show', 'RABO creditcard', '2001-03-17 13:24:07' , '')">
RABO creditcard,
</a>
</td>
<td class="right"><span style="white-space: nowrap;">2001-03-17</span></td>
</tr>
<tr class="flowerlist">
<td class="flowerlist>
<a href="javascript:DoSubmit('show', 'ABN AMRO creditcard', '2001-03-17 13:23:25' , '')">
ABN AMRO creditcard, 5422 2300 0373 7907
</a>
</td>
<td class="right"><span style="white-space: nowrap;">2001-03-17</span></td>
</tr>
</table>
</div>
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
</div>
</div>
</div>
</div>
<!-- script language="javascript">
var $rows = $('#flowerlist tr');
$('#search').keyup(function() {
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function() {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
});
$('#search').keyup();
</script -->
<form id="gotopage" action="/app" method="POST">
<input type="hidden" id="action" name="action" value="">
<input type="hidden" id="organization" name="organization" value="">
<input type="hidden" id="myid" name="myid" value="">
<input type="hidden" id="datetime" name="datetime" value="">
<input type="hidden" id="filter" name="filter" value="">
</form>
<!-- script language="javascript">
function DoSubmit(action, org, datetime, filter) {
$('#action').val(action);
$('#organization').val(org);
$('#datetime').val(datetime);
$('#filter').val( $('#search').val() );
$('#gotopage').submit();
}
</script -->
<div class="footer l-box is-center">
Thank you for using the flowershop.
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+24 -62
View File
@@ -2,74 +2,36 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>{% block title %}Flowers{% endblock %}</title>
<!-- link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css" -->
<link rel="stylesheet" href="static/pure-min.css">
<link rel="stylesheet" href="static/grids-responsive-min.css">
<link rel="stylesheet" href="static/font-awesome.css">
<link rel=stylesheet type=text/css href="static/marketing.css">
<link rel=stylesheet type=text/css href="static/flower.css">
<script type="text/javascript" src="static/jquery-min.js"></script>
<link rel="stylesheet" href="{{ url_for('flower_vase.static', filename='flower.css') }}">
</head>
<body>
<header class="site-header">
<a class="brand" href="{{ url_for('flower_vase.index') }}" aria-label="Flowers home">
<img class="brand-logo" src="{{ url_for('flower_vase.static', filename='wwwsuynl.png') }}" width="34" height="34" alt="">
<span>Flowers</span>
</a>
<nav class="nav-actions" aria-label="Bouquet actions">
{% block menuoptions %}{% endblock %}
</nav>
</header>
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
<ul class="pure-menu-list">
{% block menuoptions %}
<input type="text" id="search" placeholder="search..." value="{{filter}}">
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('new')" class="pure-menu-link">New</a></li>
<li class="pure-menu-item"><a href="javascript:DoSubmit('logout')" class="pure-menu-link">Logout</a></li>
<li class="pure-menu-item"><a href="javascript:DoSubmit('rehush')" class="pure-menu-link">Password</a></li>
{% endblock menuoptions %}
</ul>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="notices" aria-live="polite">
{% for category, message in messages %}
<div class="notice notice-{{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% endblock headandmenu %}
{% block content %}
{% endblock %}
<form id="gotopage" action="{{ url_for('flower_vase.application') }}" method="POST">
<input type="hidden" id="_action" name="action" value="">
<input type="hidden" id="_organization" name="organization" value="">
<input type="hidden" id="_myid" name="myid" value="">
<input type="hidden" id="_datetime" name="datetime" value="">
<input type="hidden" id="_filter" name="filter" value="">
</form>
<script language="javascript">
function DoSubmit(action, org, datetime, filter) {
$('#_action').val(action);
$('#_organization').val(org);
$('#_datetime').val(datetime);
$('#_filter').val( $('#search').val() );
$('#gotopage').submit();
}
</script>
{% block footer %}
<div class="footer l-box is-center">
Thank you for using the flowershop.
</div>
{% endblock %}
<main class="page-shell">
{% block content %}{% endblock %}
</main>
<footer>Private by design. Stored in your existing Flowers bouquet.</footer>
</body>
</html>
+19 -31
View File
@@ -1,35 +1,23 @@
{% extends "common.html" %}
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
</div>
</div>
{% endblock headandmenu %}
{% block title %}Create a bouquet · Flowers{% endblock %}
{% block menuoptions %}<a class="text-link" href="{{ url_for('flower_vase.index') }}">Sign in</a>{% endblock %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-aligned" action="{{ url_for('flower_vase.create') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input maxlength="32" name="name" type="text" placeholder="make up your username">
<section class="auth-layout">
<div class="intro">
<p class="eyebrow">New bouquet</p>
<h1>Create a bouquet</h1>
<p>This uses the existing Flowers database structure so it remains readable by compatible installations.</p>
</div>
<div class="pure-control-group">
<input maxlength="10" name="password" type="password" placeholder="make up you password, the longer the better">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Create Vase</button>
</div>
</fieldset>
<form class="card form-card" action="{{ url_for('flower_vase.create') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h2>New bouquet</h2>
{% if message %}<p class="field-error">{{ message }}</p>{% endif %}
<label for="name">Username</label>
<input maxlength="31" id="name" name="name" type="text" pattern="[A-Za-z0-9_]+" autocomplete="username" required>
<label for="password">Password</label>
<input maxlength="43" id="password" name="password" type="password" pattern="[A-Za-z0-9_+/-]+" autocomplete="new-password" required>
<p class="field-help">Use Base64-compatible characters for legacy encryption compatibility.</p>
<button class="button primary" type="submit">Create bouquet</button>
</form>
{{ message }}
</div>
</div>
{% endblock content %}
</section>
{% endblock %}
+37 -41
View File
@@ -1,45 +1,41 @@
{% extends "common.html" %}
{% block title %}{% if flower.organization %}Edit{% else %}New entry{% endif %} · Flowers{% endblock %}
{% block menuoptions %}
<li class="pure-menu-item pure-menu-selected"><a href="javascript:$('#newFlower').submit()" class="pure-menu-link">Save</a></li>
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('deactivate','{{ flower.organization }}','{{ flower.dateCreated }}','')" class="pure-menu-link">Delete</a></li>
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('list','','','')" class="pure-menu-link">Cancel</a></li>
{% endblock menuoptions %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-stacked" id="newFlower" action="{{ url_for('flower_vase.application') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input maxlength="70" size="50" id="organization" name="organization" type="text" value="{{ flower.organization }}" placeholder="Organization">
</div>
<div class="pure-control-group">
<input maxlength="70" id="myname" name="myname" type="text" value="{{ flower.myName }}" placeholder="Your name (optional)">
</div>
<div class="pure-control-group">
<input maxlength="70" id="myid" name="myid" type="text" value="{{ flower.myID }}" placeholder="Your Login">
</div>
<div class="pure-control-group">
<input maxlength="30" id="secret" name="secret" type="text" placeholder="Your secret">
</div>
<input type="hidden" id="action" name="action" value="save">
<input type="hidden" id="filter" name="filter" value="DoNotSetFilterinCookie">
</fieldset>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="list">
<button class="button ghost compact" type="submit">Cancel</button>
</form>
</div>
</div>
<script language="javascript">
</script>
{% endblock %}
{% block content %}
<section class="editor-layout">
<div>
<p class="eyebrow">{% if flower.organization %}New version{% else %}New flower{% endif %}</p>
<h1>{% if flower.organization %}Edit entry{% else %}Add an entry{% endif %}</h1>
<p class="muted">Saving creates a new version. Your historical rows remain untouched.</p>
</div>
<form class="card form-card wide" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="save">
<input type="hidden" name="filter" value="{{ filter|default('') }}">
<label for="organization">Organization</label>
<input maxlength="80" id="organization" name="organization" type="text" value="{{ flower.organization }}" autocomplete="organization" required>
<label for="myname">Your name <span>optional</span></label>
<input maxlength="70" id="myname" name="myname" type="text" value="{{ flower.myName }}" autocomplete="name">
<label for="myid">Login</label>
<input maxlength="70" id="myid" name="myid" type="text" value="{{ flower.myID }}" autocomplete="username" required>
<label for="secret">Secret</label>
<input maxlength="120" id="secret" name="secret" type="password" autocomplete="new-password" required>
<button class="button primary" type="submit">Save new version</button>
</form>
{% if flower.dateCreated %}
<form class="danger-zone" action="{{ url_for('flower_vase.application') }}" method="post" onsubmit="return confirm('Remove this entry from the active list?')">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="deactivate">
<input type="hidden" name="organization" value="{{ flower.organization }}">
<input type="hidden" name="datetime" value="{{ flower.dateCreated }}">
<button class="button danger" type="submit">Deactivate entry</button>
</form>
{% endif %}
</section>
{% endblock %}
+20 -33
View File
@@ -1,37 +1,24 @@
{% extends "common.html" %}
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
</div>
</div>
{% endblock headandmenu %}
{% block title %}Sign in · Flowers{% endblock %}
{% block menuoptions %}
<a class="text-link" href="{{ url_for('flower_vase.create') }}">Create a bouquet</a>
{% endblock %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-aligned" action="{{ url_for('flower_vase.application') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input name="name" type="text" placeholder="{{ name_suggestion }}">
<section class="auth-layout">
<div class="intro">
<p class="eyebrow">Private bouquet</p>
<h1>Your flowers</h1>
<p>Sign in to open your encrypted records.</p>
</div>
<div class="pure-control-group">
<input name="password" type="password" placeholder="{{ pwd_suggestion }}">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Submit</button>
</div>
</fieldset>
<input type="hidden" id="action" name="action" value="login">
<input type="hidden" id="filter" name="filter" value="DoNotSetFilterinCookie">
<form class="card form-card" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="login">
<h2>Sign in</h2>
<label for="name">Username</label>
<input id="name" name="name" type="text" autocomplete="username" placeholder="{{ name_suggestion }}" required autofocus>
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" placeholder="{{ pwd_suggestion }}" required>
<button class="button primary" type="submit">Open bouquet</button>
</form>
</div>
</div>
{% endblock content %}
</section>
{% endblock %}
+68 -42
View File
@@ -1,51 +1,77 @@
{% extends "common.html" %}
{% block title %}Your bouquet · Flowers{% endblock %}
{% block menuoptions %}
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="new">
<button class="button primary compact" type="submit">New entry</button>
</form>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="rehush">
<button class="button ghost compact" type="submit">Password</button>
</form>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="logout">
<button class="button ghost compact" type="submit">Sign out</button>
</form>
{% endblock %}
{% block content %}
<div class="content-wrapper">
<div class="content">
<div class="pure-g">
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
<section class="vault-header">
<div>
<h1>Your bouquet</h1>
</div>
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-3-5">
<table id="flowerlist" class="flowerlist pure-table pure-table-horizontal">
<label class="search-field" for="search">
<span class="sr-only">Search entries</span>
<input id="search" type="search" placeholder="Search organization or login" value="{{ filter|default('') }}" autocomplete="off">
</label>
</section>
<div class="entry-list" id="entry-list">
{% for flower in flowers %}
<tr class="flowerlist">
<td class="flowerlist">
<a href="javascript:DoSubmit('show', '{{flower.organization}}', '{{flower.dateCreated}}' , '')">
{{ flower.organization }}, {{ flower.myID }}
</a>
</td>
<td class="right"><span style="white-space: nowrap;">{{ flower.dateCreated[:10] }}</span></td>
</tr>
<form class="entry-row" data-search="{{ flower.organization }} {{ flower.myID }}" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="show">
<input type="hidden" name="organization" value="{{ flower.organization }}">
<input type="hidden" name="datetime" value="{{ flower.dateCreated }}">
<input type="hidden" name="filter" class="current-filter" value="{{ filter|default('') }}">
<button type="submit" class="entry-button">
<span class="entry-icon" aria-hidden="true">{{ flower.organization[:1]|upper }}</span>
<span class="entry-copy">
<strong>{{ flower.organization }}</strong>
<span>{{ flower.myID }}</span>
</span>
<time datetime="{{ flower.dateCreated }}">{{ flower.dateCreated[:10] }}</time>
<span class="chevron" aria-hidden="true"></span>
</button>
</form>
{% else %}
<div class="empty-state">
<span aria-hidden="true"></span>
<h2>No active entries</h2>
<p>Create an entry to start your collection.</p>
</div>
{% endfor %}
</table>
</div>
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
<p class="empty-state filtered-empty" id="filtered-empty" hidden>No entries match that search.</p>
</div>
</div>
</div>
</div>
<script language="javascript">
var $rows = $('#flowerlist tr');
$('#search').keyup(function() {
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function() {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
<script>
const search = document.querySelector('#search');
const rows = [...document.querySelectorAll('.entry-row')];
const filteredEmpty = document.querySelector('#filtered-empty');
function filterRows() {
const query = search.value.trim().toLocaleLowerCase();
let visible = 0;
rows.forEach((row) => {
const match = row.dataset.search.toLocaleLowerCase().includes(query);
row.hidden = !match;
row.querySelector('.current-filter').value = search.value;
if (match) visible += 1;
});
$('#search').keyup();
filteredEmpty.hidden = visible !== 0 || rows.length === 0;
}
search.addEventListener('input', filterRows);
filterRows();
</script>
{% endblock content %}
{% endblock %}
+20 -36
View File
@@ -1,40 +1,24 @@
{% extends "common.html" %}
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
</div>
</div>
{% endblock headandmenu %}
{% block title %}Change password · Flowers{% endblock %}
{% block menuoptions %}<a class="text-link" href="{{ url_for('flower_vase.index') }}">Back to sign in</a>{% endblock %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-aligned" action="{{ url_for('flower_vase.update_pwd') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input name="name" type="text" placeholder="your existing name">
<section class="auth-layout">
<div class="intro">
<p class="eyebrow">Bouquet security</p>
<h1>Change password</h1>
<p>Every existing encrypted field will be rewritten with the new legacy-compatible key.</p>
</div>
<div class="pure-control-group">
<input name="old_password" type="password" placeholder="your existing password">
</div>
<div class="pure-control-group">
<input maxlength="10" name="new_password" type="password" placeholder="make up you password, the longer the better">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Update Password</button>
</div>
</fieldset>
<form class="card form-card" action="{{ url_for('flower_vase.update_pwd') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h2>Bouquet flowers</h2>
{% if message %}<p class="field-error">{{ message }}</p>{% endif %}
<label for="name">Username</label>
<input id="name" name="name" type="text" autocomplete="username" required>
<label for="old-password">Current password</label>
<input id="old-password" name="old_password" type="password" autocomplete="current-password" required>
<label for="new-password">New password</label>
<input maxlength="43" id="new-password" name="new_password" type="password" pattern="[A-Za-z0-9_+/-]+" autocomplete="new-password" required>
<button class="button primary" type="submit">Re-encrypt bouquet</button>
</form>
{{ message }}
</div>
</div>
{% endblock content %}
</section>
{% endblock %}
+79 -44
View File
@@ -1,47 +1,82 @@
{% extends "common.html" %}
{% block title %}{{ flower.organization }} · Flowers{% endblock %}
{% block menuoptions %}
<li class="pure-menu-item pure-menu-selected"><a href="javascript:editFlower()" class="pure-menu-link">Edit</a></li>
{% endblock menuoptions %}
{% block content %}
<div class="splash-container">
<div class="splash">
<table class="pure-table" style="width: 100%">
<tr><td class="white right">Org</td><td class="white left">{{ flower.organization }}</td> </tr>
<tr><td class="white right">You</td><td class="white left">{{ flower.myName }}</td> </tr>
<tr><td class="white right">Login</td><td class="white left">{{ flower.myID }}</td> </tr>
<tr><td class="white right">Hash</td><td id="secrettd" class="white left"><div id="secret" value="{{ flower.mySecret }}" onclick="myCopyFunction()">{{ flower.mySecret }}</div></td></tr>
<tr><td class="white">{{ flower.dateCreated[:10] }}</td><td></td> </tr>
</table>
</div>
</div>
<script language="javascript">
setTimeout(function(){
DoSubmit('list', '{{flower.organization}}', '{{flower.dateCreated}}', 'DoNotSetFilterinCookie')
}, 5000);
function editFlower() {
DoSubmit('edit', '{{flower.organization}}', '{{flower.dateCreated}}', 'DoNotSetFilterinCookie')
}
function myCopyFunction() {
var copyText = document.getElementById("secret");
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.getAttribute("value"));
// alert("Copied the text: "+copyText.getAttribute("value"));
$("#secrettd").removeClass("white");
$("#secrettd").addClass("blue");
}
</script>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="list">
<input type="hidden" name="filter" value="{{ filter|default('') }}">
<button class="button ghost compact" type="submit">Back to bouquet</button>
</form>
{% endblock %}
{% block content %}
<section class="detail-card card">
<div class="detail-heading">
<span class="entry-icon large" aria-hidden="true">{{ flower.organization[:1]|upper }}</span>
<div>
<p class="eyebrow">Flower</p>
<h1>{{ flower.organization }}</h1>
{% if flower.dateCreated not in ('STORED', 'FAILED') %}<p class="muted">Version from {{ flower.dateCreated[:10] }}</p>{% endif %}
</div>
</div>
<dl class="details">
<div><dt>Name</dt><dd>{{ flower.myName or '—' }}</dd></div>
<div><dt>Login</dt><dd>{{ flower.myID }}</dd></div>
<div>
<dt>Secret</dt>
<dd class="secret-line">
<span id="secret" data-secret="{{ flower.mySecret }}">••••••••••••</span>
<button class="icon-button" id="reveal" type="button">Reveal</button>
<button class="icon-button" id="copy" type="button">Copy</button>
</dd>
</div>
</dl>
{% if flower.dateCreated not in ('STORED', 'FAILED') %}
<div class="detail-actions">
<form id="edit-form" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="organization" value="{{ flower.organization }}">
<input type="hidden" name="datetime" value="{{ flower.dateCreated }}">
<button class="button primary" type="submit">Edit entry</button>
</form>
</div>
{% endif %}
</section>
<script>
const secret = document.querySelector('#secret');
const reveal = document.querySelector('#reveal');
const copy = document.querySelector('#copy');
const editForm = document.querySelector('#edit-form');
let visible = false;
const returnToList = window.setTimeout(() => {
window.location.assign('{{ url_for("flower_vase.application") }}');
}, 8000);
if (editForm) {
editForm.addEventListener('submit', () => window.clearTimeout(returnToList));
}
reveal.addEventListener('click', () => {
visible = !visible;
secret.textContent = visible ? secret.dataset.secret : '••••••••••••';
reveal.textContent = visible ? 'Hide' : 'Reveal';
});
copy.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(secret.dataset.secret);
copy.textContent = 'Copied';
} catch (_) {
const field = document.createElement('textarea');
field.value = secret.dataset.secret;
field.style.position = 'fixed';
field.style.opacity = '0';
document.body.appendChild(field);
field.select();
copy.textContent = document.execCommand('copy') ? 'Copied' : 'Select and copy';
field.remove();
}
window.setTimeout(() => copy.textContent = 'Copy', 1400);
});
</script>
{% endblock %}
-39
View File
@@ -1,39 +0,0 @@
# from flask import Flask
import datetime
import pytz
from Config import *
import sys
import datetime
from FlowerServices import Flower,SuperFlower
from AccessControl import Access
import json
# from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash, json, make_response
# from werkzeug.exceptions import HTTPException
newuser = 'janine'
newhush = 'demaat196'
# login db with generic user
#f = SuperFlower(newuser, newhush)
#print(f)
#if f.createNewTable():
# login as new user
# u = Flower(newuser, newhush)
# add one line
# u.add('Demo organisation', 'Demo name', 'Demo login', 'Demo hush')
# add 1 pwd
# u = Flower(newuser, newhush)
# # add one line
# u.add('Demo2 organisation', 'Demo name', 'Demo login', 'Demo hush')
# read one line
u = Flower(newuser, newhush)
#flower=u.one('webmail catch-all-email@suy.nu', '2023-03-13 20:25:52')
flowers=u.numberOfEntries()
print(flowers)
#update pwd
#u = Flower(newuser, newhush)
#u.update_pwd('white')
-33
View File
@@ -1,33 +0,0 @@
CREATE TABLE `sjaak` (
`organization` varchar(50) NOT NULL,
`myID` varchar(50) NOT NULL,
`myName` varchar(50) NOT NULL,
`mySecret` varchar(50) NOT NULL,
`dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` int(11) DEFAULT '0',
PRIMARY KEY (`organization`,`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
MariaDB [Flowers]> create user ignace@localhost identified by 'ignace';
Query OK, 0 rows affected (0.00 sec)
MariaDB [Flowers]> grant create,update,insert on Flowers.ignace to ignace@localhost identified by 'ignace';
ERROR 1044 (42000): Access denied for user 'dude'@'localhost' to database 'mysql'
MariaDB [Flowers]> grant create,update,insert on Flowers.ignace to ignace@localhost;
# as root:
create database Flowers;
grant create,select,update,insert,grant option on Flowers.* to flower@localhost identified by '608f0b988db4a96066af7dd8870de96c';
grant create user,reload on *.* to flower@localhost;
flush privileges;
create user {0}@localhost identified by '{1}';
grant select,update,insert on Flowers.{0} to {0}@localhost;
flush privileges;
delete from ignace where organization in ('myRepository', 'Nero', 'office XP', 'SwisH');
-29
View File
@@ -1,29 +0,0 @@
from fernet import Fernet
# Generate a Fernet key
key = b'XBxB603cX_mULEXxfavOg3FDc0Ox3gChwYEY-Uxd3tE='
pwd = 'black'
key = bytes((pwd*10)[:43]+"=", "ascii")
print(key)
# Create a Fernet object with that key
f = Fernet(key)
# Input string to be encrypted
input_string = "Hello World!Hello World!Hello World!Hello World!Hello World!"
# Encrypt the string
encrypted_string = f.encrypt(input_string.encode()).decode()
print(encrypted_string)
# Decrypt the encrypted string
decrypted_string = f.decrypt(encrypted_string.encode()).decode()
# Print the original and decrypted strings
print("Original String:", input_string)
print("Decrypted String:", decrypted_string)
+74
View File
@@ -0,0 +1,74 @@
import json
import unittest
from unittest.mock import patch
from flowers import create_app
class FakeVault:
def __init__(self, *args):
pass
def authenticate(self):
return True
def all(self):
return [
{
"organization": "Acme",
"myID": "alice@example.test",
"dateCreated": "2024-02-03 12:00:00",
}
]
class AppTests(unittest.TestCase):
def setUp(self):
self.app = create_app({"TESTING": True, "SECRET_KEY": "test-secret"})
self.client = self.app.test_client()
def csrf(self):
self.client.get("/")
with self.client.session_transaction() as browser_session:
return browser_session["csrf_token"]
def test_login_form_requires_csrf(self):
response = self.client.post(
"/browser",
data={"action": "login", "name": "alice", "password": "legacy_pwd"},
)
self.assertEqual(response.status_code, 400)
@patch("flower_vase.Flower", FakeVault)
def test_login_renders_existing_entries(self):
response = self.client.post(
"/browser",
data={
"csrf_token": self.csrf(),
"action": "login",
"name": "alice",
"password": "legacy_pwd",
},
follow_redirects=True,
)
self.assertEqual(response.status_code, 200)
self.assertIn(b"Acme", response.data)
with self.client.session_transaction() as browser_session:
self.assertNotIn("dbpwd", browser_session)
self.assertNotIn("password", browser_session)
self.assertIn("auth_token", browser_session)
@patch("flower_vase.Flower", FakeVault)
def test_legacy_api_keeps_double_encoded_list(self):
response = self.client.post(
"/app",
data={"action": "list", "name": "alice", "password": "legacy_pwd"},
)
payload = response.get_json()
nested = json.loads(payload["list"])
self.assertEqual(payload["result"], 1)
self.assertEqual(nested[0]["organization"], "Acme")
if __name__ == "__main__":
unittest.main()
+120
View File
@@ -0,0 +1,120 @@
import unittest
from FlowerServices import Flower, InvalidCredentials, legacy_key
class FakeCursor:
def __init__(self, one=None, many=(), changed=1):
self.one = one
self.many = many
self.changed = changed
self.calls = []
def __enter__(self):
return self
def __exit__(self, *args):
return False
def execute(self, sql, parameters=None):
self.calls.append((" ".join(sql.split()), parameters))
return self.changed
def fetchone(self):
return self.one
def fetchall(self):
return self.many
class FakeConnection:
def __init__(self, cursor):
self.fake_cursor = cursor
self.commits = 0
self.rollbacks = 0
self.closed = 0
def cursor(self):
return self.fake_cursor
def commit(self):
self.commits += 1
def rollback(self):
self.rollbacks += 1
def close(self):
self.closed += 1
class FlowerCompatibilityTests(unittest.TestCase):
def make_vault(self, cursor):
connection = FakeConnection(cursor)
calls = []
def connect(**kwargs):
calls.append(kwargs)
return connection
return Flower("alice", "legacy_pwd", connect), connection, calls
def test_legacy_key_derivation_is_unchanged(self):
self.assertEqual(legacy_key("black"), "blackblackblackblackblackblackblackblackbla=")
def test_legacy_ciphertext_round_trip(self):
vault, _, _ = self.make_vault(FakeCursor())
token = vault.encode("existing database secret")
self.assertEqual(vault.decode(token), "existing database secret")
def test_existing_row_is_decrypted_without_schema_changes(self):
seed, _, _ = self.make_vault(FakeCursor())
row = (seed.encode("secret"), seed.encode("login"), seed.encode("Alice"))
cursor = FakeCursor(one=row)
vault, _, calls = self.make_vault(cursor)
item = vault.one("Acme", "2024-02-03 12:00:00")
self.assertEqual(item["mySecret"], "secret")
self.assertEqual(item["myID"], "login")
self.assertEqual(item["myName"], "Alice")
self.assertEqual(
cursor.calls[0][1], ("Acme", "2024-02-03 12:00:00")
)
self.assertEqual(calls[0]["user"], "alice_")
self.assertEqual(calls[0]["db"], "Flowers")
def test_writes_use_parameters_and_existing_columns(self):
cursor = FakeCursor()
vault, connection, _ = self.make_vault(cursor)
hostile_label = 'Acme"; DROP TABLE alice_; --'
result = vault.add(hostile_label, "Alice", "login", "secret")
sql, parameters = cursor.calls[0]
self.assertIn("organization, myID, myName, mySecret, deleted", sql)
self.assertNotIn(hostile_label, sql)
self.assertEqual(parameters[0], hostile_label)
self.assertEqual(result["dateCreated"], "STORED")
self.assertEqual(connection.commits, 1)
def test_latest_active_list_keeps_legacy_query_semantics(self):
seed, _, _ = self.make_vault(FakeCursor())
cursor = FakeCursor(
many=(("Acme", seed.encode("login"), "2024-02-03 12:00:00"),)
)
vault, _, _ = self.make_vault(cursor)
items = vault.all()
self.assertEqual(items[0]["organization"], "Acme")
self.assertEqual(items[0]["myID"], "login")
self.assertIn("MAX(dateCreated)", cursor.calls[0][0])
self.assertEqual(cursor.calls[0][1], (0,))
def test_unsafe_table_identifier_is_rejected(self):
with self.assertRaises(InvalidCredentials):
Flower("alice`; DROP DATABASE Flowers", "legacy_pwd")
if __name__ == "__main__":
unittest.main()