Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c27556b3b8 | ||
|
|
cafec256b5 | ||
|
|
897477bec9 | ||
|
|
73380b0c07 | ||
|
|
d642e0e2b8 | ||
|
|
ab82ecd306 | ||
|
|
882bf0d409 | ||
|
|
4a5ebc726e | ||
|
|
399f539b4f | ||
|
|
f9dc885937 | ||
|
|
9c726e49d4 |
@@ -0,0 +1,11 @@
|
||||
__pycache__/AccessControl.cpython-314.pyc
|
||||
__pycache__/Config.cpython-314.pyc
|
||||
__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
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
def decode(self, s):
|
||||
if len(s)==0:
|
||||
return ''
|
||||
return self.fernet.decrypt(s.encode()).decode()
|
||||
|
||||
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):
|
||||
|
||||
long_newpwd = (new_pwd*10)[:43]+"="
|
||||
self.new_fernet = Fernet(bytes(long_newpwd, 'ascii'))
|
||||
|
||||
def new_encode(s):
|
||||
if len(s)==0:
|
||||
return ''
|
||||
return self.new_fernet.encrypt(s.encode()).decode()
|
||||
|
||||
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, long_newpwd)
|
||||
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
|
||||
@contextmanager
|
||||
def _connection(self) -> Iterator[object]:
|
||||
connection = 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
|
||||
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 con:
|
||||
con.close()
|
||||
return result, data
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
|
||||
def encode(self, value: str) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
return self.fernet.encrypt(value.encode("utf-8")).decode("ascii")
|
||||
|
||||
def do_db_1row(self, sql):
|
||||
Log.debug('1row sql: '+sql)
|
||||
result = 0
|
||||
con = None
|
||||
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:
|
||||
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 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 do_db_allrows(self, sql):
|
||||
Log.debug('all rows sql: '+sql)
|
||||
result = 0
|
||||
con = None
|
||||
def numberOfEntries(self) -> int:
|
||||
"""Compatibility method used by older clients and scripts."""
|
||||
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 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
|
||||
|
||||
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)
|
||||
|
||||
sql = f"INSERT INTO {self._table} ({columns}) VALUES ({placeholders})"
|
||||
self._execute_write(sql, values)
|
||||
return self._item(organization, "STORED", myid, myname, secret)
|
||||
|
||||
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
|
||||
]
|
||||
|
||||
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 --")
|
||||
|
||||
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"))
|
||||
|
||||
try:
|
||||
with self._connection() as connection:
|
||||
try:
|
||||
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 _execute_write(self, sql: str, parameters) -> int:
|
||||
with self._connection() as connection:
|
||||
try:
|
||||
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]+"="
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
except StorageError as exc:
|
||||
Log.info(f"Could not create vault: {exc}")
|
||||
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)
|
||||
def flush_privs(self) -> int:
|
||||
try:
|
||||
self._execute_write("FLUSH PRIVILEGES", ())
|
||||
return 1
|
||||
except StorageError:
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("this is a library, sorry dude")
|
||||
if __name__ == "__main__":
|
||||
print("This module is a library.")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,30 +1,327 @@
|
||||
FLowers
|
||||
-------
|
||||
# Flowers
|
||||
|
||||
The current code base is designed to run undert a waitress server as a backend to apache
|
||||
The app should be called as http:// ... /flowers (so as a subfolder of the domain)
|
||||
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.
|
||||
|
||||
to install the env:
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install flask waitress fernet pymysql pytz
|
||||
## Existing database compatibility
|
||||
|
||||
No data migration is required. The rewritten application continues to use:
|
||||
|
||||
install apache and activate proxy:
|
||||
a2enmod proxy
|
||||
a2enmod proxy_http
|
||||
- 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.
|
||||
|
||||
and put the following in the site...conf file:
|
||||
ProxyPass "/flowers" "http://127.0.0.1:5012/flowers"
|
||||
ProxyPassReverse "/flowers" "http://127.0.0.1:5012/flowers"
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## What changed
|
||||
|
||||
# as root in mysql
|
||||
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;
|
||||
- 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.
|
||||
|
||||
start waitress:
|
||||
waitress-serve --threads=6 --host=127.0.0.1 --port=5012 --call flowers:create_app
|
||||
## Requirements
|
||||
|
||||
- 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 -r requirements.txt
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Local secrets are loaded from the git-ignored `secrets.yaml` file:
|
||||
|
||||
```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 '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;
|
||||
```
|
||||
|
||||
Put the same value in `database.admin_password` in `secrets.yaml` before starting Flowers.
|
||||
|
||||
## Run locally
|
||||
|
||||
Development:
|
||||
|
||||
```bash
|
||||
python flowers.py
|
||||
```
|
||||
|
||||
To exercise the production WSGI server locally:
|
||||
|
||||
```bash
|
||||
gunicorn --workers 1 --threads 6 --bind 127.0.0.1:5012 'flowers:create_app()'
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Deploy with Nginx, Gunicorn, and systemd
|
||||
|
||||
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.
|
||||
|
||||
### 1. Install the application
|
||||
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
### 2. Configure secrets and the environment
|
||||
|
||||
Keep deployment secrets outside the repository:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Create the writable locations referenced above:
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0750 -o flowers -g flowers /var/lib/flowers
|
||||
sudo install -d -m 0750 -o flowers -g flowers /var/log/flowers
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### 3. Create the systemd service
|
||||
|
||||
Create `/etc/systemd/system/flowers.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Flowers credential vault
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[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
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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']))
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -1,317 +0,0 @@
|
||||
"https://en.lespassionnesduvin.com/","2023-07-23 15:05:32","janine@demaat.info","","HELLO2023&$",
|
||||
"Www.kaartje2go.nl","2023-05-22 20:13:29","janine@demaat.info","","Kkart2211zs#",
|
||||
"Ignace tel ontgrendelen","2023-04-30 19:17:14","janine@demaat.info","","007662",
|
||||
"www.almeerplant.nl","2023-04-17 20:00:44","janine@demaat.info","kaart nummer 00089277","TISmeWHAT!@",
|
||||
"www.spasereen.nl","2023-04-08 08:38:59","janine@demaat.info",""," Massage Sport 25 min. Start",
|
||||
"https://www.america-today.com","2023-03-28 15:38:35","janine@demaat.info","","9yrtaqSXRUhx5Wm",
|
||||
"komoot.nl","2023-03-08 07:55:02","janine@demaat.info","","K20k23@#&",
|
||||
"Promedico.topdesk.net zelfservice portaal selfservice ","2023-03-01 13:11:37","jdmaat","","Pro2020!",
|
||||
"www.topgear.nl","2023-02-14 09:57:39","janine@demaat.info","","tttopGEAR#@",
|
||||
"https://loket.loket.nl werkgevers loket","2023-02-02 14:52:33","janine@demaat.info","","Welkom01",
|
||||
"https://gegevensportaal.belastingdienst.nl","2023-02-02 09:25:35","UBD002281","","Muiden2023!!",
|
||||
"www.eherkenning.nl Reconi","2023-01-26 17:23:34","janine@demaat.info","","Muiden2023#@!",
|
||||
"www.eherkenning.nl","2023-01-26 16:57:57","janine@demaat.info","","Muiden2023#@!",
|
||||
"https://waarneemapp.nl/companies/4708/schedules/openshifts","2023-01-26 15:46:15","janine@demaat.info","","HASamen2019",
|
||||
"Loonheffing praktijk Belastingdienst Postbus 8738 4820 BA BREDA","2023-01-18 17:02:51","janine@demaat.info","","857592683L02",
|
||||
"Bureaublad waarnemer","2023-01-11 07:35:31","waarnemer ","","Welkom2023",
|
||||
"nationale nederlanden nn.nl --&amp;gt; ZAKELIJK!","2023-01-09 16:27:05","ci4000025776","ci4000025776","NAT2023Burg15A",
|
||||
"www.allesvoordesauna.nl","2023-01-08 22:25:54","janine@demaat.info","","SELdeVie202367",
|
||||
"Www.mijngezondheidsmeter.nl","2023-01-03 06:20:53","janine@demaat.info","","PgOin2023@#!",
|
||||
"https://www.milieubarometer.nl/csr/o/zhjws/i/88566/y1/n/0/","2022-12-29 18:37:12","v_verstappen@hotmail.com","","Burgemeester15A",
|
||||
"www.asr.nl","2022-12-20 21:26:06","janine@demaat.info","","6497",
|
||||
"www.debevlogenhuisarts.nl","2022-12-01 14:38:56","janine@demaat.info","","FlyFree2023#!",
|
||||
"Https:/portaal.zorgvandezaak.nl/login","2022-11-25 11:13:42","Huisartsdemaat@ezorg.nl","","ZZZorg2023%!",
|
||||
"www.gowah.nl","2022-11-20 21:45:22","janine@demaat.info","","LeD$&2022!",
|
||||
"Www.123led.nl","2022-11-20 16:28:41","janine@demaat.info","","LeD$&2022!",
|
||||
"www.uziregister.nl","2022-11-03 13:37:36","janine@demaat.info","Intrek VwuS5neALLc8","Pin 283357 puk 231958 ",
|
||||
"www.coolblue.com","2022-10-23 13:20:42","janine@demaat.info","","B2v-EPG-Cri-uu",
|
||||
"www.FMS.nl","2022-10-09 09:54:32","janine@demaat.info ","","FMSricht202144",
|
||||
"Digitalis NHG rx","2022-10-04 21:35:30","v_verstappen@hotmail.com","","Burgemeester15a",
|
||||
"www.1reaal.nl","2022-10-04 15:45:57","janine@demaat.info","","REa!@33X",
|
||||
"MedGemak+","2022-10-04 08:45:12","Janine","","64971",
|
||||
"ignaceontgrendelingtelefoon","2022-09-11 09:11:13","janine@demaat.info","","070662",
|
||||
"Inlog praktijk start scherm bureaublad desktop","2023-07-25 10:35:26","janine@demaat.info","","HUISarts67!@#$%!!!!",
|
||||
"Dpg.nl","2022-07-12 16:52:54","janine@demaat.info","","DpppG2021#!",
|
||||
"transferpunt-go.nl","2022-07-12 16:15:41","JdeMaat","","Muiden2022oke",
|
||||
"www.arcusit.itclientportal.com","2022-07-08 12:27:28","Huisartsdemaat@ezorg.nl","","MuidenGez!!",
|
||||
"Uber","2022-06-20 19:02:51","janine@demaat.info","","ZzGg56&#",
|
||||
"https://mijnoproep.sanquin.nl/","2022-06-15 16:07:04","janine@demaat.info","","SanQUIN2022&##",
|
||||
"www.netflix.com","2022-06-03 18:06:00","ignace.suy@gmail.com","","querijnnf",
|
||||
"GAIA https://www.pe-online.org/SPE001_PR_Inloggen.aspx?taalID=&Calenda","2022-05-26 13:07:07","BIG-19042868401","","GaIA2022$#",
|
||||
"Moby parkeren","2022-05-08 08:48:19","janine@demaat.info","","MoBy2022$$",
|
||||
"Www.Beowulf.nl","2022-05-01 18:40:38","janine@demaat.info","","BEoAlm2022!",
|
||||
"Discord","2022-04-27 09:53:37","janinedemaat","janine@demaat.info","Pompen2022#",
|
||||
"Inlog praktijk startscherm bureaublad","2022-11-25 07:01:05","janine@demaat.info","","HUISarts67!@#$%!!",
|
||||
"ABN Amro Gold Creditcard 64971","2022-03-02 21:38:21","JDEMAAT1967","1381","creditin2015isnotBAD@",
|
||||
"www.light in the box","2022-02-24 16:53:54","janine@demaat.info","","R9TC8cS",
|
||||
"https://www.gowah.nl/default.htm","2022-02-22 15:13:00","janine@demaat.info","","GGowah3342&#",
|
||||
"Medfeed","2022-02-20 15:50:29","janine@demaat.info","","ME21&&#@",
|
||||
"https://www.neleman.org","2022-02-17 12:06:37","janine@demaat.info","","87NEel&&&",
|
||||
"www.gocards.nl","2022-02-03 08:24:40","Janinedm","","44554xq!",
|
||||
"Www.bezorgdehuisartsen.nl","2022-01-31 06:06:06","janine@demaat.info","","Bezorgdin2022!?&",
|
||||
"Cees.bezorgdehuisartsen.nl","2022-01-31 06:05:40","janine@demaat.info","","Bezorgdin2022!?&",
|
||||
"www.hkvi.nl","2022-01-28 15:00:34","Huisartsdemaat@ezorg.nl","","HuisartsvanMuiden2021!",
|
||||
"IMDB","2022-01-01 20:05:41","janine@demaat.info","","IiMmDdBB2021!?",
|
||||
"www.greetz.com","2021-12-10 17:22:32","janine@demaat.info","","GRRR2019&@ha",
|
||||
"Administratiehuisartsvanmuiden@ezorg.nl","2021-12-02 14:53:33","janine@demaat.info","","LiesKa80",
|
||||
"Www.vasco.nl","2021-11-28 11:36:50","janine@demaat.info","","RivmVasco2921## 64971",
|
||||
"Inlog praktijk start scherm bureaublad","2022-07-26 12:58:56","janine@demaat.info","","HUISarts67!@#$%!",
|
||||
"www.henryschein.nl","2021-10-26 08:10:21","janine@demaat.info","","HENry2021@@",
|
||||
"www.denieuwebibliotheek.nl","2021-10-24 19:29:11","janinedemaat","","BiBlio2121!",
|
||||
"www.wehkamp.nl","2021-10-17 07:09:50","janine@demaat.info","","WEH2021k!!",
|
||||
"Www.doecksen.nl","2021-10-13 21:04:21","janine@demaat.info","","2021Doecksen&&",
|
||||
"Www.skge.nl","2021-10-13 16:25:10","janine@demaat.info","","VanKlacht2021$#",
|
||||
"Spar.nl","2021-10-10 17:58:07","janine@demaat.info","","SpAr67$#!",
|
||||
"Hopin.com","2021-10-05 16:44:07","janine@demaat.info","","HOPhop2021%$",
|
||||
"Microsoft Teams.nl","2021-10-03 11:34:47","janine@demaat.info","","Xander@2",
|
||||
"Kinepolis.nl bioscoop","2021-10-03 07:36:50","janine@demaat.info","","AlmKin67$$",
|
||||
"Stocard.com","2021-09-30 11:15:26","janine@demaat.info","","1112",
|
||||
"DHL.nl app","2021-09-26 21:19:04","janine@demaat.info","","@3daH-55",
|
||||
"Pharmeon beheer pagina","2022-07-12 13:26:55","assistenteverstappen@ezorg.nl","","Muiden2022!",
|
||||
"persgroep.beleefklassiek.nl","2021-09-18 08:24:54","Janine.Maat.978","","7fb5a18ca6",
|
||||
"https://www.familienet.nl/client/katja-suy-linders/","2021-09-12 10:54:13","janine@demaat.info","","KatjaMOERG2020!",
|
||||
"Bureaublad praktijk waarnemer en assistentes","2021-09-10 13:59:14","Waarnemer","","Muiden2021!",
|
||||
"Www.picknick.nl","2021-08-26 14:31:01","janine@demaat.info","","vooruitmetdegeit2021!",
|
||||
"Promedico dongel wrnarts","2021-07-30 06:17:52","wrnarts","","4444",
|
||||
"Www.paul&pitalarm.nl","2021-07-22 07:59:35","janine@demaat.info","Janine","102127",
|
||||
"www.raamdecoratie.com","2021-07-04 17:38:04","janine@demaat.info","","r2021aam@&",
|
||||
"https://www.supersaas.nl/schedule/login/hartforherAlmere/hart_for_her_","2021-06-21 18:44:57","janine@demaat.info","","sport2021!!",
|
||||
"Vecozo persoonlijk certificaat 30000000252756","2023-06-21 10:26:24","JdeMaat","","2023Muiden!",
|
||||
"www.ssfh.nl stichting sociaal fonds huisartsenzorg","2021-05-18 15:20:36","janine@demaat.info","","SSfh2020!9",
|
||||
"https://open.viadesk.com/do/portal","2021-05-11 17:26:35","janine@demaat.info","","OPEN2020Janine$#",
|
||||
"www.natuurhuisje.nl","2021-05-11 05:03:54","janine@demaat.info","","NAtuurr2021!",
|
||||
"Promedico.topdesk.net zelfservice portaal","2021-05-02 18:03:37","jdmaat","","Pro2020!",
|
||||
"www.tuinland.nl","2021-04-24 12:05:21","janine@demaat.info","","T@uinland!2",
|
||||
"https://www.teunstuinposters.nl/","2021-03-28 10:10:53","janine@demaat.info","","857058d8",
|
||||
"Owncloud https://www.suy.nu/cloud","2021-03-26 20:27:19","janine","","6767 pincode, xanderj2",
|
||||
"sparkmail inlog mijndomijn janine@demaat.info","2021-05-03 20:03:00","janine@demaat.info","","Xander^66Suy",
|
||||
"mail inlog mijndomijn janine@demaat.info","2021-05-03 20:03:38","janine@demaat.info","","Xander^66Suy",
|
||||
"Www.fagron.nl","2021-03-15 14:45:28","janine@demaat.info","","FGRN2021#@",
|
||||
"financieelhuisartsvanmuiden@ezorg.nl webmail","2021-03-14 16:09:59","financieelhuisartsvanmuiden@ezorg.nl","","kr39S5q0lonc",
|
||||
"huisartsvanmuiden@ezorg.nl webmail ","2021-03-14 16:08:45","huisartsvanmuiden@ezorg.nl","","Ywlz9SWyuOLF",
|
||||
"https://online.loket.nl/vso.prd.loket werkgevers loket","2021-03-11 15:21:10","janine@demaat.info","","Welkom01",
|
||||
"Medi-access","2021-02-28 17:08:18","janine@demaat.info","","MeDI2021!Acc!$",
|
||||
"intershift","2023-02-12 11:22:35","janine@demaat.info","","XanderJ2!",
|
||||
"ABN Amro Gold Creditcard","2021-02-18 10:12:13","JDEMAAT1967","1381","creditin2015isnotBAD@",
|
||||
"ABN NL65ABNA0247117560 pas 221 ","2021-02-14 21:26:34","janine@demaat.info","","pin 6783",
|
||||
"ABN NL53ABNA0247355232 pas 441","2021-02-14 21:18:26","janine@demaat.info","","2921",
|
||||
"Geneeskunde boek.nl","2021-02-07 13:30:10","janine@demaat.info","","BSl2021xj!",
|
||||
"Zorgdomein.nl","2023-06-08 19:39:55","Huisartsdemaat@ezorg.nl",""," ZOrg2021!!!",
|
||||
"Gast guest login","2021-02-01 21:49:20","Legio scipio","","althome_2019*FF",
|
||||
"Login.WeSeeDo.nl","2021-02-01 08:20:15","Huisartsdemaat@ezorg.nl","","We2020SEE!",
|
||||
"www.speld.nl","2021-01-28 15:42:03","janine@demaat.info","","Sspeld2021!!",
|
||||
"Gold card","2021-01-25 18:42:58","janine@demaat.info","5341260091362308 425","2021secabn (3d security code)",
|
||||
"Signal","2021-01-17 10:23:14","janine@demaat.info","","6767",
|
||||
"Amazon","2021-01-14 23:06:47","janine@demaat.info","","AMAzing2121&!",
|
||||
"Amazon prime","2021-01-14 22:12:51","Ignace.suy@gmail.com","","querijnAP",
|
||||
"https://landelijkeleeromgeving.nspoh.nl/local/nspoh_register/signup.ph","2021-01-14 15:52:22","janine@demaat.info","","CoViD2019@#",
|
||||
"Stumpel","2021-01-14 10:37:33","janine@demaat.info","","ST2021u$$",
|
||||
"beheer.pharmeon.nl/Inloggen/app/","2021-01-14 09:00:05","v_verstappen@hotmail.com","","Burgemeester15a!",
|
||||
"www.staatsloterijl.nl","2021-01-10 18:17:04","janine@demaat.info","","ST@aat!@2!35",
|
||||
"Leeg worddocument","2021-01-07 10:15:58","janine@demaat.info","Op de Fschijf","Hierinstaanallewachtwoorden",
|
||||
"Huisarts.mediq.nl/inloggen Mediq voorschrijfportaal hulpmiddelen","2020-12-21 07:39:17","MEDIQpatientdeMaat","","Mediq20211398",
|
||||
"Mediq voorschrijfportaal hulpmiddelen","2020-12-18 10:10:24","Huisartsdemaat@ezorg.nl","","MEDIQpatientdeMaat",
|
||||
"sum-up app","2020-11-13 11:59:42","v_verstappen@hotmail.com","","Burgemeester1398BE!",
|
||||
"https://www.dokterandrebeautyworld.nl/","2020-11-09 22:14:11","janine@demaat.info","","##!2020jm!",
|
||||
"Lanteltelefooninlogstoring","2020-11-02 11:36:49","janine@demaat.info toestel 16","16","3316572479",
|
||||
"LHV panel","2020-10-30 19:05:34","janine@demaat.info","","LHv2222#@",
|
||||
"Mcc gooi en Vechtstreek","2020-10-29 15:36:30","janine@demaat.info","","McC2019xj",
|
||||
"Flitsmeister","2020-10-17 10:53:17","janine@demaat.info","","Flitzz!",
|
||||
"Weesper nieuws","2020-10-16 20:29:56","janine@demaat.info","","WEEsper2020&#",
|
||||
"Weesper nieuwa","2020-10-16 20:29:32","janine@demaat.info","","WEEsper2020&#",
|
||||
"Natuurmonumenten","2020-10-15 13:37:03","janine@demaat.info","","3996486 lidnummer",
|
||||
"https://www.bambooimport.com","2020-10-01 20:22:00","janine@demaat.info","","Bbambooo2020!",
|
||||
"onlinebibliotheek.nl","2020-09-03 13:54:23","20060004773818","","BIEB2020@!67e",
|
||||
"Mijn.euronature.nl","2020-09-03 08:34:35","janine@demaat.info","","rGPqNciD",
|
||||
"DASHBOARD open","2020-08-22 11:54:04","janine@demaat.info","","DASH2020@#",
|
||||
"Van Haren","2020-08-22 10:54:27","janine@demaat.info","","V5e11wONm",
|
||||
"Zoom janine@demaat.info","2020-08-21 12:55:46","janine@demaat.info","","ZooM@#2020!",
|
||||
"decathlon","2020-08-09 09:19:57","janine@demaat.info","","DE2020LL!",
|
||||
"Faxhuisartsvanmuiden@ezorg.nl","2020-08-05 10:38:10","Faxhuisartsvanmuiden@ezorg.nl","","Welkom2020!",
|
||||
"Promedico.topdesk.net","2020-07-28 11:32:25","jdmaat","","Pro2020!",
|
||||
"www.voedingscentrum.nl","2020-07-19 15:40:23","janine@demaat.info","","Voe2020DD@",
|
||||
"EZORG webmail inloggen assistenteverstappen@ezorg.nl","2020-07-14 06:17:29","assistenteverstappen@ezorg.nl","","Muiden2020",
|
||||
"Bergman clinics app","2020-07-13 14:00:55","janine@demaat.info","","676764",
|
||||
"https://mijnhypotheek.lloydsbank.nl/hypotheek","2020-07-09 12:33:55","janine@demaat.info","","LLOY2019%#!",
|
||||
"Flevo-landschap.nl","2020-06-30 21:00:59","janine@demaat.info","","Flevo2020!@",
|
||||
"Rhogo.qarebase.nl/account/login","2020-06-30 17:50:46","janine@demaat.info","","Mcix+Q69",
|
||||
"Zilveren kruis app persoonlijk","2020-06-28 11:42:37","janine@demaat.info","","64971",
|
||||
"Https://Promedico.topdesk.net","2020-06-19 14:58:02","jdmaat","","Muiden2020",
|
||||
"Richtlijnendatabase.nl","2020-06-19 13:34:12","Huisartsdemaat@ezorg.nl","","MedSpeFed2020",
|
||||
"EZORG webmail inloggen","2020-05-13 10:21:33","assistentedemaat@ezorg.nl","","Muiden2020",
|
||||
"Mediq.nl","2020-05-04 05:47:30","Huisartsdemaat@ezorg.nl","","MedBis2020!",
|
||||
"NPA Vincent","2021-08-03 08:33:23","v_verstappen@hotmail.com","","npaweb1398BE",
|
||||
"www.avaya.com workplace","2020-04-30 13:25:13","Toestel 16","","Muiden1398",
|
||||
"www.avaya.com","2020-04-30 13:21:50","Toestel 16","","Muiden1398",
|
||||
"NPAweb.nl","2022-06-13 13:33:10","janine@demaat.info","","NPA2020Muiden1!!!",
|
||||
"Groene Amsterdammer","2020-04-24 14:55:39","janine@demaat.info","","Groen22!",
|
||||
"Open","2020-04-23 16:36:21","janine@demaat.info","","OPEN2020Janine$#",
|
||||
"ISSUU","2020-04-13 17:02:19","janine@demaat.info","","ISSUU2020#@1",
|
||||
"nationale nederlanden nn.nl --&gt; ZAKELIJK!","2020-04-12 11:17:15","ci4000025776","v_verstappen@gmail.com","Burgemeester1398B&",
|
||||
"Https://covid19.u-diagnostics.com","2020-04-07 14:45:35","Huisartsdemaat@ezorg.nl","","GoFit2020#$2",
|
||||
"WeSeeDo","2020-03-30 10:17:36","Huisartsdemaat@ezorg.nl","","We2020SEE!",
|
||||
"Videoconsult.clickdoc.nl CGM videobellen","2020-03-27 09:39:39","jdemaat","jdemaat","CGMvideo2020$$",
|
||||
"Zoom","2020-03-25 14:30:20","Huisartsdemaat@ezorg.nl","","ZZZoom2020!",
|
||||
"Webmail.mijndomein.nl","2020-05-15 10:35:09","janine@demaat.info","","xanderJ2",
|
||||
"CGM videobellen","2020-03-23 14:50:03","jdemaat","","CGMvideo2020$$",
|
||||
"Siilo","2021-03-25 14:58:30","janine@demaat.info","janine@demaat.info","64971",
|
||||
"Wish","2020-03-22 10:28:33","janine@demaat.info","janine@demaat.info","WiSH2121#@",
|
||||
"Medischescholing.nl","2020-05-12 18:19:45","janine@demaat.info","janine@demaat.info"," MediCovid2020!",
|
||||
"MedGemak","2020-03-03 09:12:38","Janine","","64971",
|
||||
"Zorgmail POH GGZ","2020-02-27 11:53:47","500110871","","pjj4s-zb3s9-yzntx-62kvu-f2x7d",
|
||||
"Zorgmail Huisarts van Muiden","2020-02-27 11:52:52","500107607","","5wgza-ajpxv-h5j7j-2wpjm-28wny",
|
||||
"Zorgmail Verstappen","2020-02-27 11:51:52","500107252","","yup5s-sn62n-vmsvr-7wp68-j4ru7",
|
||||
"Zorgmail de Maat","2020-05-22 10:18:39","xa3bm-x6uak-zsmw4-6fsra-jbdh8","500013469","ZorgMaildemaat2020!",
|
||||
"De nieuwe bibliotheek","2020-02-20 19:42:42","JdeMaat","","BiebAlmere2020!",
|
||||
"Bureaublad praktijk waarnemer","2021-01-27 06:54:56","Waarnemer","","Welkom2021",
|
||||
"Waarneemapp","2019-12-28 11:29:57","janine@demaat.info","janine@demaat.info","HASamen2019",
|
||||
"www.nvve.nl","2019-12-21 15:26:45","janine@demaat.info","janine@demaat.info","MaatNVVE2019",
|
||||
"SNGP Griep","2021-10-11 12:21:09","0108030-01","janine@demaat.info","MUIDEN1398BE!!",
|
||||
"Computer praktijk Vincent","2020-02-26 13:45:33","Vincent","Vincent","Muiden2017!",
|
||||
"GhoGo.nl","2019-11-13 22:37:35","janine@demaat.info","janine@demaat.info","GhoGo2019#$2",
|
||||
"PIM inloggen telefonie huisartsenpost","2019-11-10 09:05:22","janine@demaat.info","primair-bla-spreekkamer5 ( of een ander nummer)","Pr1m@1r!",
|
||||
"MX-5 Owners","2019-11-03 10:16:44","janine@demaat.info","janine@demaat.info","Owners445",
|
||||
"MX-5 Iwners","2019-11-03 10:15:53","janine@demaat.info","janine@demaat.info","Owners445",
|
||||
"Omzetbelasting nummer (ob-nummer)","2019-10-31 15:41:01","janine@demaat.info","janine@demaat.info","101394585B01",
|
||||
"BTW identificatienummer","2019-10-31 15:39:55","janine@demaat.info","janine@demaat.info","NL001387127B24",
|
||||
"paspoort Janine de Maat 17 05 1967","2019-10-24 19:03:50","Janine paspoort tot 16 11 2027","janine@demaat.info","NM87JHH81",
|
||||
"paspoort Ignace Marie Lucien Suy 12 11 1964","2019-10-24 19:02:34","janine@demaat.info","Ignace paspoort tot 17 10 2026","NT9DKR0C1",
|
||||
"Eetmeter","2019-10-17 15:41:41","janine@demaat.info","janine@demaat.info","Eet!Eet",
|
||||
"Body FX figure8","2019-09-26 16:40:09","janine@demaat.info","","2019janinenew",
|
||||
"Inlog praktijk start scherm","2021-07-14 05:54:56","janine@demaat.info","","HUISarts67!@#",
|
||||
"Lantel cloud","2020-11-10 06:53:35","Huisartsdemaat@ezorg.nl","Huisartsdemaat@ezorg.nl","LanTel2019@@##",
|
||||
"Lantel cliud","2019-08-14 15:26:51","Huisartsdemaat@ezorg.nl","Huisartsdemaat@ezorg.nl","LanTel2019@#3EZORG",
|
||||
"Zelfstroom zonnepanelen","2019-08-02 16:08:01","janine@demaat.info","","Zelf2019&&",
|
||||
"nationale nederlanden nn.nl --> ZAKELIJK!","2019-11-28 12:07:53","ci4000025776","huisartsverstappen@ezorg.nl","NAT2019Burg15A",
|
||||
"Bayer medischwijzer.nl","2019-07-08 08:08:48","janine@demaat.info","","BaYer2019$$",
|
||||
"Google","2020-03-22 13:46:51","janine.de.maat@gmail.com","","Xanderj2!",
|
||||
"Vecozo","2023-06-02 12:27:06","Systeemcertificaat","MUIDEN2021!@","40000000127056pin MUIDEN2023!!",
|
||||
"IC Taurus zelfservice portaal","2019-06-03 07:24:52","Huisartsverstappen@ezorg.nl","","W3Lkom@19!!",
|
||||
"Vakantie piraten","2019-05-30 17:47:29","janine@demaat.info","janinepir","Vak2019Piraten#",
|
||||
"Pharmeon","2021-09-07 06:40:54","assistenteverstappen@ezorg.nl","","2DEjTsFsDtw1",
|
||||
"Makro","2019-05-28 15:04:16","janine@demaat.info","","Makro&&2019",
|
||||
"de la mar","2019-05-25 11:54:42","janine@demaat.info","","altijdLEUK2019!",
|
||||
"CBOARDS caresharing","2019-05-27 10:48:47","Huisartsdemaat@ezorg.nl","Huisartsdemaat@ezorg.nl","CBOARDS janine 2019 pin 64971",
|
||||
"Instituut verantwoord medicijn gebruik IVM","2019-05-16 14:54:02","janine@demaat.info","","IvM2019IvM!",
|
||||
"Digitalis","2019-05-08 14:05:42","v_verstappen@hotmail.com","","Burgemeester15a",
|
||||
"Parool","2020-10-29 20:58:54","janine@demaat.info","","Parool1112@@",
|
||||
"Mijn lcr inloggen","2019-04-29 21:54:01","VERSTAPPENV01","","Burgemeester15a",
|
||||
"Marktplaats.nl","2019-04-28 14:04:39","janine@demaat.info","","MaRkTplaats2019$$#",
|
||||
"Hopir","2019-04-25 08:25:58","janine@demaat.info","","Noorder2019",
|
||||
"Secundum WIFI guests","2019-04-19 17:14:09","Secundum","Secundum","HH*20190405gg",
|
||||
"Woonveilig app","2020-12-04 12:32:41","janinedemaat","","G7^Fddsr43#3 of pincode 6497",
|
||||
"EHerkenning Cream Reconi de Maat","2019-04-05 14:35:36","janine@demaat.info","","64971",
|
||||
"https://login.ns.nl/login NS","2021-05-24 13:21:27","janine@demaat.info","","NS2019ns@#!",
|
||||
"Hudson Bay","2019-04-04 17:49:20","janine@demaat.info","","Hud2020Son@#$!",
|
||||
"primus internet","2019-04-04 09:19:52","althome_2019*FF","","althome_2019*FF",
|
||||
"Notaris unie 2","2019-03-25 19:28:01","janine@demaat.info","","XanderV20",
|
||||
"Notaris unie","2019-03-25 19:19:31","Ignace.suy@gmail.com","","querijnv20",
|
||||
"ParkMobile","2019-03-10 13:29:11","janine@demaat.info","","piaivtd",
|
||||
"ASN bank","2020-12-02 19:22:25","janineasnbank","64971","asn!janine67DEMAAT",
|
||||
"Overheid berichtenbox app","2019-02-14 18:48:23","Janine","Janine","64972",
|
||||
"Dongel janine werk","2019-02-14 16:16:25","jdmaat","","1112",
|
||||
"Dongel Janine thuus","2019-02-14 16:15:36","JdeMaat2","","2228",
|
||||
"Dongel van co","2019-02-14 16:14:58","comuiden","","3333",
|
||||
"Post.nl","2019-02-07 17:48:39","janine@demaat.info","","PosT2019@#6",
|
||||
"Social deal","2019-02-07 10:14:33","janine@demaat.info","","SoCial2020$#$",
|
||||
"FTO Online","2019-02-06 21:19:26","janine@demaat.info","","FtO2019#$6",
|
||||
"Wachtwoorden praktijk c schijf facilitair wat wij belangrijk vinden","2019-02-06 15:33:13","Praktijk login c schijf","","Is er niet",
|
||||
"Plex","2019-02-03 21:44:47","janine.de.maat@gmail.com","","Querijnp7",
|
||||
"cloud https://www.suy.nu/cloud","2020-04-28 21:21:10","janine@demaat.info","","6767 pincode xanderj2",
|
||||
"Werkgevers Loket","2020-03-09 13:28:12","janine@demaat.info","","Welkom01",
|
||||
"DIGID","2020-05-01 14:10:28","JanineDigid","","Eninaj!2 67941",
|
||||
"EZORG inloggen webmail: webmail.ezorg.nl ","2019-01-03 12:58:56","huisartsdemaat@ezorg.nl","","EZORG2017$%",
|
||||
"nationale nederlanden nn.nl --> ZAKELIJK!","2019-01-03 12:48:28","CI4000025776","","NN2017HuisartsvanMuiden",
|
||||
"Twiiter","2018-12-31 18:29:55","janine@demaat.info","","twetenisfun2018&",
|
||||
"DIGIPAS Promedico 1112","2018-12-24 11:05:31","jdmaat","","1112",
|
||||
"DIGIPAS Dongel Promedico 2228","2018-12-24 11:05:02","JdeMaat2","","2228",
|
||||
"Primair Huisartsenpost","2020-08-23 10:38:30","janine@demaat.info","jdemaat","xanderjprimair",
|
||||
"HAWEB","2022-12-14 06:05:30","janine@demaat.info","","HaWeb#2023&!",
|
||||
"123INKT","2018-12-24 10:59:39","janine@demaat.info","","janine2012#123inkt",
|
||||
"Nespresso","2018-12-24 10:52:06","janine@demaat.info","","nespresso12#",
|
||||
"Herensokken.nl","2018-12-24 10:50:22","janine@demaat.info","","herensokken2013HAHA",
|
||||
"GAIA https://www.pe-online.org/SPE001_PR_Inloggen.aspx?taalID=&Calenda","2018-12-24 10:47:57","BIG-19042868401","","weet ik niet",
|
||||
"Wordfeut","2018-12-24 10:44:01","janinewordfeud67","","hallo67",
|
||||
"Dropbox","2018-12-24 10:42:51","janine@demaat.info","","janinedropbox2014#$",
|
||||
"NTVG inloggen","2018-12-24 10:34:45","jdemaat","","Ntvg2013demaat",
|
||||
"FACEBOOK Facebook","2018-12-24 10:31:28","janine@demaat.info","","faceb2015@!@",
|
||||
"ABN Amro Creditcard","2021-02-18 10:11:48","JDEMAAT1967","1381","creditin2015isnotBAD@",
|
||||
"Linda TV","2018-12-24 10:21:06","janine@demaat.info","","HalloLindaTV2015",
|
||||
"Toplenzen","2018-12-24 10:20:34","janine@demaat.info","","TopLenzen2015@#",
|
||||
"Museumkaart","2018-12-24 10:19:57","janine@demaat.info",""," Museum2016$%",
|
||||
"WDHMN","2018-12-24 10:19:28","jdemaat","","WDHMN2016$%",
|
||||
"Nextdoor","2019-11-14 17:22:23","janine@demaat.info","","NextDoor201954@",
|
||||
"Muidernieuws online lezen","2022-07-06 21:27:18","janine@demaat.info","","WEEsper2020&#",
|
||||
"Managemenstboek","2018-12-24 10:17:54","janine@demaat.info","","manage2016@#",
|
||||
"Volkskrant","2022-11-03 13:44:56","janine@demaat.info","","Nrcleuk!!!@",
|
||||
"Airmiles","2020-03-12 13:07:49","JDEMAAT","","airmiles2016HIEPHOI!",
|
||||
"Ticketveiling https://www.ticketveiling.nl","2022-03-25 19:15:17","jantje345","","ticket2022!)",
|
||||
"Bonprix","2020-10-15 19:37:23","janine@demaat.info","","Bbonie21!",
|
||||
"Vakantie Veiling","2018-12-24 10:14:12","janine@demaat.info","","vak2016veling@#",
|
||||
"Pinterest","2018-12-24 10:13:17","jappie","","pint@@@2016ABC",
|
||||
"Media Markt","2018-12-24 10:12:49","janine@demaat.info","","ongelovelijk2017#$%",
|
||||
"Wijgergangs Medical www.wijgergangsmedical.nl","2018-12-24 10:12:07","janine@demaat.info","","Medische2016$%",
|
||||
"Love For Rain https://www.loveforrain.nl/","2018-12-24 10:10:51","janine@demaat.info","","love2017welkom12",
|
||||
"KRUIDVAT","2018-12-24 10:09:51","janine@demaat.info","","kruidvat2016#$%",
|
||||
"Deen","2018-12-24 10:09:11","janine@demaat.info","","Deen2016$%6",
|
||||
"gall gall","2018-12-24 10:08:23","janine@demaat.info","","GALL2016gall@#",
|
||||
"Iens Seatme","2018-12-24 10:06:57","janine@demaat.info","","SeatMe2017$%!",
|
||||
"Tui Mijntui www.tui.nl/mijntui","2019-10-24 18:42:48","janine@demaat.info","janine@demaat.info","TuiBonaire2019!!",
|
||||
"Gmail inloggen","2018-12-24 10:04:26","janine.de.maat@gmail.com","","xanderj2",
|
||||
"ANWB","2022-12-06 15:02:56","JaninedeMaatANWB","janine@demaat.info","ANWB2017#$",
|
||||
"Appie Albert Heijn","2018-12-24 10:01:01","janine@demaat.info","","Appie2017Hallo!",
|
||||
"FTO https://medicijngebruik.nl/","2018-12-24 09:59:15","janine@demaat.info","","FTO%&%2017!",
|
||||
"Bol Com","2021-03-18 20:44:09","janine@demaat.info","","BolCC202123",
|
||||
"Joviene Lourens Massage","2018-12-17 21:59:48","Janine67","","Loure2017@#$",
|
||||
"Concertgebouw","2018-12-17 21:57:38","janine@demaat.info","","2017ConcertGeb$%",
|
||||
"Hema.nl","2019-08-25 10:51:14","janine@demaat.info","","HemaTheBest2019!",
|
||||
"koopjedeal","2018-12-17 21:55:23","janine@demaat.info","","koop2017je%^deal",
|
||||
"Health Investment https://www.lpx.nl/healthinvestment/login","2018-12-17 21:54:12","janine@demaat.info","","HealthInvest2017^%",
|
||||
"Medisch contact","2020-10-25 14:41:46","janine@demaat.info","","MeDisch2020@@",
|
||||
"VVAA","2018-12-17 21:49:51","013265","","VvAa2017%$#",
|
||||
"https://www.pil-nascholing.nl","2020-12-20 21:06:49","JaninePiL","","PIL2021Janinex!!",
|
||||
"Groupon","2018-12-17 21:44:02","janine@demaat.info","","Groupon2017top67",
|
||||
"Top Bloemen","2018-12-17 21:42:37","janine@demaat.info","","TipTop1967#$",
|
||||
"Albert Heijn bestellen thuisbezorgen prive","2018-12-17 21:42:16","janine@demaat.info","","Appie2017Hallo!",
|
||||
"Praxisdienst.nl","2018-12-17 21:40:06","janine@demaat.info","","Praxis2017Dienst#$",
|
||||
"Frascati","2018-12-17 21:39:29","janine@demaat.info","","fRxVtz",
|
||||
"Bijenkorf.nl","2018-12-17 21:36:43","janine@demaat.info","","BijBij2017Bij#$",
|
||||
"https://medicijngebruik.nl/ verandwoord medicijngebruik","2018-12-17 21:35:40","janine@demaat.info","","FTO%&%2017!",
|
||||
"https://branderwines.nl/wijnshop/customer/account/login/","2018-12-17 21:26:00","janine@demaat.info","","RRRSuU_K",
|
||||
"Mijn Promedico","2018-12-17 21:25:20","janine@demaat.info","","yI8xtB_E",
|
||||
"We Fashion","2018-12-17 21:22:09","janine@demaat.info","","WeFashion2018$%",
|
||||
"actie van de dag","2018-12-17 21:20:25","janine@demaat.info","","AcTie2018Van$%",
|
||||
"Geneeskundeboek.nl","2018-12-17 21:19:48","janine@demaat.info","","geneeskunde2017$%",
|
||||
"administratie Ignace en janine","2018-12-17 21:18:12","janine","","Xanderj",
|
||||
"https://www.daxtrio.nl/customer/account/login/ DAXTRIO","2021-05-02 20:43:22","janine@demaat.info","","DaX2021&#",
|
||||
"NPO start","2018-12-17 21:13:09","janine@demaat.info","","StartNPOHallo123",
|
||||
"Praktijkspiegel https://vektis.okta-emea.com/login/login.htm?fromURI=/","2023-06-14 19:18:40","01022281@vektis.nl","","Halh18@#18!xx!",
|
||||
"GHO GO https://www.ghogo.nl/wp-login.php","2018-12-17 20:56:29","jdemaat","","3PWdU5Nt31Ts)pjgWA#FP)5s ",
|
||||
"Soap Treatments","2019-07-03 15:56:53","janine@demaat.info","0624276531","HelloBotox!@",
|
||||
"https://www.ah.nl/kies-moment/bezorgen albert heijn","2018-12-17 20:52:09","huisartsdemaat@ezorg.nl","","AHis2018TOP",
|
||||
"Bel Centrale","2018-12-17 20:50:43","janine@demaat.info","","Bellen222!@",
|
||||
"Bohn Stafleu en van Loghum","2018-12-17 20:50:04","janine@demaat.info","","bsl2018Yes!",
|
||||
"https://www.myheritage.nl/","2018-12-17 20:48:52","janine@demaat.info","","AfKomst2018!@",
|
||||
"Huisarts Vandaag","2018-12-17 20:46:07","demaat","","VanDaag2018@#7",
|
||||
"Belastingdienst Janine de Maat","2018-12-16 15:29:47","NL0013176906","","HelpAangifteDoen2017$%",
|
||||
"Belastingdienst Huisarts van Muiden","2018-12-16 15:28:58","NL0050922414","","Huisarts15A$ViJa",
|
||||
"VIP live","2021-06-11 08:33:18","JdeMaat","","DeMaat1967#$7 pincode 64971",
|
||||
"KPN https://account.kpn.com/#/","2022-03-03 20:49:05","janine@demaat.info","Puk 31777541","64646 KPN2020ha!@",
|
||||
"Zilveren Kruis Verstappen","2021-12-21 12:22:04","400079996","","Burgemeester15a@#!",
|
||||
"Zilveren Kruis de Maat","2022-12-23 10:59:17","400079997","","ZilvKruis2021#$#!",
|
||||
"NHG.org","2018-12-16 15:13:50","janine@demaat.info","","2012HAweb@#",
|
||||
"LinkedIn","2018-12-17 22:01:13","janine@demaat.info","","LinkeIn2017#$%5",
|
||||
"Independer","2022-12-06 16:42:45","janine@demaat.info","","InDEPEND2018$%!",
|
||||
"https://www.centraalbeheer.nl/Paginas/default.aspx Centraal beheer","2018-11-10 22:18:32","janine2018BeHEER","","CenTRAAL2018@#",
|
||||
"Movir https://mijnmovir.nl/MyMovirTheme/NoPermission.aspx","2023-02-26 14:07:45","janine@demaat.info","","Mo2018Vir$%2!23",
|
||||
"https://mijn.belastingdienst.nl/ppa/ belastingdienst janine zakelijk","2018-11-10 22:12:34","NL0013176906","","HelpAangifteDoen2017$%",
|
||||
"https://www.etos.nl/ ETOS","2018-11-10 22:02:54","janine@demaat.info","","INetos2018!@",
|
||||
"https://www.ghogo.nl/wp-login.php GHOGO","2018-11-10 21:58:46","jdemaat","","3PWdU5Nt31Ts)pjgWA#FP)5s ",
|
||||
"www.doq.nl doq","2018-11-10 21:57:04","maatDOQ","","DOQ2017demaat67%!",
|
||||
"Spotify","2018-11-10 21:56:05","janinespotify2013","","SpoTy2018$%",
|
||||
"https://www.ah.nl/kies-moment/bezorgen/1398BE albert heijn","2018-11-10 21:52:31","huisartsdemaat@ezorg.nl","","AHis2018TOP",
|
||||
"www.belcentrale.nl","2018-11-10 21:50:37","janine@demaat.info","","Bellen222!@",
|
||||
"https://mijn.bsl.nl/nascholing","2018-11-10 21:50:02","janine@demaat.info","","bsl2018Yes!",
|
||||
"My Heritage","2018-11-10 21:47:58","janine@demaat.info","","AfKomst2018!@",
|
||||
"NVDA","2018-12-17 20:46:56","huisartsdemaat@ezorg.nl","","Huisartsvanmuiden2017!",
|
||||
|
||||
|
@@ -0,0 +1,2 @@
|
||||
-r requirements.txt
|
||||
pytest>=8.0,<10
|
||||
@@ -0,0 +1,6 @@
|
||||
Flask>=3.1,<4
|
||||
fernet>=1.0,<2
|
||||
gunicorn>=23.0,<24
|
||||
PyMySQL>=1.1,<3
|
||||
PyYAML>=6.0,<7
|
||||
|
||||
@@ -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"
|
||||
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -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);
|
||||
@@ -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"
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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>');
|
||||
}
|
||||
};
|
||||
})();
|
||||
}
|
||||
@@ -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__();
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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.*");
|
||||
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 86 B |
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 42 KiB |
@@ -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); }
|
||||
|
||||
.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; }
|
||||
|
||||
.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; }
|
||||
|
||||
.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); }
|
||||
|
||||
.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; }
|
||||
|
||||
.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; }
|
||||
}
|
||||
|
||||
tr.flowerlist a {
|
||||
color: inherit; /* blue colors for links too */
|
||||
text-decoration: inherit; /* no underline */
|
||||
}
|
||||
|
||||
td.white {
|
||||
color: #eee
|
||||
}
|
||||
|
||||
td.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.blue {
|
||||
color: #208dd6;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 89 B |
|
Before Width: | Height: | Size: 92 B |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 290 B |
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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+'¶meters='+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);
|
||||
}
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
After Width: | Height: | Size: 22 KiB |
@@ -1,75 +1,37 @@
|
||||
<!doctype html>
|
||||
<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" />
|
||||
|
||||
<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>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<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="{{ 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>
|
||||
</div>
|
||||
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
|
||||
<main class="page-shell">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>Private by design. Stored in your existing Flowers bouquet.</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -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">
|
||||
</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>
|
||||
{{ message }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script language="javascript">
|
||||
|
||||
</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">
|
||||
<button class="button ghost compact" type="submit">Cancel</button>
|
||||
</form>
|
||||
{% 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 %}
|
||||
|
||||
@@ -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 }}">
|
||||
</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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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 %}
|
||||
<section class="vault-header">
|
||||
<div>
|
||||
<h1>Your bouquet</h1>
|
||||
</div>
|
||||
<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="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">
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="entry-list" id="entry-list">
|
||||
{% for flower in flowers %}
|
||||
<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 %}
|
||||
</div>
|
||||
<p class="empty-state filtered-empty" id="filtered-empty" hidden>No entries match that search.</p>
|
||||
|
||||
<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>
|
||||
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;
|
||||
});
|
||||
filteredEmpty.hidden = visible !== 0 || rows.length === 0;
|
||||
}
|
||||
search.addEventListener('input', filterRows);
|
||||
filterRows();
|
||||
</script>
|
||||
|
||||
{% endblock content %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -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">
|
||||
</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>
|
||||
{{ message }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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')
|
||||
@@ -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');
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||