Compare commits

..

5 commits

Author SHA1 Message Date
Benjamin Renard
05a6c19264
Translate application's messages with english as default and add french translation 2024-09-22 18:39:11 +02:00
Benjamin Renard
28e6e167ee
Introduce some pre-commit hooks 2024-09-21 17:23:21 +02:00
Benjamin Renard
84716b4527
Implement server scases data synchronization 2024-09-21 17:07:52 +02:00
Benjamin Renard
c7796ac341
Introduce Alertify JS to replace myconfirm & native alert function 2024-09-15 10:17:22 +02:00
Benjamin Renard
81b902ea5d
Introduce EesyPHP framework 2024-01-30 18:40:00 +01:00
85 changed files with 11063 additions and 1729 deletions

6
.codespell-exclusions Normal file
View file

@ -0,0 +1,6 @@
.git
./static/lib
./vendor
./locales/*/LC_MESSAGES/*.po
./locales/*.js
./locales/*.pot

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
/config.local.yml
# Ignore vim temporary/backup files
*~
.*.swp
# Exclude composer installed libs
/vendor
/composer.lock
# Common UNIX user home directory files
/.bash*
/.vim*
/.composer
/.gitconfig
/.vim*
/.less*
/.ssh
/.phplint-cache

7
.phplint.yml Normal file
View file

@ -0,0 +1,7 @@
path: ./
jobs: 10
extensions:
- php
exclude:
- vendor
warning: true

33
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,33 @@
# Pre-commit hooks to run tests and ensure code is cleaned.
# See https://pre-commit.com for more information
---
repos:
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
- id: codespell
exclude: static/lib/|locales/.*\.js$|\.pot$
#args: ["--write-changes"]
exclude_types: [csv, json, pofile]
- repo: https://github.com/adrienverge/yamllint
rev: v1.32.0
hooks:
- id: yamllint
ignore: .github/
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
- repo: https://github.com/digitalpulp/pre-commit-php.git
rev: 1.4.0
hooks:
- id: php-stan
files: ^(?!example/).*\.(php)$
args: ["--configuration=phpstan.neon"]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-executables-have-shebangs
stages: [manual]
- id: check-json
exclude: (.vscode|.devcontainer)

2
.prettierignore Normal file
View file

@ -0,0 +1,2 @@
/static/lib/*
/locales/*

9
.yamllint.yaml Normal file
View file

@ -0,0 +1,9 @@
extends: default
ignore: |
static/lib/*
rules:
line-length:
max: 100
level: warning

7
bin/manage Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/php
<?php
use EesyPHP\Cli;
require realpath(dirname(__FILE__).'/..')."/includes/core.php";
Cli :: handle_args();

29
composer.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "brenard/mysc",
"description": "My Sweetcase",
"type": "project",
"require": {
"brenard/eesyphp": "dev-master"
},
"license": "GPL3",
"autoload": {
"psr-4": {
"MySC\\": "src/"
}
},
"authors": [
{
"name": "Benjamin Renard",
"email": "brenard@zionetrix.net"
}
],
"minimum-stability": "dev",
"config": {
"allow-plugins": {
"php-http/discovery": true
}
},
"require-dev": {
"phpstan/phpstan": "2.0.x-dev"
}
}

350
config.yml Normal file
View file

@ -0,0 +1,350 @@
# Public root URL
public_root_url: "/"
# Application root data directory
data_directory: "${root_directory_path}/data"
# Temporary files root directory
tmp_root_directory: "${data_directory}/tmp"
# Temporary uploading files directory (optional, default: PHP ini default)
upload_tmp_directory: ${tmp_root_directory}/uploading"
# Uploading file size limit (in bytes, example: 104857600 == 100Mb)
# (optional, default: PHP ini default)
upload_max_filesize: 104857600
# Main pagetitle
main_pagetitle: "Eesyphp"
# Debug Ajax request/response
debug_ajax: false
#
# Log configuration
#
log:
# Logs directory path
directory_path: "${data_directory}/logs"
# CLI log file path
cli_file_path: "${log.directory_path}/cli.log"
# Log file path
file_path: "${log.directory_path}/app.log"
# Log level (TRACE / DEBUG / INFO / WARNING / ERROR / FATAL)
level: "INFO"
# Log PHP errors levels (as specified to set_error_handler())
# Note: expected a list of PHP error level constants that will be resolved and combine
# with a bitwise operator. After the first value, the bitwise operator "|" (OR) or "^" (XOR)
# could be specified as contanst name prefix, otherwise the "&" (AND) operator will be used.
# The constant name could also be prefix with "~" or "!" (NOT) bitwise operator.
#
# Default:
# - In TRACE or DEBUG: E_ALL & ~E_STRICT
# - Otherwise: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
#log_php_errors_levels:
# - E_ALL
# - ~E_NOTICE
# - ~E_STRICT
# - ~E_DEPRECATED
#
# Sentry configuration
#
sentry:
# Sentry DSN
#dsn: null
# Log PHP errors in Sentry: list of errors types to logs
# See: https://www.php.net/manual/fr/errorfunc.constants.php
php_error_types:
- "E_ERROR"
- "E_PARSE"
- "E_CORE_ERROR"
- "E_COMPILE_ERROR"
- "E_USER_ERROR"
- "E_RECOVERABLE_ERROR"
- "E_DEPRECATED"
# Traces sample rate (between 0 and 1)
# Note: this parameter permit to determine how many transactions (=~ access) are traced and
# sent to Sentry, for instance, 0.2 meen that 20% of the transactions will be traced. In dev
# mode, set to 1 if you want all transactions are sent to Sentry.
# Default: 0.2
traces_sample_rate: 0.2
#
# Smarty templates configuration
#
templates:
# Smarty directories
directory: "${root_directory_path}/templates"
cache_directory: "${tmp_root_directory}/templates_c"
# Theme CSS file
included_css_files:
#- css/custom.css
included_js_files:
#- js/custom.js
#
# Translations
#
i18n:
# Default locale (see locales directory for available languages list)
default_locale: "en_US.UTF8"
#
# Session
#
session:
# Session timeout due to inactivity (in seconds)
timeout: 1800
# Session max duration (in seconds, default : 12h)
max_duration: 43200
# Directory path where to store PHP sessions data (optional, default: use system default)
directory_path: "${data_directory}/sessions"
#
# Database configuration
#
db:
# Sqlite
dsn: "sqlite:${data_directory}/db.sqlite3"
options: null
# Date/Datetime format in database (strptime format)
date_format: "%s"
datetime_format: "%s"
# Postgresql
#dsn: "pgsql:host=localhost;port=5432;dbname=items"
#user: "items"
#pwd: "items"
#options: null
# Date/Datetime format in database (strptime format)
#date_format: '%Y-%m-%d' # Example : 2018-10-12
#datetime_format: '%Y-%m-%d %H:%M:%S' # Example : 2018-10-12 18:06:59
# MariaDB / MySQL
#dsn: "mysql:host=localhost;dbname=items"
#user: "items"
#pwd: "items"
#options: null
# Date/Datetime format in database (strptime format)
#date_format: '%Y-%m-%d' # Example : 2018-10-12
#datetime_format: '%Y-%m-%d %H:%M:%S' # Example : 2018-10-12 18:06:59
#
# Authentication
#
auth:
# Enabled authentication
enabled: true
#
# Login form
#
login_form:
# Include application navbar (default: true)
include_navbar: true
# Display link for other authentication methods
# Note: method as key and label as value
display_other_methods:
http: "HTTP"
cas: "SSO"
# Remember username
# Enable the feature (default: true)
# remember_username: true
# Cookie name (default: remember_username)
# remember_username_cookie_name: "remember_username"
#
# HTTP Authentication Configuration
#
http:
# HTTP Auth methods :
# * AUTHORIZATION : use HTTP_AUTHORIZATION environment. This mode could be use with PHP FPM.
# Specific configuration is need in Apache to use this mode :
# RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# * REMOTE_USER : use REMOTE_USER environment variable to retrieve authenticated user's login
# This method could be only used with $http_auth_trust_without_password_challenge
# enabled.
# * PHP_AUTH : use PHP_AUTH_USER and PHP_AUTH_PW environment variables (only available
# using mod_php with Apache. (default)
method: "PHP_AUTH"
# Trust HTTP server authentication
# If enabled, the application will no check user credentials and only retrieve user's login
# from HTTP server.
#trust_without_password_challenge: true
# Realm (use when force HTTP login, optional)
#realm: "Authentication required"
#
# CAS Configuration
#
cas:
# CAS host (just the domain name)
host: "idp.example.com"
# CAS context (the root path, default: '/idp/cas')
# Example: for 'http://idp.example.com/idp/cas', put '/idp/cas'
context: "/idp/cas"
# CAS HTTP port (default: 443)
#port: 443
# CAS procotol version
# Possible values: "1.0", "2.0" (default), "3.0" or "S1" (SAML1)
#version: '2.0'
# CAS server SSL certificate validation (set to false to disable)
ca_cert_certificate_path: "/etc/ssl/certs/ca-certificates.crt"
# CAS Debug log file
#debug_log_file: "${root_directory_path}/data/logs/cas.log"
# CAS Logout
#logout: true # Enable CAS logout on app logout
#logout_url: "https://my.example.fr/logout/" # Specify custom CAS logout URL
# CAS Fake authenticated user
#fake_authenticated_user: 'myusername'
# CAS user attributes to retrieve with their properties:
# [attr name]:
# # CAS attribute name (optional, default: [attr name])
# cas_name: [CAS attr name]
# # Alternative CAS attribute name to retrieve if the first one is not defined (optional)
# alt_cas_name: [alternative CAS attr name]
# # Type of value (optional, default: 'string', possible values: string, bool, int, float)
# type: [type of value]
# # Default attribute value (optional, default: null)
# default: null
# Note: only used by casuser auth backend.
user_attributes:
login:
cas_name: "uid"
default: null
name:
cas_name: "displayName"
cas_ldap_name: "cn"
default: null
mail:
type: "string"
#
# Database user backend
#
db:
# Users table name (optional, default: users)
#users_table: "users"
# Username field name (optional, default: username)
#username_field: "username"
# Password field name (optional, default: password)
#password_field: "password"
# Exposed users table fields in resulting EesyPHP\Auth\User object
# (optional, default: name, mail)
#exposed_fields:
# - "name"
# - "mail"
#
# LDAP user backend
#
ldap:
# LDAP host (required, multiple hosts could be specified with comma separator)
host: "ldap://localhost"
# LDAP port (optional)
#port: 389
# Enable STARTTLS (optional, default: false)
#starttls: false
# LDAP directory base DN (required)
basedn: "o=example"
# LDAP bind DN (optional)
#bind_dn: 'uid=eesyphp,ou=sysaccounts,${auth.ldap.basedn}'
# LDAP bind password (optional)
#bind_password: 'secret'
# User search filter by username. The keyword "[username]" will be replace before search by
# the looked username (default: "uid=[username]")
#user_filter_by_uid: 'uid=[username]'
# User base DN
user_basedn: "ou=people,${auth.ldap.basedn}"
# Bind with username instead of user DN (optional, default: false)
#bind_with_username: true
# LDAP user attributes to retrieve with their properties:
# [attr name]:
# # LDAP attribute name (optional, default: [attr name])
# ldap_name: [LDAP attr name]
# # Alternative LDAP attribute name to retrieve if the first one is not defined (optional)
# alt_ldap_name: [alternative LDAP attr name]
# # Type of value (optional, default: 'string', possible values: string, bool, int, float)
# type: [type of value]
# # Multivalued attribute (optional, default: false)
# multivalued: true
# # Default attribute value (optional, default: null)
# default: null
user_attributes:
login:
ldap_name: "uid"
multivalued: false
default: null
name:
ldap_name: "displayName"
alt_ldap_name: "cn"
multivalued: false
default: null
mail:
type: "string"
# PEAR Net_LDAP2 library path (optional, default: Net/LDAP2.php)
#netldap2_path: 'Net/LDAP2.php'
#
# Email configuration
#
email:
# PHP PEAR Mail and Mail_Mine paths
php_mail_path: "Mail.php"
php_mail_mime_path: "Mail/mime.php"
# Sending method :
# - mail : use PHP mail function
# - sendmail : use sendmail system command
# - smtp : use an SMTP server (PHP PEAR Net_SMTP required)
send_method: "smtp"
# Sending parameters
# See : http:#pear.php.net/manual/en/package.mail.mail.factory.php
send_params: NULL
# Headers add to all e-mails sent
headers:
#- "CC: support@example.com"
# Email sender address (for all emails sent by the application)
sender: "noreply@example.org"
# Catch all e-mails sent to a configured e-mail address
catch_all: false
# Web Stats JS code
#webstats_js_code: ''
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

1
data/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
db.sqlite3

2
data/logs/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.log
*.log.*

1
data/sessions/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
sess_*

46
data/sqlite.init-db.sql Normal file
View file

@ -0,0 +1,46 @@
CREATE TABLE users (
username text NOT NULL PRIMARY KEY,
name text COLLATE NOCASE NOT NULL,
mail text COLLATE NOCASE,
password text NOT NULL
);
INSERT INTO users (username, name, mail, password) VALUES (
"admin", "Administrator", "admin@example.com",
"$argon2id$v=19$m=65536,t=4,p=1$WTQ0di44NW11MUJ1b3RMQw$+LRAQRaIXE2jhfavNFNuxnEtEUT6tEBz/98pTtD0EnM"
);
CREATE TABLE auth_tokens (
token text NOT NULL PRIMARY KEY,
username text NOT NULL REFERENCES users(username) ON UPDATE CASCADE ON DELETE CASCADE,
creation_date REAL,
expiration_date REAL
);
CREATE TABLE scases (
uuid text NOT NULL PRIMARY KEY,
username text NOT NULL REFERENCES users(username) ON UPDATE CASCADE ON DELETE CASCADE,
name text NOT NULL,
last_change REAL NOT NULL,
removed INTEGER NOT NULL
FOREIGN KEY(username) REFERENCES artist(artistid)
);
CREATE TABLE categories (
uuid text NOT NULL PRIMARY KEY,
scase_uuid text NOT NULL REFERENCES scases(uuid) ON UPDATE CASCADE ON DELETE CASCADE,
name text NOT NULL,
color text NOT NULL,
last_change REAL NOT NULL,
removed INTEGER NOT NULL
);
CREATE TABLE things (
uuid text NOT NULL PRIMARY KEY,
category_uuid text NOT NULL REFERENCES categories(uuid) ON UPDATE CASCADE ON DELETE CASCADE,
label text NOT NULL,
nb INTEGER NOT NULL,
checked INTEGER NOT NULL,
last_change REAL NOT NULL,
removed INTEGER NOT NULL
);

1
data/tmp/templates_c/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.tpl.php

77
includes/core.php Normal file
View file

@ -0,0 +1,77 @@
<?php
use EesyPHP\App;
use EesyPHP\Db;
use EesyPHP\I18n;
use EesyPHP\SentrySpan;
use MySC\Auth\API;
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
// Root directory path
$script = null;
if (defined('__FILE__') && constant('__FILE__')) { // @phpstan-ignore-line
$script = __FILE__;
}
else {
// Fallback method : detect path from core.php path
foreach(get_included_files() as $script)
if (basename($script) == 'core.php')
break;
}
if (!$script) die('Failed to detect root directory path');
$root_dir_path = realpath(dirname($script).'/../');
// Include App's includes and vendor directories to PHP include paths
set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path());
// Load composer autoload.php
require("$root_dir_path/vendor/autoload.php");
// Initialize EesyPHP application
App::init(
"$root_dir_path/config.yml",
array(
'overwrite_config_files' => array(
"$root_dir_path/config.local.yml",
),
'templates' => array(
'static_directories' => array(
"$root_dir_path/static"
),
),
'auth' => array(
'enabled' => true,
'methods' => array(
'\\MySC\\Auth\\API',
),
'backends' => array(
'db',
),
),
'default' => array(
'auth' => array(
'enabled' => true,
'methods' => array(
'\\MySC\\Auth\\API',
),
'auth_token' => array(
'expiration_delay' => 31536000,
),
),
),
),
$root_dir_path
);
$sentry_span = new SentrySpan('core.init', 'Core initialization');
// Put here your own initialization stuff
Db :: init();
API :: init();
require 'views/index.php';
$sentry_span->finish();
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

161
includes/views/index.php Normal file
View file

@ -0,0 +1,161 @@
<?php
use EesyPHP\Auth;
use EesyPHP\Db;
use EesyPHP\Log;
use EesyPHP\Tpl;
use EesyPHP\Url;
use MySC\Db\Category;
use MySC\Db\SCase;
use MySC\Db\Thing;
use function EesyPHP\vardump;
use function EesyPHP\generate_uuid;
if (php_sapi_name() == "cli")
return true;
/**
* Redirect to homepage
* @param EesyPHP\UrlRequest $request
* @return void
*/
function handle_redirect_homepage($request) {
Url::redirect("home");
}
Url :: add_url_handler(null, 'handle_redirect_homepage', null, false);
/**
* Homepage
* @param EesyPHP\UrlRequest $request
* @return void
*/
function handle_homepage($request) {
Tpl :: display("index.tpl");
}
Url :: add_url_handler("#^home$#", 'handle_homepage', null, false);
function _list_static_directory_files($root_dir, $dir, &$result, &$last_updated) {
foreach (array_diff(scandir($dir), array('.','..')) as $file) {
$path = "$dir/$file";
if (is_dir($path)) {
_list_static_directory_files($root_dir, $path, $result, $last_updated);
}
else {
$result[] = Tpl::static_url(substr($path, strlen($root_dir)+1));
$file_last_updated = filemtime($path);
if ($file_last_updated > $last_updated)
$last_updated = $file_last_updated;
}
}
}
/**
* Cache manifest file
* @param EesyPHP\UrlRequest $request
* @return void
*/
function handle_cache_manifest($request) {
$cache = ["home"];
$last_updated = filemtime(Tpl::resolve_templates_path("index.tpl"));
foreach (Tpl::static_directories() as $static_directory)
_list_static_directory_files($static_directory, $static_directory, $cache, $last_updated);
Tpl::assign("cache", $cache);
Tpl::assign("last_updated", date("Y/m/d H:i:s", $last_updated));
header("Content-type: text/plain");
$content = Tpl::fetch("cache_manifest.tpl");
$etag = md5($content);
header("Cache-Control: max-age=3000, must-revalidate");
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_updated)." GMT");
header("Etag: $etag");
print($content);
exit();
}
Url :: add_url_handler("#^cache\.manifest$#", 'handle_cache_manifest');
/**
* Sync data
* @param EesyPHP\UrlRequest $request
* @return void
*/
function handle_sync($request) {
$data = json_decode($_POST["data"], true);
Log::debug("Sync scases data: %s", vardump($data["scases"]));
$user = Auth::user();
$updated = false;
$db_scases = SCase :: list(['username' => $user -> username]);
foreach($data["scases"] as $scase_uuid => $scase_data) {
Log::debug("sync(): scase %s", $scase_uuid);
$scase = Scase :: from_json($scase_data, $user);
if (array_key_exists($scase_uuid, $db_scases)) {
Log::debug("sync(): scase %s exist in DB", $scase_uuid);
$db_scases[$scase_uuid] -> sync($scase, $updated);
// @phpstan-ignore-next-line
$db_categories = $db_scases[$scase_uuid] -> categories();
}
else {
Log::debug("sync(): scase %s does not exist in DB, create it", $scase_uuid);
$scase -> save();
$updated = true;
$db_categories = [];
}
foreach($scase_data['cats'] as $category_uuid => $category_data) {
Log::debug("sync(): scase %s / category %s", $scase_uuid, $category_uuid);
$category = Category :: from_json($category_data, $scase);
if (array_key_exists($category_uuid, $db_categories)) {
Log::debug("sync(): scase %s / category %s exists in DB, sync it", $scase_uuid, $category_uuid);
$db_categories[$category_uuid] -> sync($category, $updated);
$db_things = $db_categories[$category_uuid] ->things();
}
else {
Log::debug("sync(): scase %s / category %s does not exists in DB, create it", $scase_uuid, $category_uuid);
$category -> save();
$updated = true;
$db_things = [];
}
foreach($category_data['things'] as $thing_uuid => $thing_data) {
$thing = Thing :: from_json($thing_data, $category);
if (array_key_exists($thing_uuid, $db_things)) {
$db_things[$thing_uuid] -> sync($thing, $updated);
}
else {
$thing -> save();
$updated = true;
}
}
}
}
$data = [
"scases" => [],
"updated" => $updated,
];
foreach (SCase :: list(['username' => $user -> username]) as $uuid => $scase) {
$data["scases"][$uuid] = $scase -> to_json();
}
Tpl::display_ajax_return($data);
}
Url :: add_url_handler("#^sync$#", 'handle_sync', null, true, true, true);
/**
* Support info page
* @param EesyPHP\UrlRequest $request
* @return void
*/
function handle_support_info($request) {
if (isset($_REQUEST['download'])) {
header('Content-Type: text/plain');
header('Content-disposition: attachment; filename="'.date('Ymd-His-').Auth::user()->username.'-aide-support.txt"');
}
Tpl :: display(
isset($_REQUEST['download'])?
'support_info_content.tpl':"support_info.tpl",
"Page d'aide à l'assistance utilisateurs"
);
}
Url :: add_url_handler('|^support/?$|', 'handle_support_info');
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

4
locales/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
headers.pot
js-*-messages.pot
php-messages.pot
templates-*-messages.pot

1
locales/fr_FR.UTF8.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1,657 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2024-09-22 16:25+0000\n"
"PO-Revision-Date: 2024-09-22 18:26+0200\n"
"Last-Translator: Benjamin Renard <brenard@easter-eggs.com>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.2.2\n"
#: src/Auth/API.php:49
msgid "Invalid username or password."
msgstr "Nom d'utilisateur ou mot de passe invalide."
#: src/Auth/API.php:56
msgid "Invalid authentication token."
msgstr "Jeton d'authentification invalide."
#: src/Auth/API.php:58
msgid "Authentication token expired."
msgstr "Jeton d'authentification expiré."
#: src/Auth/API.php:65
msgid "Your account appears to have been deleted."
msgstr "Votre compte semble avoir été supprimé."
#: static/main.js:27
msgid "Confirmation"
msgstr "Confirmation"
#: static/main.js:29
msgid "OK"
msgstr "OK"
#: static/main.js:31
msgid "Cancel"
msgstr "Annuler"
#: static/main.js:86
msgid "You have to enter the name of the suitcase!"
msgstr "Vous devez saisir le nom de la valise !"
#: static/main.js:96 static/main.js:143 static/main.js:200
msgid "A suitcase exist with this name exist in the trash."
msgstr "Une valise portant ce nom existe déjà dans la corbeille."
#: static/main.js:101
msgid "This suitcase already exist!"
msgstr "Cette valise existe déjà !"
#: static/main.js:135
msgid "You have to enter the new name of the suitcase!"
msgstr "Vous devez saisir le nouveau nom de la valise !"
#: static/main.js:149 static/main.js:206
msgid "A suitcase with this name already exist!"
msgstr "Une valise portant ce nom existe déjà !"
#: static/main.js:164
msgid "An error occurred renaming this suitcase."
msgstr "Une erreur est survenue en renommant cette valise."
#: static/main.js:193
msgid "You have to enter the new suitcase name."
msgstr "Vous devez saisir le nom de la nouvelle valise."
#: static/main.js:219
msgid "An error occurred copying the suitcase."
msgstr "Une erreur est survenue en copiant cette valise."
#: static/main.js:240
#, javascript-format
msgid "Reset the %s suitcase"
msgstr "Réinitialiser la valise %s"
#: static/main.js:241
#, javascript-format
msgid "Are-you sure you want to reset the suitcase %s?"
msgstr "Êtes-vous sûre de vouloir réinitialiser la valise %s ?"
#: static/main.js:260
#, javascript-format
msgid "Delete the %s suitcase"
msgstr "Supprimer la valise %s"
#: static/main.js:261
#, javascript-format
msgid "Are-you sure you want to delete the suitcase %s?"
msgstr "Êtes-vous sûre de vouloir supprimer la valise %s ?"
#: static/main.js:278
#, javascript-format
msgid "Restaure the %s suitcase"
msgstr "Restaurer la valise %s"
#: static/main.js:279
#, javascript-format
msgid "Are-you sure you want to restaure the suitcase %s?"
msgstr "Êtes-vous sûre de vouloir restaurer la valise %s ?"
#: static/main.js:311
msgid "You have to enter the category name!"
msgstr "Vous devez saisir le nom de la catégorie !"
#: static/main.js:320 static/main.js:378
msgid "A category with this name already exist in the trash!"
msgstr "Une catégorie portant ce nom existe déjà dans la corbeille !"
#: static/main.js:326
msgid "A category with this name already exist!"
msgstr "Une catégorie portant ce nom existe déjà !"
#: static/main.js:366
msgid "You have to enter the new name of the category!"
msgstr "Vous devez saisir le nouveau nom de la catégorie !"
#: static/main.js:383
msgid "A category with this name already!"
msgstr "Une catégorie portant ce nom existe déjà !"
#: static/main.js:414
#, javascript-format
msgid "Delete the category %s"
msgstr "Supprimer la catégorie %s"
#: static/main.js:415
#, javascript-format
msgid "Are-you sure you want to delete the category %s?"
msgstr "Êtes-vous sûre de vouloir supprimer la catégorie %s ?"
#: static/main.js:433
#, javascript-format
msgid "Restore the category %s"
msgstr "Restaurer la catégorie %s"
#: static/main.js:434
#, javascript-format
msgid "Are-you sure you want to restore the category %s?"
msgstr "Êtes-vous sûre de vouloir restaurer la catégorie %s ?"
#: static/main.js:497
msgid "Tow elements can't have the same name!"
msgstr "Deux éléments ne peuvent pas porter le même nom !"
#: static/main.js:505
#, javascript-format
msgid "The element '%s' already exist!"
msgstr "Un élément portant le nom '%s' existe déjà !"
#: static/main.js:528
msgid "You have to enter at least one element name!"
msgstr "Vous devez saisir au moins un nom d'élément !"
#: static/main.js:561
msgid "Another?"
msgstr "Un autre ?"
#: static/main.js:566
msgid "Nb"
msgstr "Nb"
#: static/main.js:593
msgid "You have to enter the new element name!"
msgstr "Vous devez saisir le nom du nouvel élément !"
#: static/main.js:609
msgid "An element with this name already exist in the trash!"
msgstr "Un élément portant ce nom existe déjà dans la corbeille !"
#: static/main.js:615
msgid "An element with this name already exist!"
msgstr "Un élément portant ce nom existe déjà !"
#: static/main.js:659
#, javascript-format
msgid "Delete the element %s"
msgstr "Supprimer l'élément %s"
#: static/main.js:660
#, javascript-format
msgid "Are-you sure you want to delete the element %s?"
msgstr "Êtes-vous sûre de vouloir supprimer l'élément %s ?"
#: static/main.js:681
#, javascript-format
msgid "Restore the element %s"
msgstr "Restaurer l'élément %s"
#: static/main.js:682
#, javascript-format
msgid "Are-you sure you want to restore the element %s?"
msgstr "Êtes-vous sûre de vouloir restaurer l'élément %s ?"
#: static/main.js:785
msgid "Add an element"
msgstr "Ajouter un élément"
#: static/main.js:823 static/main.js:963
msgid "Trash"
msgstr "Corbeille"
#: static/main.js:836
msgid "The trash is empty."
msgstr "La corbeille est vide."
#: static/main.js:924
msgid "Your suitcases"
msgstr "Vos valises"
#: static/main.js:995
msgid "No suitcase in the trash."
msgstr "Aucune valise dans la corbeille."
#: static/main.js:1050
msgid "Delete all local data"
msgstr "Purger les données locales"
#: static/main.js:1051
msgid "Are-you sure you want to delete all local data (irreversible action)?"
msgstr ""
"Êtes-vous sûre de vouloir supprimer toutes les données locales (action "
"irréversible) ?"
#: static/main.js:1068
msgid "Loading example data"
msgstr "Charger les données d'exemple"
#: static/main.js:1070
msgid ""
"Are-you sure you want to load example data in place of your own local data "
"(irreversible action)?"
msgstr ""
"Êtes-vous sûre de vouloir charger les données d'exemple à la place de vos "
"données locales (action irréversible) ?"
#: static/main.js:1120
msgid "Failed to decode JSON file."
msgstr "Impossible de décoder le fichier JSON."
#: static/main.js:1125
msgid "Import from file"
msgstr "Import depuis un fichier"
#: static/main.js:1127
msgid ""
"Are-you sure you want to overwrite your local data with the data from this "
"file (irreversible action)?"
msgstr ""
"Êtes-vous sûre de vouloir écraser vos données locales par celles issues de "
"ce fichier (action irréversible) ?"
#: static/main.js:1134
msgid "The file has been imported successfully."
msgstr "Le fichier as bien été importé."
#: static/main.js:1141
msgid "An error occurred loading this file. Restoring previous data..."
msgstr ""
"Une erreur est survenue en chargeant ce fichier. Restauration des données "
"précédentes..."
#: static/main.js:1148 static/main.js:1315
msgid "Previous data has been restored successfully."
msgstr "Les données précédentes ont bien été restaurées."
#: static/main.js:1154 static/main.js:1321
msgid "An error occurred restoring previous data."
msgstr "Une erreur est survenue en restaurant les données précédentes."
#: static/main.js:1200
msgid "You have to enter your username and password!"
msgstr "Vous devez saisir votre nom d'utilisateur et votre mot de passe !"
#: static/main.js:1218
msgid "Connected."
msgstr "Connecté."
#: static/main.js:1226
msgid "An error occurred logging in. Please try again later."
msgstr ""
"Une erreur est survenue durant la connexion. Merci de réessayer "
"ultérieurement."
#: static/main.js:1247
msgid "Logout"
msgstr "Déconnexion"
#: static/main.js:1248
msgid "Do you really want to log out?"
msgstr "Voulez-vous vraiment vous déconnecter ?"
#: static/main.js:1259
msgid "Logged out"
msgstr "Déconnecté"
#: static/main.js:1262 static/main.js:1270
msgid "An error occurred logging you out. Please try again later."
msgstr ""
"Une erreur est survenue durant la déconnexion. Merci de réessayer "
"ultérieurement."
#: static/main.js:1303
msgid "Data synchronized."
msgstr "Données synchronisées."
#: static/main.js:1308
msgid ""
"An error occurred loading data from the server. Restoring previous data..."
msgstr ""
"Une erreur est survenue en chargeant les données depuis le serveur. "
"Restauration des données précédentes..."
#: static/main.js:1331 static/main.js:1353
msgid "An error occurred synchronizing your data. Please try again later."
msgstr ""
"Une erreur est survenue durant la synchronisation de vos données. Merci de "
"réessayer ultérieurement."
#: static/main.js:1345
msgid "Your session appears to have expired, please re-authenticate."
msgstr "Votre session semble avoir expirée, merci de vous réidentifier."
#: static/main.js:1372
msgid "You must sign in before you can sync your data."
msgstr "Vous devez vous connecter avant de synchroniser vos données."
#: static/main.js:1386
msgid "Synchronize your suitcases from server"
msgstr "Synchronisation de vos valises avec les serveurs"
#: static/main.js:1387
msgid "Are-you sure you want to synchronize your suitcases from server?"
msgstr "Êtes-vous sûre de vouloir synchroniser vos valises avec le serveur ?"
#: static/main.js:1450
msgid "An example of a suitcase?"
msgstr "Un exemple de valise ?"
#: static/main.js:1451
msgid "Would you like to load a sample suitcase to get started?"
msgstr "Voulez-vous chargeant un exemple de valise pour commencer ?"
#: static/main.js:1468
msgid ""
"Your internet browser does not support local data storage. Therefore, "
"unfortunately you cannot use this application."
msgstr ""
"Votre navigateur internet ne semble pas supporter le stockage de données "
"locales. Malheureusement, vous ne pouvez donc pas utiliser cette application."
#: static/main.js:1557
msgid "Error loading local data"
msgstr "Une erreur est survenue en chargeant les données locales"
#: static/main.js:1558
msgid "An error occurred while loading local data. Should we purge it?"
msgstr ""
"Une erreur est survenue en chargeant vos données locales. Devons-nous les "
"purger ?"
#: static/mysc_objects.js:10
msgid "White paper"
msgstr "Papier blanc"
#: static/mysc_objects.js:11
msgid "Pen"
msgstr "Stylo"
#: static/mysc_objects.js:12
msgid "ID card/passport"
msgstr "Carte d'identité / passeport"
#: static/mysc_objects.js:18
msgid "Watch"
msgstr "Montre"
#: static/mysc_objects.js:19
msgid "Watch charger"
msgstr "Chargeur montre"
#: static/mysc_objects.js:20
msgid "Laptop"
msgstr "PC portable"
#: static/mysc_objects.js:21
msgid "Laptop charger"
msgstr "Chargeur PC portable"
#: static/mysc_objects.js:294
#, javascript-format
msgid "An error occurred while loading the %s suitcase from the cache."
msgstr "Une erreur est survenue en chargeant la valise %s depuis le cache."
#: static/mysc_objects.js:417
msgid "An error occurred while loading the category list from the cache."
msgstr ""
"Une erreur est survenue en chargeant la liste de catégories depuis le cache."
#: static/mysc_objects.js:565
#, javascript-format
msgid "An error occurred while loading the %s category from the cache."
msgstr "Une erreur est survenue en chargeant la catégorie %s depuis le cache."
#: static/mysc_objects.js:636
msgid "Error loading your login information. Please log in again."
msgstr ""
"Erreur en chargeant vos données d'authentification. Merci de vous "
"réidentifier."
#: templates/support_info.tpl:4
msgid "Helpdesk page"
msgstr "Page d'assistance aux utilisateurs"
#: templates/support_info.tpl:5
msgid ""
"Upon request, please download and forward the following information to the "
"support service:"
msgstr ""
"À leur demande, merci de télécharger et transmettre les informations ci-"
"dessous au service support :"
#: templates/support_info.tpl:9
msgid "Download"
msgstr "Télécharger"
#: templates/support_info_content.tpl:1
msgid "Application URL:"
msgstr "URL de l'application :"
#: templates/support_info_content.tpl:2
msgid "Current page URL:"
msgstr "URL de la page courante :"
#: templates/support_info_content.tpl:4
msgid "Connected user:"
msgstr "Utilisateur connecté :"
#: templates/support_info_content.tpl:6
msgid "Extra user information:"
msgstr "Informations supplémentaires de l'utilisateur :"
#: templates/index.tpl:39 templates/index.tpl:91
msgid "Add a suitcase"
msgstr "Ajouter une valise"
#: templates/index.tpl:40
msgid "Show trash"
msgstr "Voir la corbeille"
#: templates/index.tpl:41
msgid "Suitcases list"
msgstr "Liste des valises"
#: templates/index.tpl:43
msgid "Manage the suitcase"
msgstr "Gérer les valises"
#: templates/index.tpl:45 templates/index.tpl:157
msgid "Add a category"
msgstr "Ajouter une catégorie"
#: templates/index.tpl:47 templates/index.tpl:135
msgid "Rename the suitcase"
msgstr "Renommer la valise"
#: templates/index.tpl:48 templates/index.tpl:113
msgid "Copy the suitcase"
msgstr "Copier la valise"
#: templates/index.tpl:49
msgid "Reset the suitcase"
msgstr "Réinitialiser la valise"
#: templates/index.tpl:50
msgid "Delete the suitcase"
msgstr "Supprimer la valise"
#: templates/index.tpl:52
msgid "Show suitcase's trash"
msgstr "Voir la corbeille de la valise"
#: templates/index.tpl:56
msgid "Manage data"
msgstr "Gérer les données"
#: templates/index.tpl:58
msgid "Backup your data"
msgstr "Sauvegarder vos données"
#: templates/index.tpl:59
msgid "Restaure your data"
msgstr "Restaurer vos données"
#: templates/index.tpl:61
msgid "Synchronize local data"
msgstr "Synchroniser les données locales"
#: templates/index.tpl:63
msgid "Purge local data"
msgstr "Purger les données locales"
#: templates/index.tpl:64
msgid "Load example data"
msgstr "Charger les données d'exemple"
#: templates/index.tpl:74
msgid "Login"
msgstr "Connexion"
#: templates/index.tpl:96
msgid "Suitcase name"
msgstr "Nom de la valise"
#: templates/index.tpl:102 templates/index.tpl:168 templates/index.tpl:218
msgid "Add"
msgstr "Ajouter"
#: templates/index.tpl:118
msgid "Name of the new suitcase"
msgstr "Nom de la nouvelle valise"
#: templates/index.tpl:124
msgid "Copy"
msgstr "Copier"
#: templates/index.tpl:140
msgid "New suitcase name"
msgstr "Nouveau nom de la valise"
#: templates/index.tpl:146 templates/index.tpl:190
msgid "Rename"
msgstr "Renommer"
#: templates/index.tpl:162
msgid "Category name"
msgstr "Nom de la catégorie"
#: templates/index.tpl:179
msgid "Rename the category"
msgstr "Renommer la catégorie"
#: templates/index.tpl:184
msgid "New category's name"
msgstr "Nouveau nom de la catégorie"
#: templates/index.tpl:207 templates/index.tpl:234
msgid "Element name"
msgstr "Nom de l'élément"
#: templates/index.tpl:229
msgid "Edit the element"
msgstr "Modifier l'élément"
#: templates/index.tpl:241
msgid "Modify"
msgstr "Modifier"
#: templates/index.tpl:252 templates/index.tpl:257
msgid "Loading..."
msgstr "Chargement..."
#: templates/index.tpl:271
msgid "Connection"
msgstr "Connexion"
#: templates/index.tpl:276
msgid "Username"
msgstr "Nom d'utilisateur"
#: templates/index.tpl:277
msgid "Password"
msgstr "Mot de passe"
#: templates/index.tpl:283 templates/index.tpl:326
msgid "Connect"
msgstr "Connexion"
#: templates/index.tpl:295
msgid "Welcome"
msgstr "Bienvenu"
#: templates/index.tpl:298
msgid ""
"<p>\n"
" This application allows you to manage lists of things not to "
"forget to pack in your\n"
" suitcase before your departure:\n"
" <ul>\n"
" <li>\n"
" To get started, create a suitcase, add the categories of "
"things you will need to\n"
" pack, and add all these things to it;\n"
" </li>\n"
" <li>\n"
" Before your departure, you can then gradually check off all "
"the things you have\n"
" already gathered and ensure you don't forget anything!\n"
" </li>\n"
" </ul>\n"
" </p>\n"
" <p>\n"
" <strong>Note:</strong> This application has been designed to work "
"completely locally.\n"
" The data manipulated (your suitcases, etc.) is stored only in your "
"internet browser.\n"
" It is also possible to export/import this information in JSON "
"format.<br/>\n"
" However, to facilitate the management of your suitcases from "
"multiple devices, it's\n"
" possible to synchronize this information on the server. For this, "
"you need an account,\n"
" and at this time, registrations are not open.\n"
" </p>\n"
" <p>\n"
" If you have an account, you can log in by clicking the <em>Login</"
"em> button below.\n"
" If not, click the <em>Anonymous usage</em> button to start using "
"the application.\n"
" </p>"
msgstr ""
"<p>\n"
" Cette application vous permet de gérer des listes de choses à ne pas "
"oublier de glisser dans votre valise avant votre départ :\n"
" <ul>\n"
" <li>Pour commencer, créer une valise, ajouter les catégories de choses "
"que vous aurez à y glisser et ajouter y toutes ces choses.</li>\n"
" <li>Avant votre départ, vous pourrez alors cocher petit à petit toutes "
"les choses que vous aurez déjà réunies et vous asurez de ne rien oublier !</"
"li>\n"
" </ul>\n"
"</p>\n"
"<p>\n"
" <strong>Note :</strong> Cette application a été conçue pour pouvoir "
"fonctionner complètement localement. Les données manipulées (vos "
"valises, ...) sont stockés\n"
" uniquement dans votre navigateur internet. Il est par ailleurs possible "
"d'exporter/importer ces informations au format JSON.<br/>\n"
" Cependant, pour faciliter la gestion de vos valises depuis plusieurs "
"appareils, il est possible de synchroniser ces informations sur le serveur. "
"Pour cela,\n"
" il vous faut un compte et à ce jour, les inscriptions ne sont pas "
"ouvertes.\n"
"</p>\n"
"<p>\n"
" Si vous disposez d'un compte, vous pouvez vous connecter en cliquant sur "
"le bouton <em>Connexion</em> ci-dessous.\n"
" À défaut, cliquer sur le bouton <em>Utilisation annonyme</em> pour "
"commencer à utiliser l'application.\n"
"</p>"
#: templates/index.tpl:327
msgid "Anonymous usage"
msgstr "Utilisation annonyme"

597
locales/messages.pot Normal file
View file

@ -0,0 +1,597 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2024-09-22 16:25+0000\n"
"PO-Revision-Date: 2024-09-22 16:25+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/Auth/API.php:49
msgid "Invalid username or password."
msgstr ""
#: src/Auth/API.php:56
msgid "Invalid authentication token."
msgstr ""
#: src/Auth/API.php:58
msgid "Authentication token expired."
msgstr ""
#: src/Auth/API.php:65
msgid "Your account appears to have been deleted."
msgstr ""
#: static/main.js:27
msgid "Confirmation"
msgstr ""
#: static/main.js:29
msgid "OK"
msgstr ""
#: static/main.js:31
msgid "Cancel"
msgstr ""
#: static/main.js:86
msgid "You have to enter the name of the suitcase!"
msgstr ""
#: static/main.js:96 static/main.js:143 static/main.js:200
msgid "A suitcase exist with this name exist in the trash."
msgstr ""
#: static/main.js:101
msgid "This suitcase already exist!"
msgstr ""
#: static/main.js:135
msgid "You have to enter the new name of the suitcase!"
msgstr ""
#: static/main.js:149 static/main.js:206
msgid "A suitcase with this name already exist!"
msgstr ""
#: static/main.js:164
msgid "An error occurred renaming this suitcase."
msgstr ""
#: static/main.js:193
msgid "You have to enter the new suitcase name."
msgstr ""
#: static/main.js:219
msgid "An error occurred copying the suitcase."
msgstr ""
#: static/main.js:240
#, javascript-format
msgid "Reset the %s suitcase"
msgstr ""
#: static/main.js:241
#, javascript-format
msgid "Are-you sure you want to reset the suitcase %s?"
msgstr ""
#: static/main.js:260
#, javascript-format
msgid "Delete the %s suitcase"
msgstr ""
#: static/main.js:261
#, javascript-format
msgid "Are-you sure you want to delete the suitcase %s?"
msgstr ""
#: static/main.js:278
#, javascript-format
msgid "Restaure the %s suitcase"
msgstr ""
#: static/main.js:279
#, javascript-format
msgid "Are-you sure you want to restaure the suitcase %s?"
msgstr ""
#: static/main.js:311
msgid "You have to enter the category name!"
msgstr ""
#: static/main.js:320 static/main.js:378
msgid "A category with this name already exist in the trash!"
msgstr ""
#: static/main.js:326
msgid "A category with this name already exist!"
msgstr ""
#: static/main.js:366
msgid "You have to enter the new name of the category!"
msgstr ""
#: static/main.js:383
msgid "A category with this name already!"
msgstr ""
#: static/main.js:414
#, javascript-format
msgid "Delete the category %s"
msgstr ""
#: static/main.js:415
#, javascript-format
msgid "Are-you sure you want to delete the category %s?"
msgstr ""
#: static/main.js:433
#, javascript-format
msgid "Restore the category %s"
msgstr ""
#: static/main.js:434
#, javascript-format
msgid "Are-you sure you want to restore the category %s?"
msgstr ""
#: static/main.js:497
msgid "Tow elements can't have the same name!"
msgstr ""
#: static/main.js:505
#, javascript-format
msgid "The element '%s' already exist!"
msgstr ""
#: static/main.js:528
msgid "You have to enter at least one element name!"
msgstr ""
#: static/main.js:561
msgid "Another?"
msgstr ""
#: static/main.js:566
msgid "Nb"
msgstr ""
#: static/main.js:593
msgid "You have to enter the new element name!"
msgstr ""
#: static/main.js:609
msgid "An element with this name already exist in the trash!"
msgstr ""
#: static/main.js:615
msgid "An element with this name already exist!"
msgstr ""
#: static/main.js:659
#, javascript-format
msgid "Delete the element %s"
msgstr ""
#: static/main.js:660
#, javascript-format
msgid "Are-you sure you want to delete the element %s?"
msgstr ""
#: static/main.js:681
#, javascript-format
msgid "Restore the element %s"
msgstr ""
#: static/main.js:682
#, javascript-format
msgid "Are-you sure you want to restore the element %s?"
msgstr ""
#: static/main.js:785
msgid "Add an element"
msgstr ""
#: static/main.js:823 static/main.js:963
msgid "Trash"
msgstr ""
#: static/main.js:836
msgid "The trash is empty."
msgstr ""
#: static/main.js:924
msgid "Your suitcases"
msgstr ""
#: static/main.js:995
msgid "No suitcase in the trash."
msgstr ""
#: static/main.js:1050
msgid "Delete all local data"
msgstr ""
#: static/main.js:1051
msgid "Are-you sure you want to delete all local data (irreversible action)?"
msgstr ""
#: static/main.js:1068
msgid "Loading example data"
msgstr ""
#: static/main.js:1070
msgid ""
"Are-you sure you want to load example data in place of your own local data "
"(irreversible action)?"
msgstr ""
#: static/main.js:1120
msgid "Failed to decode JSON file."
msgstr ""
#: static/main.js:1125
msgid "Import from file"
msgstr ""
#: static/main.js:1127
msgid ""
"Are-you sure you want to overwrite your local data with the data from this "
"file (irreversible action)?"
msgstr ""
#: static/main.js:1134
msgid "The file has been imported successfully."
msgstr ""
#: static/main.js:1141
msgid "An error occurred loading this file. Restoring previous data..."
msgstr ""
#: static/main.js:1148 static/main.js:1315
msgid "Previous data has been restored successfully."
msgstr ""
#: static/main.js:1154 static/main.js:1321
msgid "An error occurred restoring previous data."
msgstr ""
#: static/main.js:1200
msgid "You have to enter your username and password!"
msgstr ""
#: static/main.js:1218
msgid "Connected."
msgstr ""
#: static/main.js:1226
msgid "An error occurred logging in. Please try again later."
msgstr ""
#: static/main.js:1247
msgid "Logout"
msgstr ""
#: static/main.js:1248
msgid "Do you really want to log out?"
msgstr ""
#: static/main.js:1259
msgid "Logged out"
msgstr ""
#: static/main.js:1262 static/main.js:1270
msgid "An error occurred logging you out. Please try again later."
msgstr ""
#: static/main.js:1303
msgid "Data synchronized."
msgstr ""
#: static/main.js:1308
msgid ""
"An error occurred loading data from the server. Restoring previous data..."
msgstr ""
#: static/main.js:1331 static/main.js:1353
msgid "An error occurred synchronizing your data. Please try again later."
msgstr ""
#: static/main.js:1345
msgid "Your session appears to have expired, please re-authenticate."
msgstr ""
#: static/main.js:1372
msgid "You must sign in before you can sync your data."
msgstr ""
#: static/main.js:1386
msgid "Synchronize your suitcases from server"
msgstr ""
#: static/main.js:1387
msgid "Are-you sure you want to synchronize your suitcases from server?"
msgstr ""
#: static/main.js:1450
msgid "An example of a suitcase?"
msgstr ""
#: static/main.js:1451
msgid "Would you like to load a sample suitcase to get started?"
msgstr ""
#: static/main.js:1468
msgid ""
"Your internet browser does not support local data storage. Therefore, "
"unfortunately you cannot use this application."
msgstr ""
#: static/main.js:1557
msgid "Error loading local data"
msgstr ""
#: static/main.js:1558
msgid "An error occurred while loading local data. Should we purge it?"
msgstr ""
#: static/mysc_objects.js:10
msgid "White paper"
msgstr ""
#: static/mysc_objects.js:11
msgid "Pen"
msgstr ""
#: static/mysc_objects.js:12
msgid "ID card/passport"
msgstr ""
#: static/mysc_objects.js:18
msgid "Watch"
msgstr ""
#: static/mysc_objects.js:19
msgid "Watch charger"
msgstr ""
#: static/mysc_objects.js:20
msgid "Laptop"
msgstr ""
#: static/mysc_objects.js:21
msgid "Laptop charger"
msgstr ""
#: static/mysc_objects.js:294
#, javascript-format
msgid "An error occurred while loading the %s suitcase from the cache."
msgstr ""
#: static/mysc_objects.js:417
msgid "An error occurred while loading the category list from the cache."
msgstr ""
#: static/mysc_objects.js:565
#, javascript-format
msgid "An error occurred while loading the %s category from the cache."
msgstr ""
#: static/mysc_objects.js:636
msgid "Error loading your login information. Please log in again."
msgstr ""
#: templates/support_info.tpl:4
msgid "Helpdesk page"
msgstr ""
#: templates/support_info.tpl:5
msgid ""
"Upon request, please download and forward the following information to the "
"support service:"
msgstr ""
#: templates/support_info.tpl:9
msgid "Download"
msgstr ""
#: templates/support_info_content.tpl:1
msgid "Application URL:"
msgstr ""
#: templates/support_info_content.tpl:2
msgid "Current page URL:"
msgstr ""
#: templates/support_info_content.tpl:4
msgid "Connected user:"
msgstr ""
#: templates/support_info_content.tpl:6
msgid "Extra user information:"
msgstr ""
#: templates/index.tpl:39 templates/index.tpl:91
msgid "Add a suitcase"
msgstr ""
#: templates/index.tpl:40
msgid "Show trash"
msgstr ""
#: templates/index.tpl:41
msgid "Suitcases list"
msgstr ""
#: templates/index.tpl:43
msgid "Manage the suitcase"
msgstr ""
#: templates/index.tpl:45 templates/index.tpl:157
msgid "Add a category"
msgstr ""
#: templates/index.tpl:47 templates/index.tpl:135
msgid "Rename the suitcase"
msgstr ""
#: templates/index.tpl:48 templates/index.tpl:113
msgid "Copy the suitcase"
msgstr ""
#: templates/index.tpl:49
msgid "Reset the suitcase"
msgstr ""
#: templates/index.tpl:50
msgid "Delete the suitcase"
msgstr ""
#: templates/index.tpl:52
msgid "Show suitcase's trash"
msgstr ""
#: templates/index.tpl:56
msgid "Manage data"
msgstr ""
#: templates/index.tpl:58
msgid "Backup your data"
msgstr ""
#: templates/index.tpl:59
msgid "Restaure your data"
msgstr ""
#: templates/index.tpl:61
msgid "Synchronize local data"
msgstr ""
#: templates/index.tpl:63
msgid "Purge local data"
msgstr ""
#: templates/index.tpl:64
msgid "Load example data"
msgstr ""
#: templates/index.tpl:74
msgid "Login"
msgstr ""
#: templates/index.tpl:96
msgid "Suitcase name"
msgstr ""
#: templates/index.tpl:102 templates/index.tpl:168 templates/index.tpl:218
msgid "Add"
msgstr ""
#: templates/index.tpl:118
msgid "Name of the new suitcase"
msgstr ""
#: templates/index.tpl:124
msgid "Copy"
msgstr ""
#: templates/index.tpl:140
msgid "New suitcase name"
msgstr ""
#: templates/index.tpl:146 templates/index.tpl:190
msgid "Rename"
msgstr ""
#: templates/index.tpl:162
msgid "Category name"
msgstr ""
#: templates/index.tpl:179
msgid "Rename the category"
msgstr ""
#: templates/index.tpl:184
msgid "New category's name"
msgstr ""
#: templates/index.tpl:207 templates/index.tpl:234
msgid "Element name"
msgstr ""
#: templates/index.tpl:229
msgid "Edit the element"
msgstr ""
#: templates/index.tpl:241
msgid "Modify"
msgstr ""
#: templates/index.tpl:252 templates/index.tpl:257
msgid "Loading..."
msgstr ""
#: templates/index.tpl:271
msgid "Connection"
msgstr ""
#: templates/index.tpl:276
msgid "Username"
msgstr ""
#: templates/index.tpl:277
msgid "Password"
msgstr ""
#: templates/index.tpl:283 templates/index.tpl:326
msgid "Connect"
msgstr ""
#: templates/index.tpl:295
msgid "Welcome"
msgstr ""
#: templates/index.tpl:298
msgid ""
"<p>\n"
" This application allows you to manage lists of things not to "
"forget to pack in your\n"
" suitcase before your departure:\n"
" <ul>\n"
" <li>\n"
" To get started, create a suitcase, add the categories of "
"things you will need to\n"
" pack, and add all these things to it;\n"
" </li>\n"
" <li>\n"
" Before your departure, you can then gradually check off all "
"the things you have\n"
" already gathered and ensure you don't forget anything!\n"
" </li>\n"
" </ul>\n"
" </p>\n"
" <p>\n"
" <strong>Note:</strong> This application has been designed to work "
"completely locally.\n"
" The data manipulated (your suitcases, etc.) is stored only in your "
"internet browser.\n"
" It is also possible to export/import this information in JSON "
"format.<br/>\n"
" However, to facilitate the management of your suitcases from "
"multiple devices, it's\n"
" possible to synchronize this information on the server. For this, "
"you need an account,\n"
" and at this time, registrations are not open.\n"
" </p>\n"
" <p>\n"
" If you have an account, you can log in by clicking the <em>Login</"
"em> button below.\n"
" If not, click the <em>Anonymous usage</em> button to start using "
"the application.\n"
" </p>"
msgstr ""
#: templates/index.tpl:327
msgid "Anonymous usage"
msgstr ""

14
phpstan.neon Normal file
View file

@ -0,0 +1,14 @@
parameters:
level: 5
paths:
- bin
- includes
- src
- public_html
universalObjectCratesClasses:
- EesyPHP\HookEvent
- EesyPHP\UrlRequest
- EesyPHP\Auth\User
- EesyPHP\Db\DbObject
bootstrapFiles:
- includes/core.php

8
public_html/.htaccess Normal file
View file

@ -0,0 +1,8 @@
RewriteEngine On
#RewriteBase /app
# If the request is not for a valid file
RewriteCond %{REQUEST_FILENAME} !-f
# If the request is not for a valid link
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^ index.php [L]

View file

@ -1,39 +0,0 @@
CACHE MANIFEST
# Date : 2018/06/18 - Version : 1
CACHE:
index.html
favicon.png
icon-196x196.png
icon-128x128.png
inc/main.css
inc/main.js
inc/mysc_objects.js
inc/mydialog.js
# Bootstrap
inc/lib/bootstrap/css/bootstrap.min.css
inc/lib/bootstrap/css/bootstrap-theme.min.css
inc/lib/bootstrap/css/bootstrap.css
inc/lib/bootstrap/css/bootstrap-theme.css
inc/lib/bootstrap/css/bootstrap.css.map
inc/lib/bootstrap/css/bootstrap-theme.min.css.map
inc/lib/bootstrap/css/bootstrap-theme.css.map
inc/lib/bootstrap/css/bootstrap.min.css.map
inc/lib/bootstrap/js/bootstrap.js
inc/lib/bootstrap/js/npm.js
inc/lib/bootstrap/js/bootstrap.min.js
inc/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
inc/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
inc/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
inc/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
inc/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
# jQuery
inc/lib/jquery.min.js
NETWORK:
*

View file

@ -1,962 +0,0 @@
$('#cats').collapse({
toggle: false
});
on_title_click=function(event) {
if (event.target.tagName=='BUTTON') {
return;
}
var title=$(this);
var panel=title.parent().parent();
var panel_collapse=panel.find('.panel-collapse');
var show=!panel_collapse.hasClass('in');
$('.panel-collapse').each(function(idx,div) {
$(div).collapse('hide');
});
if (show) {
panel_collapse.collapse('show');
}
}
/***********************
* Add scase
**********************/
on_add_scase_btn_click=function(event) {
navbar_collapse_hide();
$('#add_scase_modal').modal('show');
}
on_valid_add_scase_modal=function (e) {
e.preventDefault();
var name=$('#add_scase_name')[0].value;
if (name=='') {
alert("Vous devez saisir le nom de la valise !");
return;
}
var nameshake=scases.byName(name);
if (nameshake) {
if (nameshake.removed) {
alert("Une valise de ce nom existe dans la corbeille !");
}
else {
alert("Cette valise existe déjà !");
}
return;
}
var scase=scases.newSCase(name);
if (scase) {
scases.save();
show_scase(scase);
}
$('#add_scase_modal').modal('hide');
}
on_show_add_scase_modal=function () {
$('#add_scase_name').focus();
}
on_close_add_scase_modal=function () {
$('#add_scase_modal form')[0].reset();
}
/***********************
* Rename scase
**********************/
on_rename_scase_btn_click=function(event) {
navbar_collapse_hide();
$('#rename_scase_name')[0].value=$('#cats').data('scase');
$('#rename_scase_modal').modal('show');
}
on_valid_rename_scase_modal=function (e) {
e.preventDefault();
var name=$('#rename_scase_name')[0].value;
if (name=='') {
alert("Vous devez saisir le nouveau nom de la valise !");
return;
}
if ($('#cats').data('scase')!=name) {
var nameshake=scases.byName(name);
if (nameshake) {
if (nameshake.removed) {
alert("Une valise portant ce nom existe dans la corbeille !");
}
else {
alert("Une valise portant ce nom existe déjà !");
}
return;
}
var scase=scases.renameSCase($('#cats').data('scase'),name);
if (scase) {
scases.save();
show_scase(scase);
}
else {
alert('Une erreur est survenue en renomant la valise...');
}
}
$('#rename_scase_modal').modal('hide');
}
on_show_rename_scase_modal=function () {
$('#rename_scase_name').focus();
}
on_close_rename_scase_modal=function () {
$('#rename_scase_modal form')[0].reset();
}
/***********************
* Copy scase
**********************/
on_copy_scase_btn_click=function(event) {
navbar_collapse_hide();
$('#copy_scase_modal').modal('show');
}
on_valid_copy_scase_modal=function (e) {
e.preventDefault();
var name=$('#copy_scase_name')[0].value;
if (name=='') {
alert("Vous devez saisir le nom de la nouvelle valise !");
return;
}
var nameshake=scases.byName(name);
if (nameshake) {
if (nameshake.removed) {
alert("Une valise portant ce nom existe dans la corbeille !");
}
else {
alert("Une valise portant ce nom existe déjà !");
}
return;
}
var scase=scases.copySCase($('#cats').data('scase'),name);
if (scase) {
scases.save();
show_scase(scase);
}
else {
alert('Une erreur est survenue en copiant la valise...');
}
$('#copy_scase_modal').modal('hide');
}
on_show_copy_scase_modal=function () {
$('#copy_scase_name').focus();
}
on_close_copy_scase_modal=function () {
$('#copy_scase_modal form')[0].reset();
}
/***********************
* Reset scase
**********************/
on_reset_scase_btn_click=function(event) {
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
myconfirm('Voulez-vous vraiment réinitialiser la valise '+$('#cats').data('scase')+' ?',
function(data) {
scases.resetSCase(scase.name);
scases.save();
show_scase(scase);
});
}
}
/***********************
* Delete scase
**********************/
on_delete_scase_btn_click=function(event) {
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
myconfirm('Voulez-vous vraiment supprimer la valise '+$('#cats').data('scase')+' ?',
function(data) {
scases.removeSCase(scase.name);
scases.save();
show_scases();
});
}
}
on_restore_scase_btn_click=function(event) {
navbar_collapse_hide();
var scase=event.data.scase;
if (scase) {
myconfirm('Voulez-vous vraiment restaurer la valise '+scase.name+' ?',
function(data) {
scase.restore();
scases.save();
show_scases();
});
}
}
on_scase_trash_btn_click=function(event) {
event.preventDefault();
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
show_scase_trash(scase);
}
}
/***********************
* Add cat
**********************/
on_add_cat_btn_click=function(event) {
navbar_collapse_hide();
$('#add_cat_modal').modal('show');
}
on_valid_add_cat_modal=function (e) {
e.preventDefault();
var name=$('#add_cat_name')[0].value;
if (name=='') {
alert("Vous devez saisir le nom de la catégorie !");
return;
}
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var nameshake=scase.cats.byName(name);
if (nameshake) {
if (nameshake.removed) {
alert("Une catégorie portant ce nom existe dans la corbeille !");
}
else {
alert("Une catégorie portant ce nom existe déjà !");
}
return;
}
var cat=scase.cats.newCat(name);
if (cat) {
scases.save();
show_scase(scase,cat.name);
}
}
$('#add_cat_modal').modal('hide');
}
on_show_add_cat_modal=function () {
$('#add_cat_name').focus();
}
on_close_add_cat_modal=function () {
$('#add_cat_modal form')[0].reset();
}
/***********************
* Rename cat
**********************/
on_rename_cat_btn_click=function(event) {
navbar_collapse_hide();
$('#rename_cat_modal').data('cat',event.data.cat.name);
$('#rename_cat_name')[0].value=event.data.cat.name;
$('#rename_cat_modal').modal('show');
}
on_valid_rename_cat_modal=function (e) {
e.preventDefault();
var name=$('#rename_cat_name')[0].value;
if (name=='') {
alert("Vous devez saisir le nouveau nom de la catégorie !");
return;
}
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
if (scase.cats.byName(name)) {
var namesake=scase.cats.byName(name);
if (namesake.removed) {
alert("Une catégorie de se nom existe dans la corbeille !");
}
else {
alert("Cette catégorie existe déjà !");
}
return;
}
var cat=scase.cats.renameCat($('#rename_cat_modal').data('cat'),name);
if (cat) {
scases.save();
show_scase(scase,cat.name);
}
}
$('#rename_cat_modal').modal('hide');
}
on_show_rename_cat_modal=function () {
$('#rename_cat_name').focus();
}
on_close_rename_cat_modal=function () {
$('#rename_cat_modal form')[0].reset();
}
/***********************
* Delete cat
**********************/
on_delete_cat_btn_click=function(event) {
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=event.data.cat.name;
myconfirm('Voulez-vous vraiment supprimer la catégorie '+cat+' ?',
function(data) {
scase.cats.removeCat(cat);
scases.save();
show_scase(scase);
});
}
}
on_restore_cat_btn_click=function(event) {
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=event.data.cat.name;
myconfirm('Voulez-vous vraiment restaure la catégorie '+cat+' ?',
function(data) {
scase.cats.restoreCat(cat);
scases.save();
show_scase(scase);
});
}
}
/************************
* Check/Uncheck thing
***********************/
on_li_click=function(event) {
if (event.target.tagName!='LI') {
return;
}
var li=$(this);
if (li.hasClass('checked')) {
li.removeClass('checked');
}
else {
li.addClass('checked');
}
var ul=li.parent();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=scase.cats.byName(ul.data('cat'));
if (cat) {
var thing=cat.byLabel(li.data('label'));
if (thing) {
thing.setChecked(li.hasClass('checked'));
scases.save();
}
show_scase(scase,cat.name);
}
}
}
/***********************
* Add thing
**********************/
on_li_add_click=function(event) {
var li=$(this);
var cat=li.parent().data('cat');
var modal=$('#add_thing_modal');
modal.data('cat',cat);
modal.modal('show');
}
on_valid_add_thing_modal=function (e) {
e.preventDefault();
var modal=$('#add_thing_modal');
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=scase.cats.byName(modal.data('cat'));
if (cat) {
var things=[];
var labels=[];
var error=false;
var add_thing_nbs=$('input.add_thing_nb');
$('input.add_thing_label').each(function(idx,input) {
var label=$(input).val();
if (label && label!='') {
if (labels.indexOf(label)>-1) {
alert("Deux élements ne peuvent porter le même nom !");
error=true;
return;
}
if (cat.byLabel(label)) {
alert("L'élément '"+label+"' existe déjà !");
error=true;
return;
}
var nb=1;
if (add_thing_nbs[idx]) {
nb=parseInt($(add_thing_nbs[idx]).val());
if (!nb || nb==0) {
nb=1;
}
}
things.push({
'label': label,
'nb': nb
});
labels.push(label);
}
});
if (error) {
return;
}
if (things.length==0) {
alert("Vous devez saisir au moins un nom d'élément !");
return;
}
for (idx in things) {
cat.newThing(things[idx]['label'], things[idx]['nb']);
}
scases.save();
show_scase(scase,cat.name);
}
}
modal.modal('hide');
}
on_show_add_thing_modal=function () {
$('.add_thing_other').remove();
$('input.add_thing_label').val('');
$('input.add_thing_nb').val('');
$('input.add_thing_label').first().focus();
}
on_close_add_thing_modal=function () {
$('#add_thing_modal form')[0].reset();
}
on_add_thing_label_focus=function(event) {
if ($('input.add_thing_label').last()[0] == event.target) {
var new_input_group=$('<div class="form-group add_thing_other"></div>');
var new_input_label=$('<input type="text" class="form-control add_thing_label" placeholder="Encore un ?"/>');
var new_input_nb=$('<input type="number" class="form-control add_thing_nb" placeholder="Nb"/>');
new_input_group.append(new_input_label);
new_input_group.append(' ');
new_input_group.append(new_input_nb);
new_input_label.bind('focus', on_add_thing_label_focus);
$(event.target).parent().after(new_input_group);
}
}
/***********************
* Edit thing
**********************/
on_edit_thing_btn_click=function(event) {
navbar_collapse_hide();
$('#edit_thing_modal').data('cat',event.data.cat.name);
$('#edit_thing_modal').data('thing',event.data.thing.label);
$('#edit_thing_label').val(event.data.thing.label);
$('#edit_thing_nb').val(event.data.thing.nb);
$('#edit_thing_modal').modal('show');
}
on_valid_edit_thing_modal=function (e) {
e.preventDefault();
var label=$('#edit_thing_label').val();
if (label=='') {
alert("Vous devez saisir le nouveau nom de l'élément !");
return;
}
var nb=parseInt($('#edit_thing_nb').val());
if (!nb || nb==0) {
nb=1;
}
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=scase.cats.byName($('#edit_thing_modal').data('cat'));
if (cat) {
if (label!=$('#edit_thing_modal').data('thing')) {
var namesake=cat.byLabel(label);
if (namesake) {
if (namesake.removed) {
alert("Un élément de ce nom existe dans la corbeille !");
}
else {
alert("Un élément de ce nom existe déjà !");
}
return;
}
var thing=cat.renameThing($('#edit_thing_modal').data('thing'),label);
}
else {
var thing=cat.byLabel(label);
}
if (thing) {
thing.setNb(nb);
scases.save();
show_scase(scase,cat.name);
}
}
}
$('#edit_thing_modal').modal('hide');
}
on_show_edit_thing_modal=function () {
$('#edit_thing_label').focus();
}
on_close_edit_thing_modal=function () {
$('#edit_thing_modal form')[0].reset();
}
/***********************
* Delete thing
**********************/
on_delete_thing_btn_click=function(event) {
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=scase.cats.byName(event.data.cat.name);
if (cat) {
var thing=event.data.thing.label;
myconfirm("Voulez-vous vraiment supprimer l'élément "+thing+" ?",
function(data) {
cat.removeThing(thing);
scases.save();
show_scase(scase,cat.name);
});
}
}
}
on_restore_thing_btn_click=function(event) {
navbar_collapse_hide();
var scase=scases.byName($('#cats').data('scase'));
if (scase) {
var cat=scase.cats.byName(event.data.cat.name);
if (cat) {
var thing=event.data.thing.label;
myconfirm("Voulez-vous vraiment restaurer l'élément "+thing+" ?",
function(data) {
cat.restoreThing(thing);
scases.save();
show_scase(scase,cat.name);
});
}
}
}
/********************
* Show one scase
*******************/
show_cat=function(cat,displayed) {
var panel=$('<div class="panel panel-default"></div>');
var panel_heading=$('<div class="panel-heading" role="tab"></div>');
var panel_title=$('<h4 class="panel-title">'+cat.name+' </h4>');
panel_title.bind('click',on_title_click);
var stats=cat.stats();
var tag=$('<span class="count-tag pull-right"></span>');
if (stats.things==stats.done) {
tag.append($('<span class="label label-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></span>'));
}
else {
tag.append($('<span class="badge">'+stats.done+' / '+stats.things+'</span>'));
}
var delete_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-trash"></button>');
delete_btn.bind('click',{'cat': cat},on_delete_cat_btn_click);
tag.append(delete_btn);
var rename_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-edit"></button>');
rename_btn.bind('click',{'cat': cat},on_rename_cat_btn_click);
tag.append(rename_btn);
panel_title.append(tag);
panel_heading.append(panel_title);
panel.append(panel_heading);
var panel_collapse=$('<div class="panel-collapse collapse" role="tabpanel"></div>');
if (displayed) {
panel_collapse.addClass('in');
}
var ul=$('<ul class="list-group" data-cat="'+cat.name+'"></ul>');
for (idx in cat.things) {
if (cat.things[idx].removed) {
continue;
}
var li=$('<li class="list-group-item checkable" data-label="'+cat.things[idx].label+'">'+cat.things[idx].label+'</li>');
if (cat.things[idx].nb>1) {
li.append(' <em>('+cat.things[idx].nb+')</em>');
}
if (cat.things[idx].checked) {
li.addClass('checked');
}
li.bind('click',on_li_click);
var li_actions=$('<span class="actions pull-right"></span>');
var delete_el_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-trash"></button>');
delete_el_btn.bind('click',{'cat': cat,'thing': cat.things[idx]},on_delete_thing_btn_click);
li_actions.append(delete_el_btn);
var edit_el_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-edit"></button>');
edit_el_btn.bind('click',{'cat': cat,'thing': cat.things[idx]},on_edit_thing_btn_click);
li_actions.append(edit_el_btn);
li.append(li_actions);
ul.append(li);
}
var li=$('<li class="list-group-item"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter un élément</li>');
li.bind('click',on_li_add_click);
ul.append(li);
panel_collapse.append(ul);
panel.append(panel_collapse);
$('#cats').append(panel);
}
show_scase=function(scase,display_cat) {
clear_page('<h3><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> '+scase.name+'</h3><div class="panel-group" id="cats" role="tablist" aria-multiselectable="true" data-scase="'+scase.name+'"></div>');
scase.cats.each(function(idx,cat) {
if (cat.removed) {
return;
}
show_cat(cat,(cat.name==display_cat));
});
show_menu('scase');
}
on_back_to_scases_btn_click=function(e) {
e.preventDefault();
navbar_collapse_hide();
show_scases();
}
/********************
* Show scase trash
*******************/
show_scase_trash=function(scase,display_cat) {
clear_page('<h3><span class="glyphicon glyphicon-trash" aria-hidden="true"></span>'+scase.name+' : Corbeille <button class="btn btn-default btn-xs" id="back_btn"><span class="glyphicon glyphicon-arrow-left"></button></h3><div class="panel-group" id="cats" role="tablist" aria-multiselectable="true" data-scase="'+scase.name+'"></div>');
$('#content h3 #back_btn').bind('click', {'scase': scase}, function(event) {
show_scase(event.data.scase);
});
scase.cats.each(function(idx,cat) {
show_cat_trash(cat,(cat.name==display_cat));
});
if ($('#cats .panel').length==0) {
$('#content').append('<p class="center">La corbeille est vide.</p>');
}
show_menu('scase');
}
show_cat_trash=function(cat,displayed) {
var panel=$('<div class="panel panel-default"></div>');
var panel_heading=$('<div class="panel-heading" role="tab"></div>');
var panel_title=$('<h4 class="panel-title">'+cat.name+' </h4>');
var tag=$('<span class="count-tag pull-right"></span>');
panel_title.append(tag);
panel_heading.append(panel_title);
panel.append(panel_heading);
if (cat.removed) {
var stats=cat.stats();
tag.append($('<span class="badge">'+stats.things+'</span>'));
var restore_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-ok"></button>');
restore_btn.bind('click',{'cat': cat},on_restore_cat_btn_click);
tag.append(restore_btn);
}
else {
var deleted_things=[];
for (idx in cat.things) {
if (cat.things[idx].removed) {
deleted_things.push(cat.things[idx]);
}
}
if (deleted_things.length==0) {
return true;
}
panel_title.bind('click',on_title_click);
tag.append($('<span class="badge">'+deleted_things.length+'</span>'));
var panel_collapse=$('<div class="panel-collapse collapse" role="tabpanel"></div>');
if (displayed) {
panel_collapse.addClass('in');
}
var ul=$('<ul class="list-group" data-cat="'+cat.name+'"></ul>');
for (idx in deleted_things) {
var li=$('<li class="list-group-item" data-label="'+deleted_things[idx].label+'">'+deleted_things[idx].label+'</li>');
var li_actions=$('<span class="actions pull-right"></span>');
var restore_el_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-ok"></button>');
restore_el_btn.bind('click',{'cat': cat,'thing': deleted_things[idx]},on_restore_thing_btn_click);
li_actions.append(restore_el_btn);
li.append(li_actions);
ul.append(li);
}
panel_collapse.append(ul);
panel.append(panel_collapse);
}
$('#cats').append(panel);
}
on_back_to_scase_btn_click=function(e) {
e.preventDefault();
navbar_collapse_hide();
show_scase(e.data.scase);
}
/********************
* Show scases
*******************/
show_scases=function() {
clear_page('<h3>Vos valises</h3><ul class="list-group" id="scases"></ul>');
scases.each(function(idx,scase) {
if (scase.removed) {
return;
}
var stats=scase.stats();
var tag='<span class="count-tag pull-right">';
if (stats.things==stats.done) {
tag+='<span class="label label-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></span>';
}
else {
tag+='<span class="badge">'+stats.done+' / '+stats.things+'</span>';
}
tag+='</span>';
var li=$('<li class="list-group-item" data-name="'+scase.name+'"><span class="scase-name"><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> '+scase.name+'</span>'+tag+'</li>');
li.bind('click',on_scase_click);
$('#scases').append(li);
});
show_menu('scases');
}
on_scase_click=function(event) {
var li=$(this);
var scase=scases.byName(li.data('name'));
show_scase(scase);
}
/********************
* Show scases trash
*******************/
show_scases_trash=function() {
clear_page('<h3>Corbeille <button class="btn btn-default btn-xs" id="back_btn"><span class="glyphicon glyphicon-arrow-left"></button></h3><ul class="list-group" id="scases"></ul>');
$('#content h3 #back_btn').bind('click', function(event) {
show_scases();
});
scases.each(function(idx,scase) {
if (!scase.removed) {
return;
}
var stats=scase.stats();
var tags=$('<span class="count-tag pull-right"></span>');
tags.append('<span class="badge">'+stats.things+'</span>');
var restore_btn=$('<button class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-ok"></button>');
restore_btn.bind('click',{'scase': scase},on_restore_scase_btn_click);
tags.append(restore_btn);
var li=$('<li class="list-group-item" data-name="'+scase.name+'"><span class="scase-name"><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> '+scase.name+'</span></li>');
li.append(tags);
$('#scases').append(li);
});
if ($('#scases li').length==0) {
$('#content').append('<p class="center">Aucune valise dans la corbeille.</p>');
}
show_menu('scases');
}
on_scases_trash_btn=function(e) {
e.preventDefault();
navbar_collapse_hide();
show_scases_trash();
}
clear_page=function(new_content) {
if (new_content) {
$('#content').html(new_content);
}
else {
$('#content').html('');
}
}
/************************
* Show menu
***********************/
show_menu=function(menu) {
$('.menu').css('display','none');
$('.menu-'+menu).css('display','block');
}
/*******************
* pleaseWaitDialog
*******************/
pleaseWaitShow=function() {
$('#please_wait_modal').modal('show');
}
pleaseWaitHide=function() {
$('#please_wait_modal').modal('hide');
}
/****************
* Nav bars
****************/
navbar_collapse_hide=function() {
if ($('#navbar-top-collapse').hasClass('in')) {
$('#navbar-top-collapse').collapse('hide');
}
}
/********************
* Clear local data
********************/
clear_local_data=function() {
navbar_collapse_hide();
myconfirm('Etes-vous sûre de vouloir supprimer les données locales ?',on_confirm_clear_local_data);
}
on_confirm_clear_local_data=function(data) {
delete localStorage.scases;
location.reload();
}
/*******************************
* Import/Export local data
*******************************/
export_local_data=function() {
navbar_collapse_hide();
$('#export_local_data').attr('href','data:application/json;base64,' + btoa(JSON.stringify(scases.export())));
}
import_local_data=function() {
navbar_collapse_hide();
var input=$('<input type="file" accept="application/json">');
input.css('display', 'none');
input.bind('change',{'input': input},function(e) {
pleaseWaitShow();
var input=e.data.input;
var file=input.prop('files')[0];
if (file) {
var reader = new FileReader();
$(reader).bind('load',function(e) {
if ($.type(e.target.result)=='string') {
if (e.target.result.startsWith('data:application/json;base64,')) {
try {
json_data=atob(e.target.result.replace('data:application/json;base64,',''));
data=JSON.parse(json_data);
pleaseWaitHide();
myconfirm('Etes-vous sûre de vouloir écraser vos données locales par celle issues de ce fichier ?',function() {
scases.save();
var backData=localStorage.scases;
localStorage.scases=json_data;
scases=new SCaseList();
scases.loadFromLocalStorage(backData);
show_scases();
});
}
catch (e) {
alert('Impossible de décodé le fichier.');
pleaseWaitHide();
}
}
else {
alert('Fichier invalide.');
pleaseWaitHide();
}
}
});
reader.readAsDataURL(file);
}
});
$('body').append(input);
input[0].click();
}
/*********************
* Activate
*********************/
$( document ).ready( function() {
pleaseWaitShow();
if(typeof(localStorage)!=="undefined"){
scases=new SCaseList();
scases.loadFromLocalStorage();
show_scases();
}
else {
alert('Local storage not supported !');
pleaseWaitHide();
return;
}
$('#clear_local_data').bind('click',clear_local_data);
$('#import_local_data').bind('click',import_local_data);
$('#export_local_data').bind('click',export_local_data);
$('#add_scase_btn').bind('click',on_add_scase_btn_click);
$('#add_scase_submit').bind('click',on_valid_add_scase_modal);
$("#add_scase_modal").on('shown.bs.modal',on_show_add_scase_modal);
$("#add_scase_modal").on('hidden.bs.modal',on_close_add_scase_modal);
$("#add_scase_modal form").bind('submit',on_valid_add_scase_modal);
$('#scases_trash_btn').bind('click',on_scases_trash_btn);
$('#rename_scase_btn').bind('click',on_rename_scase_btn_click);
$('#rename_scase_submit').bind('click',on_valid_rename_scase_modal);
$("#rename_scase_modal").on('shown.bs.modal',on_show_rename_scase_modal);
$("#rename_scase_modal").on('hidden.bs.modal',on_close_rename_scase_modal);
$("#rename_scase_modal form").bind('submit',on_valid_rename_scase_modal);
$('#copy_scase_btn').bind('click',on_copy_scase_btn_click);
$('#copy_scase_submit').bind('click',on_valid_copy_scase_modal);
$("#copy_scase_modal").on('shown.bs.modal',on_show_copy_scase_modal);
$("#copy_scase_modal").on('hidden.bs.modal',on_close_copy_scase_modal);
$("#copy_scase_modal form").bind('submit',on_valid_copy_scase_modal);
$('#reset_scase_btn').bind('click',on_reset_scase_btn_click);
$('#delete_scase_btn').bind('click',on_delete_scase_btn_click);
$('#scase_trash_btn').bind('click',on_scase_trash_btn_click);
$('#add_cat_btn').bind('click',on_add_cat_btn_click);
$('#add_cat_submit').bind('click',on_valid_add_cat_modal);
$("#add_cat_modal").on('shown.bs.modal',on_show_add_cat_modal);
$("#add_cat_modal").on('hidden.bs.modal',on_close_add_cat_modal);
$("#add_cat_modal form").bind('submit',on_valid_add_cat_modal);
$('#rename_cat_submit').bind('click',on_valid_rename_cat_modal);
$("#rename_cat_modal").on('shown.bs.modal',on_show_rename_cat_modal);
$("#rename_cat_modal").on('hidden.bs.modal',on_close_rename_cat_modal);
$("#rename_cat_modal form").bind('submit',on_valid_rename_cat_modal);
$('#back_to_scases').bind('click',on_back_to_scases_btn_click);
$('input.add_thing_label').bind('focus',on_add_thing_label_focus);
$('#add_thing_submit').bind('click',on_valid_add_thing_modal);
$("#add_thing_modal").on('shown.bs.modal',on_show_add_thing_modal);
$("#add_thing_modal").on('hidden.bs.modal',on_close_add_thing_modal);
$("#add_thing_modal form").bind('submit',on_valid_add_thing_modal);
$('#edit_thing_submit').bind('click',on_valid_edit_thing_modal);
$("#edit_thing_modal").on('shown.bs.modal',on_show_edit_thing_modal);
$("#edit_thing_modal").on('hidden.bs.modal',on_close_edit_thing_modal);
$("#edit_thing_modal form").bind('submit',on_valid_edit_thing_modal);
$('#app-name').bind('click', show_scases);
pleaseWaitHide();
});

View file

@ -1,35 +0,0 @@
var _myconfirm={
'onconfirm': null,
'oncancel': null,
'data': null
};
myconfirm=function(question,onconfirm,oncancel,data) {
$('#confirm_modal #question').html(question);
_myconfirm={
'onconfirm': onconfirm,
'oncancel': oncancel,
'data': data
};
$('#confirm_modal').modal('show');
}
_myconfirm_on_valid_click=function(e) {
$('#confirm_modal').modal('hide');
console.log(jQuery.type(_myconfirm.onconfirm));
if (jQuery.type(_myconfirm.onconfirm) == 'function') {
_myconfirm.onconfirm(_myconfirm.data);
}
}
_myconfirm_on_cancel_click=function(e) {
$('#confirm_modal').modal('hide');
if (jQuery.type(_myconfirm.oncancel) == 'function') {
_myconfirm.oncancel(_myconfirm.data);
}
}
$( document ).ready( function() {
$('#confirm_modal_submit').bind('click',_myconfirm_on_valid_click);
$('#confirm_modal .cancel').bind('click',_myconfirm_on_cancel_click);
});

View file

@ -1,616 +0,0 @@
function SCaseList() {
lastChange=0;
this.importExampleData=function() {
var exampleData={
'Vacances': {
'Papier': {
'color': '#f00',
'things': [
{'label': 'Papier blanc', 'nb': 1 },
{'label': 'Stylo', 'nb': 3 },
{'label': "Carte d'identité", 'nb': 1 },
]
},
'Multimédia' : {
'color': '#0f0',
'things': [
{'label': 'Montre', 'nb': 1 },
{'label': 'Chargeur montre', 'nb': 1 },
{'label': 'PC portable', 'nb': 1 },
]
}
}
};
for (scaseName in exampleData) {
var scase=this.newSCase(scaseName);
for (catName in exampleData[scaseName]) {
var cat=scase.cats.newCat(catName);
for (idx in exampleData[scaseName][catName].things) {
cat.newThing(exampleData[scaseName][catName].things[idx]['label'],exampleData[scaseName][catName].things[idx]['nb']);
}
}
}
}
this.loadFromLocalStorage=function(backData) {
if (jQuery.type(localStorage.scases)!='undefined') {
try {
var data=JSON.parse(localStorage.scases);
this.lastChange=data.lastChange;
for (el in data.scases) {
this[el]=new SCase(false,false,data.scases[el]);
}
}
catch(e) {
for (el in this) {
if (this.isSCase(this[el])) {
delete this[el];
}
}
if (jQuery.type(backData)!='undefined') {
alert('Erreur en chargeant les données. Restauration des données précédentes');
localStorage.scases=backData;
return this.loadFromLocalStorage();
}
else {
myconfirm('Erreur en chargeant les données locales. On les purges ?',
function(data) {
delete localStorage.scases;
location.reload();
}
);
}
}
}
else {
myconfirm("<h2>Bienvenu !</h2><p>Souhaitez-vous charger les données d'exemple ?</p>",
function(scases) {
scases.importExampleData();
scases.save();
show_scases();
},
false,
this
);
}
}
this.export=function() {
return {
'lastChange': this.lastChange,
'scases': this.each(function(idx,scase) {
return scase.export();
})
};
}
this.import=function(data) {
ret={};
for (el in this) {
if (this.isSCase(this[el])) {
delete ret[el];
}
}
this.lastChange=data.lastChange;
for (el in data.scases) {
this[el]=new SCase(false,false,data.scases[el]);
}
return true;
}
this.save=function() {
localStorage.scases=JSON.stringify(this.export());
}
this.each=function(fct) {
var idx=0;
var ret={};
for (el in this) {
if(this.isSCase(this[el])) {
ret[el]=fct(idx++,this[el]);
}
}
return ret;
}
this.count=function() {
len=0;
this.each(function(idx,scase) {
len=len+1;
});
return len;
}
this.isSCase=function(el) {
return (jQuery.type(el)=='object' && jQuery.type(el.isSCase)=='function' && el.isSCase());
}
this.byName=function(name) {
for (el in this) {
if(this.isSCase(this[el])) {
if (this[el].name==name) {
return this[el];
}
}
}
return false;
}
this.removeSCase=function(name) {
for (el in this) {
if (this.isSCase(this[el]) && this[el].name==name) {
this[el].remove();
return true;
}
}
return false;
}
this.newSCase=function(name) {
if (this.byName(this[name])) {
var scase=this.byName(name);
if (scase.removed) {
scase.restore();
return true;
}
}
else {
var uuid=uuid||generate_uuid();
this[uuid]=new SCase(uuid,name);
return this[uuid];
}
return false;
}
this.renameSCase=function(name,newname) {
var scase=this.byName(name);
if (scase && !this.byName(newname)) {
scase.name=newname;
scase.lastChange=new Date().getTime();
return scase;
}
return false;
}
this.copySCase=function(name,newname) {
var orig_scase=this.byName(name);
if (this.isSCase(orig_scase) && !this.byName(newname)) {
var uuid=uuid||generate_uuid();
this[uuid]=new SCase(false,false,orig_scase.export());
this[uuid].uuid=uuid;
this[uuid].lastChange=new Date().getTime();
this[uuid].name=newname;
return this[uuid];
}
return false;
}
this.resetSCase=function(name) {
for (el in this) {
if (this.isSCase(this[el]) && this[el].name==name) {
return this[el].reset();
}
}
return false;
}
}
function SCase(uuid,name,data) {
this.uuid=uuid||generate_uuid();
this.name=name;
this.cats=new CatList();
this.lastChange=new Date().getTime();
this.removed=false;
this.isSCase=function() {
return true;
}
this.import=function(data) {
this.uuid=data.uuid || generate_uuid();
this.lastChange=data.lastChange || new Date().getTime();
this.name=decodeURIComponent(data.name);
this.removed=data.removed||false;
if (jQuery.type(data.cats) == 'object') {
this.cats=new CatList(data.cats);
}
return true;
}
this.export=function() {
return {
'uuid': this.uuid,
'lastChange': this.lastChange,
'name': encodeURIComponent(this.name),
'removed': this.removed,
'cats': this.cats.export()
};
}
this.byName=function(name) {
for (idx in this.cats) {
if (name==this.cats[idx].name) {
return this.cats[idx];
}
}
return false;
}
this.stats=function() {
var cats=0;
var things=0;
var things_done=0;
this.cats.each(function(cidx,cat) {
if (cat.removed) {
return true;
}
cats++;
for (idx in cat.things) {
if (cat.things[idx].removed) {
continue;
}
things++;
if (cat.things[idx].checked) {
things_done++;
}
}
});
return {
'cats': cats,
'things': things,
'done': things_done
}
}
this.reset=function() {
this.cats.each(function(idx,cat) {
for (idx in cat.things) {
if (cat.things[idx].checked) {
cat.things[idx].checked=false;
}
}
});
this.lastChange=new Date().getTime();
return true;
}
this.remove=function() {
this.removed=true;
this.lastChange=new Date().getTime();
}
this.restore=function() {
this.removed=false;
this.lastChange=new Date().getTime();
}
/*
* Contructor
*/
if (jQuery.type(data)=='object') {
try {
this.import(data);
}
catch (e) {
console.log(e);
alert('Une erreur est survenue en chargeant la valise '+this.name+' depuis le cache');
}
}
}
function CatList(data) {
this.export=function() {
return this.each(function(idx,cat) {
return cat.export();
});
}
this.import=function(data) {
for (el in this) {
if (this.isCat(this[el])) {
delete this[el];
}
}
for (el in data) {
this[el]=new Cat(el,false,false,data[el]);
}
return true;
}
this.each=function(fct) {
var idx=0;
var ret={};
for (el in this) {
if(this.isCat(this[el])) {
ret[el]=fct(idx++,this[el]);
}
}
return ret;
}
this.count=function() {
len=0;
this.each(function(idx,cat) {
len=len+1;
});
return len;
}
this.isCat=function(el) {
return (jQuery.type(el)=='object' && jQuery.type(el.isCat)=='function' && el.isCat());
}
this.byName=function(name) {
for (el in this) {
if(this.isCat(this[el])) {
if (this[el].name==name) {
return this[el];
}
}
}
return false;
}
this.newCat=function(name) {
if (this.byName(name)) {
var cat=this.byName(name);
if (cat.removed) {
cat.restore();
return true;
}
}
else {
var uuid=uuid||generate_uuid();
this[uuid]=new Cat(uuid,name);
return this[uuid];
}
return false;
}
this.renameCat=function(name,newname) {
var cat=this.byName(name);
if (cat && !this.byName(newname)) {
cat.name=newname;
cat.lastChange=new Date().getTime();
return cat;
}
return false;
}
this.removeCat=function(name) {
for (el in this) {
if (this.isCat(this[el]) && this[el].name==name) {
this[el].remove();
return true;
}
}
return false;
}
this.restoreCat=function(name) {
for (el in this) {
if (this.isCat(this[el]) && this[el].name==name && this[el].removed) {
this[el].restore();
return true;
}
}
return false;
}
/*
* Contructor
*/
if (jQuery.type(data)=='object') {
try {
this.import(data);
}
catch (e) {
console.log(e);
alert('Une erreur est survenue en chargeant la liste de catégorie depuis le cache');
}
}
}
function Cat(uuid,name,color,data) {
this.uuid=generate_uuid();
this.lastChange=new Date().getTime();
this.name=name;
this.color=color || '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6);
this.things={};
this.removed=false;
this.isCat=function() {
return true;
}
this.import=function(data) {
this.uuid=data.uuid || generate_uuid();
this.lastChange=data.lastChange||new Date().getTime();
this.name=decodeURIComponent(data.name);
this.color=data.color;
this.removed=data.removed||false;
if (jQuery.type(data.things) == 'object') {
for (tuuid in data.things) {
this.things[tuuid]=new Thing(tuuid);
this.things[tuuid].import(data.things[tuuid]);
}
}
return true;
}
this.export=function() {
var things={};
for (tuuid in this.things) {
things[tuuid]=this.things[tuuid].export();
}
return {
'uuid': this.uuid,
'lastChange': this.lastChange,
'name': encodeURIComponent(this.name),
'color': this.color,
'removed': this.removed,
'things': things
};
}
this.byLabel=function(label) {
for (idx in this.things) {
if (label==this.things[idx].label) {
return this.things[idx];
}
}
return false;
}
this.count=function() {
return keys(this.things).length;
}
this.stats=function() {
var count=0;
var done=0;
for (idx in this.things) {
if (this.things[idx].removed) {
continue;
}
if (this.things[idx].checked) {
done+=1;
}
count+=1;
}
return {
'things': count,
'done': done
};
}
this.newThing=function(label,nb) {
if (this.byLabel(label)) {
var thing=this.byLabel(label);
if (thing.removed) {
thing.restore();
thing.setChecked(false);
thing.setNb(nb);
return true;
}
}
else {
var uuid=generate_uuid();
this.things[uuid]=new Thing(uuid,label,nb);
return true;
}
return false;
}
this.renameThing=function(label,newlabel) {
var thing=this.byLabel(label);
if (thing && !this.byLabel(newlabel)) {
thing.label=newlabel;
thing.lastChange=new Date().getTime();
return thing;
}
return false;
}
this.removeThing=function(label) {
for (idx in this.things) {
if (this.things[idx].label==label) {
this.things[idx].remove();
return true;
}
}
return false;
}
this.restoreThing=function(label) {
for (idx in this.things) {
if (this.things[idx].label==label && this.things[idx].removed) {
this.things[idx].restore();
return true;
}
}
return false;
}
this.remove=function() {
this.removed=true;
this.lastChange=new Date().getTime();
}
this.restore=function() {
this.removed=false;
this.lastChange=new Date().getTime();
}
/*
* Contructor
*/
if (jQuery.type(data)=='object') {
try {
this.import(data);
}
catch (e) {
console.log(e);
alert('Une erreur est survenue en chargeant la catégorie catégorie '+this.name+' depuis le cache');
}
}
}
function Thing(uuid,label,nb,checked) {
this.uuid=uuid||generate_uuid();
this.lastChange=new Date().getTime();
this.label=label;
this.nb=nb || 1;
this.checked=checked;
this.removed=false;
this.import=function(data) {
this.uuid=data.uuid||generate_uuid();
this.lastChange=data.lastChange||new Date().getTime();
this.label=decodeURIComponent(data.label),
this.nb=data.nb||1;
this.checked=data.checked;
this.removed=data.removed||false;
}
this.export=function() {
return {
'uuid': this.uuid,
'lastChange': this.lastChange,
'label': encodeURIComponent(this.label),
'nb': this.nb,
'checked': this.checked,
'removed': this.removed,
};
}
this.setNb=function(nb) {
this.nb=nb;
this.lastChange=new Date().getTime();
}
this.setChecked=function(value) {
this.checked=value;
this.lastChange=new Date().getTime();
}
this.remove=function() {
this.removed=true;
this.lastChange=new Date().getTime();
}
this.restore=function() {
this.removed=false;
this.lastChange=new Date().getTime();
}
}

9
public_html/index.php Normal file
View file

@ -0,0 +1,9 @@
<?php
include '../includes/core.php';
use EesyPHP\Url;
Url :: handle_request('');
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

4
setup.cfg Normal file
View file

@ -0,0 +1,4 @@
[codespell]
ignore-words-list=fro,hass
exclude-file=.codespell-exclusions
quiet-level=2

128
src/Auth/API.php Normal file
View file

@ -0,0 +1,128 @@
<?php
namespace MySC\Auth;
use EesyPHP\Auth;
use EesyPHP\Auth\Method;
use EesyPHP\Log;
use EesyPHP\Url;
use EesyPHP\Tpl;
use MySC\Db\AuthToken;
use function EesyPHP\vardump;
class API extends Method {
protected static $initialized = false;
/**
* Initialize
* @return boolean
*/
public static function init() {
if (php_sapi_name() != "cli" && !self :: $initialized) {
Url :: add_url_handler(
'#^login$#',
['MySC\\Auth\\API', 'handle_login'],
null,
false,
true,
true
);
self :: $initialized = true;
}
return true;
}
/**
* Log user
* @param bool $force Force user authentication
* @return \EesyPHP\Auth\User|null
*/
public static function login($force=false) {
$user = null;
if (isset($_REQUEST['username']) && isset($_REQUEST['password'])) {
Log::debug("API::login(): Try to authenticate user by username & password");
$user = Auth :: authenticate($_REQUEST['username'], $_REQUEST['password']);
if (!$user)
Tpl :: add_error(_('Invalid username or password.'));
}
elseif (isset($_REQUEST['username']) && isset($_REQUEST['token'])) {
Log::debug("API::login(): Try to authenticate user by token");
$auth_token = AuthToken :: get($_REQUEST['token']);
Log::debug("API::login(): auth token = %s", vardump($auth_token));
if (!$auth_token || $auth_token->username != $_REQUEST['username'])
Tpl :: add_error(_('Invalid authentication token.'));
elseif ($auth_token->expired()) {
Tpl :: add_error(_('Authentication token expired.'));
$auth_token->delete();
}
else {
Log::debug("API::login(): auth token valid, retrieve corresponding user");
$user = Auth::get_user($auth_token->username);
if (!$user) {
Tpl :: add_error(_('Your account appears to have been deleted.'));
$auth_token->delete();
}
Log::debug("login(): authenticated as %s via token", $user);
}
}
return $user;
}
/**
* The login form view
* @param \EesyPHP\UrlRequest $request
* @return void
*/
public static function handle_login($request) {
$user = Auth :: login(false, '\\mysc\\auth\\api');
if ($user)
Tpl :: display_ajax_return([
"success" => true,
"token" => AuthToken :: create($user) -> token,
"username" => $user -> username,
"name" => $user->name,
"mail" => $user->mail,
]);
Tpl :: display_ajax_return([
"success" => false,
]);
}
/**
* The logout method
* @return void
*/
public static function logout() {
Log::debug("API::logout()");
if (isset($_REQUEST['token'])) {
$auth_token = AuthToken :: get($_REQUEST['token']);
Log::debug("API::logout(): auth token = %s", vardump($auth_token));
if (!$auth_token) {
Log::debug("API::logout(): unknown auth token");
}
else if (isset($_REQUEST['username']) && $auth_token->username != $_REQUEST['username']) {
Log::warning(
"API::logout(): bad auth token owner ('%s' vs '%s')",
$auth_token->username,
$_REQUEST['username']
);
}
else if ($auth_token->delete()) {
Log::debug("API::logout(): auth token deleted");
}
else {
Log::error("API::logout(): error deleting the auth token %s", $_REQUEST['token']);
}
}
else {
Log::warning("API::logout(): no token provided by the request");
}
Tpl :: display_ajax_return([
"success" => true,
]);
}
}

55
src/Db/AuthToken.php Normal file
View file

@ -0,0 +1,55 @@
<?php
namespace MySC\Db;
use EesyPHP\App;
use EesyPHP\Auth;
use EesyPHP\Db\AttrStr;
use EesyPHP\Db\AttrTimestamp;
use EesyPHP\Db\DbObject;
/**
* MySC database object
* @property string|null $token
* @property string|null $username
* @property \DateTime|null $creation_date
* @property \DateTime|null $expiration_date
* @property boolean $removed
*/
class AuthToken extends DbObject {
protected const TABLE = 'auth_tokens';
protected const PRIMARY_KEYS = ['token'];
protected const DEFAULT_ORDER = 'creation_date';
protected const DEFAULT_ORDER_DIRECTION = 'DESC';
protected static function get_schema() {
return [
'token' => new AttrStr(['required' => true, 'default' => '\\EesyPHP\\generate_uuid']),
'username' => new AttrStr(['required' => true]),
'creation_date' => new AttrTimestamp(['default' => 'time']),
'expiration_date' => new AttrTimestamp(
['default' => ['\\MySC\\Db\\AuthToken', 'generate_expiration_date']]
),
];
}
public static function generate_expiration_date() {
return time() + App::get('auth.auth_token.expiration_delay', null, 'int');
}
public static function create($user=null) {
$user = $user ? $user : Auth::user();
$token = new AuthToken();
$token -> username = $user->username;
$token->save();
return $token;
}
public function user() {
return User::get($this -> username);
}
public function expired() {
return $this -> expiration_date -> getTimestamp() <= time();
}
}

56
src/Db/Category.php Normal file
View file

@ -0,0 +1,56 @@
<?php
namespace MySC\Db;
use EesyPHP\Db\AttrStr;
/**
* SCase category database object
* @property string $uuid
* @property string $scase_uuid
* @property string $name
* @property string $color
* @property \DateTime|null $last_change
* @property boolean $removed
*/
class Category extends DbObject {
protected const TABLE = "categories";
protected const DEFAULT_ORDER = "name";
protected const DEFAULT_ORDER_DIRECTION = "ASC";
protected const POSSIBLE_ORDERS = ["name", "last_change"];
protected const UPDATABLE_FIELDS = ["name", "color"];
protected static function get_schema() {
return array_merge(
parent :: get_schema(),
[
"scase_uuid" => new AttrStr(["required" => true]),
"name" => new AttrStr(["required" => true]),
"color" => new AttrStr(["required" => true]),
]
);
}
public function to_json() {
$data = parent :: to_json();
$data["things"] = [];
foreach($this -> things() as $uuid => $thing)
$data["things"][$uuid] = $thing -> to_json();
return $data;
}
public static function from_json($data, $scase) {
$obj = parent :: _from_json($data);
$obj -> scase_uuid = $scase -> uuid;
return $obj;
}
public function things() {
return Thing :: list(["category_uuid" => $this -> uuid]);
}
public function delete_all_things() {
return Thing :: deleteAll(["category_uuid" => $this -> uuid]);
}
}

107
src/Db/DbObject.php Normal file
View file

@ -0,0 +1,107 @@
<?php
namespace MySC\Db;
use EesyPHP\Date;
use EesyPHP\Log;
use EesyPHP\Db\AttrBool;
use EesyPHP\Db\AttrStr;
use EesyPHP\Db\AttrTimestamp;
use function EesyPHP\vardump;
/**
* MySC database object
* @property string $uuid
* @property boolean $removed
* @property \DateTime|null $last_change
*/
class DbObject extends \EesyPHP\Db\DbObject {
protected const PRIMARY_KEYS = ['uuid'];
protected const UPDATABLE_FIELDS = [];
protected static function get_schema() {
return [
'uuid' => new AttrStr(['required' => true]),
'last_change' => new AttrTimestamp(['default' => 'time']),
'removed' => new AttrBool(['default' => false]),
];
}
public function to_json() {
$data = [
'uuid' => $this -> uuid,
'lastChange' => intval($this -> last_change -> format('Uv')),
'removed' => boolval($this -> removed),
];
foreach(static :: UPDATABLE_FIELDS as $field)
$data[$field] = $this -> $field;
return $data;
}
public static function _from_json($data) {
Log::debug(
"from_json(): input=%s",
vardump($data),
);
$class = get_called_class();
$obj = new $class();
$obj -> apply([
'uuid' => $data['uuid'],
'last_change' => Date :: from_timestamp($data['lastChange'] / 1000),
'removed' => $data['removed'] == "true",
]);
foreach(static :: UPDATABLE_FIELDS as $field)
$obj -> $field = $data[$field];
Log::debug(
"%s::from_json(): result=%s",
$obj,
vardump($obj->to_json()),
);
return $obj;
}
public function sync(&$other, &$updated) {
if ($this -> last_change >= $other -> last_change) {
Log::debug(
"%s::sync(): keep current (%s >= %s)",
$this,
Date::format($this->last_change),
Date::format($other->last_change),
);
return true;
}
Log::debug(
"%s::sync(): update from other (%s < %s) : %s",
$this,
Date::format($this->last_change),
Date::format($other->last_change),
vardump($other->to_json()),
);
$this -> apply([
'last_change' => $other -> last_change,
'removed' => $other -> removed,
]);
foreach(static :: UPDATABLE_FIELDS as $field)
$this -> $field = $other -> $field;
$updated = true;
return $this -> save();
}
/**
* List objects
* @param array<string,mixed> $where Where clauses as associative array of field name and value
* @return array<string,DbObject>|false
*/
public static function list($where=null) {
$objs = [];
foreach(parent :: list($where) as $obj)
$objs[$obj -> uuid] = $obj;
return $objs;
}
public function __toString() {
return get_called_class()."<".$this -> uuid.">";
}
}

81
src/Db/SCase.php Normal file
View file

@ -0,0 +1,81 @@
<?php
namespace MySC\Db;
use EesyPHP\Db\AttrStr;
use EesyPHP\Log;
/**
* SCase database object
* @property string $uuid
* @property string $name
* @property \DateTime|null $last_change
* @property boolean $removed
*/
class SCase extends DbObject {
protected const TABLE = 'scases';
protected const DEFAULT_ORDER = 'name';
protected const DEFAULT_ORDER_DIRECTION = 'ASC';
protected const POSSIBLE_ORDERS = ['name', 'last_change'];
protected const UPDATABLE_FIELDS = ['name'];
protected static function get_schema() {
return array_merge(
parent :: get_schema(),
[
'username' => new AttrStr(['required' => true]),
'name' => new AttrStr(['required' => true]),
]
);
}
public function to_json() {
$data = parent :: to_json();
$data["cats"] = [];
foreach($this -> categories() as $uuid => $cat)
$data["cats"][$uuid] = $cat -> to_json();
return $data;
}
public static function from_json($data, $user) {
$obj = parent :: _from_json($data);
$obj -> username = $user -> username;
return $obj;
}
/**
* Get scase's categories
* @return array<Category>|false
*/
public function categories() {
return Category :: list(['scase_uuid' => $this -> uuid]);
}
public function delete() {
foreach($this -> categories() as $category) {
if (!$category->delete_all_things()) {
Log::error(
"Db: error occurred deleting things of category '%s' (%s)",
$category->name, $category->uuid
);
return false;
}
if (!$category->delete()) {
Log::error(
"Db: error occurred deleting category '%s' (%s)",
$category->name, $category->uuid
);
return false;
}
}
if (!parent::delete()) {
Log::error(
"Db: error occurred deleting scases '%s' (%s)",
$this->name, $this->uuid
);
return false;
}
return true;
}
}

44
src/Db/Thing.php Normal file
View file

@ -0,0 +1,44 @@
<?php
namespace MySC\Db;
use EesyPHP\Db\AttrBool;
use EesyPHP\Db\AttrInt;
use EesyPHP\Db\AttrStr;
/**
* Thing database object
* @property string $uuid
* @property string $category_uuid
* @property string $label
* @property int $nb
* @property boolean $checked
* @property \DateTime|null $last_change
* @property boolean $removed
*/
class Thing extends DbObject {
protected const TABLE = 'things';
protected const DEFAULT_ORDER = 'label';
protected const DEFAULT_ORDER_DIRECTION = 'ASC';
protected const POSSIBLE_ORDERS = ['label', 'last_change'];
protected const UPDATABLE_FIELDS = ['label', 'nb', 'checked'];
protected static function get_schema() {
return array_merge(
parent :: get_schema(),
[
'category_uuid' => new AttrStr(['required' => true]),
'label' => new AttrStr(['required' => true]),
'nb' => new AttrInt(['required' => true, 'default' => 1]),
'checked' => new AttrBool(['required' => false, 'default' => false]),
]
);
}
public static function from_json($data, $category) {
$obj = parent :: _from_json($data);
$obj -> category_uuid = $category -> uuid;
return $obj;
}
}

39
src/Db/User.php Normal file
View file

@ -0,0 +1,39 @@
<?php
namespace MySC\Db;
use EesyPHP\Db\AttrStr;
use EesyPHP\Db\DbObject;
/**
* MySC database object
* @property string|null $username
* @property string|null $name
* @property string|null $password
* @property string|null $mail
* @property boolean $removed
*/
class User extends DbObject {
protected const TABLE = 'users';
protected const PRIMARY_KEYS = ['username'];
protected const DEFAULT_ORDER = 'name';
protected const DEFAULT_ORDER_DIRECTION = 'ASC';
protected static function get_schema() {
return [
'username' => new AttrStr(['required' => true]),
'name' => new AttrStr(['required' => true]),
'password' => new AttrStr(['required' => true]),
'mail' => new AttrStr(),
];
}
/**
* Get user's scases
* @return bool|SCase[]
*/
public function scases() {
return SCase :: list(['username' => $this -> username]);
}
}

0
static/.gitignore vendored Normal file
View file

View file

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 763 B

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because it is too large Load diff

3
static/lib/alertify/alertify.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,968 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer {
position: fixed;
z-index: 1981;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0;
margin: 0;
background-color: #252525;
opacity: .5;
}
.alertify .ajs-modal {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
padding: 0;
overflow-y: auto;
z-index: 1981;
}
.alertify .ajs-dialog {
position: relative;
margin: 5% auto;
min-height: 110px;
max-width: 500px;
padding: 24px 24px 0 24px;
outline: 0;
background-color: #fff;
}
.alertify .ajs-dialog.ajs-capture:before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
z-index: 1;
}
.alertify .ajs-reset {
position: absolute !important;
display: inline !important;
width: 0 !important;
height: 0 !important;
opacity: 0 !important;
}
.alertify .ajs-commands {
position: absolute;
right: 4px;
margin: -14px 24px 0 0;
z-index: 2;
}
.alertify .ajs-commands button {
display: none;
width: 10px;
height: 10px;
margin-left: 10px;
padding: 10px;
border: 0;
background-color: transparent;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.alertify .ajs-commands button.ajs-close {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAh0lEQVQYlY2QsQ0EIQwEB9cBAR1CJUaI/gigDnwR6NBL/7/xWLNrZ2b8EwGotVpr7eOitWa1VjugiNB7R1UPrKrWe0dEAHBbXUqxMQbeewDmnHjvyTm7C3zDwAUd9c63YQdUVdu6EAJzzquz7HXvTiklt+H9DQFYaxFjvDqllFyMkbXWvfpXHjJrWFgdBq/hAAAAAElFTkSuQmCC);
}
.alertify .ajs-commands button.ajs-maximize {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAOUlEQVQYlWP8//8/AzGAhYGBgaG4uBiv6t7eXkYmooxjYGAgWiELsvHYFMCcRX2rSXcjoSBiJDbAAeD+EGu+8BZcAAAAAElFTkSuQmCC);
}
.alertify .ajs-header {
margin: -24px;
margin-bottom: 0;
padding: 16px 24px;
background-color: #fff;
}
.alertify .ajs-body {
min-height: 56px;
}
.alertify .ajs-body .ajs-content {
padding: 16px 24px 16px 16px;
}
.alertify .ajs-footer {
padding: 4px;
margin-left: -24px;
margin-right: -24px;
min-height: 43px;
background-color: #fff;
}
.alertify .ajs-footer .ajs-buttons.ajs-primary {
text-align: right;
}
.alertify .ajs-footer .ajs-buttons.ajs-primary .ajs-button {
margin: 4px;
}
.alertify .ajs-footer .ajs-buttons.ajs-auxiliary {
float: left;
clear: none;
text-align: left;
}
.alertify .ajs-footer .ajs-buttons.ajs-auxiliary .ajs-button {
margin: 4px;
}
.alertify .ajs-footer .ajs-buttons .ajs-button {
min-width: 88px;
min-height: 35px;
}
.alertify .ajs-handle {
position: absolute;
display: none;
width: 10px;
height: 10px;
right: 0;
bottom: 0;
z-index: 1;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMS8xNEDQYmMAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQ0lEQVQYlaXNMQoAIAxD0dT7H657l0KX3iJuUlBUNOsPPCGJm7VDp6ryeMxMuDsAQH7owW3pyn3RS26iKxERMLN3ugOaAkaL3sWVigAAAABJRU5ErkJggg==);
-webkit-transform: scaleX(1) /*rtl:scaleX(-1)*/;
transform: scaleX(1) /*rtl:scaleX(-1)*/;
cursor: se-resize;
}
.alertify.ajs-no-overflow .ajs-body .ajs-content {
overflow: hidden !important;
}
.alertify.ajs-no-padding.ajs-maximized .ajs-body .ajs-content {
left: 0;
right: 0;
padding: 0;
}
.alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body {
margin-left: -24px;
margin-right: -24px;
}
.alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body .ajs-content {
padding: 0;
}
.alertify.ajs-no-padding.ajs-resizable .ajs-body .ajs-content {
left: 0;
right: 0;
}
.alertify.ajs-maximizable .ajs-commands button.ajs-maximize,
.alertify.ajs-maximizable .ajs-commands button.ajs-restore {
display: inline-block;
}
.alertify.ajs-closable .ajs-commands button.ajs-close {
display: inline-block;
}
.alertify.ajs-maximized .ajs-dialog {
width: 100% !important;
height: 100% !important;
max-width: none !important;
margin: 0 auto !important;
top: 0 !important;
left: 0 !important;
}
.alertify.ajs-maximized.ajs-modeless .ajs-modal {
position: fixed !important;
min-height: 100% !important;
max-height: none !important;
margin: 0 !important;
}
.alertify.ajs-maximized .ajs-commands button.ajs-maximize {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAASklEQVQYlZWQ0QkAMQhDtXRincOZX78KVtrDCwgqJNEoIB3MPLj7lRUROlpyVXGzby6zWuY+kz6tj5sBMTMAyVV3/595RbOh3cAXsww1raeiOcoAAAAASUVORK5CYII=);
}
.alertify.ajs-resizable .ajs-dialog,
.alertify.ajs-maximized .ajs-dialog {
padding: 0;
}
.alertify.ajs-resizable .ajs-commands,
.alertify.ajs-maximized .ajs-commands {
margin: 14px 24px 0 0;
}
.alertify.ajs-resizable .ajs-header,
.alertify.ajs-maximized .ajs-header {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 0;
padding: 16px 24px;
}
.alertify.ajs-resizable .ajs-body,
.alertify.ajs-maximized .ajs-body {
min-height: 224px;
display: inline-block;
}
.alertify.ajs-resizable .ajs-body .ajs-content,
.alertify.ajs-maximized .ajs-body .ajs-content {
position: absolute;
top: 50px;
right: 24px;
bottom: 50px;
left: 24px;
overflow: auto;
}
.alertify.ajs-resizable .ajs-footer,
.alertify.ajs-maximized .ajs-footer {
position: absolute;
left: 0;
right: 0;
bottom: 0;
margin: 0;
}
.alertify.ajs-resizable:not(.ajs-maximized) .ajs-dialog {
min-width: 548px;
}
.alertify.ajs-resizable:not(.ajs-maximized) .ajs-handle {
display: block;
}
.alertify.ajs-movable:not(.ajs-maximized) .ajs-header {
cursor: move;
}
.alertify.ajs-modeless .ajs-dimmer,
.alertify.ajs-modeless .ajs-reset {
display: none;
}
.alertify.ajs-modeless .ajs-modal {
overflow: visible;
max-width: none;
max-height: 0;
}
.alertify.ajs-modeless.ajs-pinnable .ajs-commands button.ajs-pin {
display: inline-block;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQklEQVQYlcWPMQ4AIAwCqU9u38GbcbHRWN1MvKQDhQFMEpKImGJA0gCgnYw0V0rwxseg5erT4oSkQVI5d9f+e9+xA0NbLpWfitPXAAAAAElFTkSuQmCC);
}
.alertify.ajs-modeless.ajs-unpinned .ajs-modal {
position: absolute;
}
.alertify.ajs-modeless.ajs-unpinned .ajs-commands button.ajs-pin {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAO0lEQVQYlWP8//8/AzGAiShV6AqLi4txGs+CLoBLMYbC3t5eRmyaWfBZhwwYkX2NTxPRvibKjRhW4wMAhxkYGbLu3pEAAAAASUVORK5CYII=);
}
.alertify.ajs-modeless:not(.ajs-unpinned) .ajs-body {
max-height: 500px;
overflow: auto;
}
.alertify.ajs-basic .ajs-header {
opacity: 0;
}
.alertify.ajs-basic .ajs-footer {
visibility: hidden;
}
.alertify.ajs-frameless .ajs-header {
position: absolute;
top: 0;
left: 0;
right: 0;
min-height: 60px;
margin: 0;
padding: 0;
opacity: 0;
z-index: 1;
}
.alertify.ajs-frameless .ajs-footer {
display: none;
}
.alertify.ajs-frameless .ajs-body .ajs-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog {
padding-top: 0;
}
.alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog .ajs-commands {
margin-top: 0;
}
.ajs-no-overflow {
overflow: hidden !important;
outline: none;
}
.ajs-no-overflow.ajs-fixed {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow-y: scroll!important;
}
.ajs-no-selection,
.ajs-no-selection * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media screen and (max-width: 568px) {
.alertify .ajs-dialog {
min-width: 150px;
}
.alertify:not(.ajs-maximized) .ajs-modal {
padding: 0 5%;
}
.alertify:not(.ajs-maximized).ajs-resizable .ajs-dialog {
min-width: initial;
min-width: auto /*IE fallback*/;
}
}
@-moz-document url-prefix() {
.alertify button:focus {
outline: 1px dotted #3593D2;
}
}
.alertify .ajs-dimmer,
.alertify .ajs-modal {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-property: opacity, visibility;
transition-property: opacity, visibility;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
-webkit-transition-duration: 250ms;
transition-duration: 250ms;
}
.alertify.ajs-hidden .ajs-dimmer,
.alertify.ajs-hidden .ajs-modal {
visibility: hidden;
opacity: 0;
}
.alertify.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-duration: 500ms;
animation-duration: 500ms;
}
.alertify.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-duration: 250ms;
animation-duration: 250ms;
}
.alertify .ajs-dialog.ajs-shake {
-webkit-animation-name: ajs-shake;
animation-name: ajs-shake;
-webkit-animation-duration: .1s;
animation-duration: .1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
@-webkit-keyframes ajs-shake {
0%,
100% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
10%,
30%,
50%,
70%,
90% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
20%,
40%,
60%,
80% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
}
@keyframes ajs-shake {
0%,
100% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
10%,
30%,
50%,
70%,
90% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
20%,
40%,
60%,
80% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
}
.alertify.ajs-slide.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-slideIn;
animation-name: ajs-slideIn;
-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.alertify.ajs-slide.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-slideOut;
animation-name: ajs-slideOut;
-webkit-animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
}
.alertify.ajs-zoom.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-zoomIn;
animation-name: ajs-zoomIn;
}
.alertify.ajs-zoom.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-zoomOut;
animation-name: ajs-zoomOut;
}
.alertify.ajs-fade.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-fadeIn;
animation-name: ajs-fadeIn;
}
.alertify.ajs-fade.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-fadeOut;
animation-name: ajs-fadeOut;
}
.alertify.ajs-pulse.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-pulseIn;
animation-name: ajs-pulseIn;
}
.alertify.ajs-pulse.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-pulseOut;
animation-name: ajs-pulseOut;
}
.alertify.ajs-flipx.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-flipInX;
animation-name: ajs-flipInX;
}
.alertify.ajs-flipx.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-flipOutX;
animation-name: ajs-flipOutX;
}
.alertify.ajs-flipy.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-flipInY;
animation-name: ajs-flipInY;
}
.alertify.ajs-flipy.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-flipOutY;
animation-name: ajs-flipOutY;
}
@-webkit-keyframes ajs-pulseIn {
0%,
20%,
40%,
60%,
80%,
100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
-webkit-transform: scale3d(0.97, 0.97, 0.97);
transform: scale3d(0.97, 0.97, 0.97);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes ajs-pulseIn {
0%,
20%,
40%,
60%,
80%,
100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
-webkit-transform: scale3d(0.97, 0.97, 0.97);
transform: scale3d(0.97, 0.97, 0.97);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@-webkit-keyframes ajs-pulseOut {
20% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
50%,
55% {
opacity: 1;
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
}
@keyframes ajs-pulseOut {
20% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
50%,
55% {
opacity: 1;
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
}
@-webkit-keyframes ajs-zoomIn {
0% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes ajs-zoomIn {
0% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@-webkit-keyframes ajs-zoomOut {
0% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
}
@keyframes ajs-zoomOut {
0% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
}
@-webkit-keyframes ajs-fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes ajs-fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes ajs-fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes ajs-fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-webkit-keyframes ajs-flipInX {
0% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@keyframes ajs-flipInX {
0% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@-webkit-keyframes ajs-flipOutX {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
opacity: 0;
}
}
@keyframes ajs-flipOutX {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
opacity: 0;
}
}
@-webkit-keyframes ajs-flipInY {
0% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@keyframes ajs-flipInY {
0% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@-webkit-keyframes ajs-flipOutY {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
opacity: 0;
}
}
@keyframes ajs-flipOutY {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
opacity: 0;
}
}
@-webkit-keyframes ajs-slideIn {
0% {
margin-top: -100%;
}
100% {
margin-top: 5%;
}
}
@keyframes ajs-slideIn {
0% {
margin-top: -100%;
}
100% {
margin-top: 5%;
}
}
@-webkit-keyframes ajs-slideOut {
0% {
margin-top: 5%;
}
100% {
margin-top: -100%;
}
}
@keyframes ajs-slideOut {
0% {
margin-top: 5%;
}
100% {
margin-top: -100%;
}
}
.alertify-notifier {
position: fixed;
width: 0;
overflow: visible;
z-index: 1982;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
.alertify-notifier .ajs-message {
position: relative;
width: 260px;
max-height: 0;
padding: 0;
opacity: 0;
margin: 0;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-duration: 250ms;
transition-duration: 250ms;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
.alertify-notifier .ajs-message.ajs-visible {
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
-webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: 1;
max-height: 100%;
padding: 15px;
margin-top: 10px;
}
.alertify-notifier .ajs-message.ajs-success {
background: rgba(91, 189, 114, 0.95);
}
.alertify-notifier .ajs-message.ajs-error {
background: rgba(217, 92, 92, 0.95);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
}
.alertify-notifier .ajs-message .ajs-close {
position: absolute;
top: 0;
right: 0;
width: 16px;
height: 16px;
cursor: pointer;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAABGdBTUEAALGPC/xhBQAAAFBJREFUGBl1j0EKADEIA+ve/P9f9bh1hEihNBfjVCO1v7RKVqJK4h8gM5cAPR42AkQEpSXPwMTyoi13n5N9YqJehm3Fnr7nL1D0ZEbD5OubGyC7a9gx+9eNAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center center;
background-color: rgba(0, 0, 0, 0.5);
border-top-right-radius: 2px;
}
.alertify-notifier.ajs-top {
top: 10px;
}
.alertify-notifier.ajs-bottom {
bottom: 10px;
}
.alertify-notifier.ajs-right {
right: 10px;
}
.alertify-notifier.ajs-right .ajs-message {
right: -320px;
}
.alertify-notifier.ajs-right .ajs-message.ajs-visible {
right: 290px;
}
.alertify-notifier.ajs-left {
left: 10px;
}
.alertify-notifier.ajs-left .ajs-message {
left: -300px;
}
.alertify-notifier.ajs-left .ajs-message.ajs-visible {
left: 0;
}
.alertify-notifier.ajs-center {
left: 50%;
}
.alertify-notifier.ajs-center .ajs-message {
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
}
.alertify-notifier.ajs-center .ajs-message.ajs-visible {
left: 50%;
-webkit-transition-timing-function: cubic-bezier(0.57, 0.43, 0.1, 0.65);
transition-timing-function: cubic-bezier(0.57, 0.43, 0.1, 0.65);
}
.alertify-notifier.ajs-center.ajs-top .ajs-message {
top: -300px;
}
.alertify-notifier.ajs-center.ajs-top .ajs-message.ajs-visible {
top: 0;
}
.alertify-notifier.ajs-center.ajs-bottom .ajs-message {
bottom: -300px;
}
.alertify-notifier.ajs-center.ajs-bottom .ajs-message.ajs-visible {
bottom: 0;
}
.ajs-no-transition.alertify .ajs-dimmer,
.ajs-no-transition.alertify .ajs-modal,
.ajs-no-transition.alertify .ajs-dialog {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
.ajs-no-transition.alertify-notifier .ajs-message {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
@media (prefers-reduced-motion: reduce) {
.alertify .ajs-dimmer,
.alertify .ajs-modal,
.alertify .ajs-dialog {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
.alertify-notifier .ajs-message {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,968 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer {
position: fixed;
z-index: 1981;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 0;
margin: 0;
background-color: #252525;
opacity: .5;
}
.alertify .ajs-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 0;
overflow-y: auto;
z-index: 1981;
}
.alertify .ajs-dialog {
position: relative;
margin: 5% auto;
min-height: 110px;
max-width: 500px;
padding: 24px 24px 0 24px;
outline: 0;
background-color: #fff;
}
.alertify .ajs-dialog.ajs-capture:before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: block;
z-index: 1;
}
.alertify .ajs-reset {
position: absolute !important;
display: inline !important;
width: 0 !important;
height: 0 !important;
opacity: 0 !important;
}
.alertify .ajs-commands {
position: absolute;
left: 4px;
margin: -14px 0 0 24px;
z-index: 2;
}
.alertify .ajs-commands button {
display: none;
width: 10px;
height: 10px;
margin-right: 10px;
padding: 10px;
border: 0;
background-color: transparent;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.alertify .ajs-commands button.ajs-close {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAh0lEQVQYlY2QsQ0EIQwEB9cBAR1CJUaI/gigDnwR6NBL/7/xWLNrZ2b8EwGotVpr7eOitWa1VjugiNB7R1UPrKrWe0dEAHBbXUqxMQbeewDmnHjvyTm7C3zDwAUd9c63YQdUVdu6EAJzzquz7HXvTiklt+H9DQFYaxFjvDqllFyMkbXWvfpXHjJrWFgdBq/hAAAAAElFTkSuQmCC);
}
.alertify .ajs-commands button.ajs-maximize {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAOUlEQVQYlWP8//8/AzGAhYGBgaG4uBiv6t7eXkYmooxjYGAgWiELsvHYFMCcRX2rSXcjoSBiJDbAAeD+EGu+8BZcAAAAAElFTkSuQmCC);
}
.alertify .ajs-header {
margin: -24px;
margin-bottom: 0;
padding: 16px 24px;
background-color: #fff;
}
.alertify .ajs-body {
min-height: 56px;
}
.alertify .ajs-body .ajs-content {
padding: 16px 16px 16px 24px;
}
.alertify .ajs-footer {
padding: 4px;
margin-right: -24px;
margin-left: -24px;
min-height: 43px;
background-color: #fff;
}
.alertify .ajs-footer .ajs-buttons.ajs-primary {
text-align: left;
}
.alertify .ajs-footer .ajs-buttons.ajs-primary .ajs-button {
margin: 4px;
}
.alertify .ajs-footer .ajs-buttons.ajs-auxiliary {
float: right;
clear: none;
text-align: right;
}
.alertify .ajs-footer .ajs-buttons.ajs-auxiliary .ajs-button {
margin: 4px;
}
.alertify .ajs-footer .ajs-buttons .ajs-button {
min-width: 88px;
min-height: 35px;
}
.alertify .ajs-handle {
position: absolute;
display: none;
width: 10px;
height: 10px;
left: 0;
bottom: 0;
z-index: 1;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMS8xNEDQYmMAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQ0lEQVQYlaXNMQoAIAxD0dT7H657l0KX3iJuUlBUNOsPPCGJm7VDp6ryeMxMuDsAQH7owW3pyn3RS26iKxERMLN3ugOaAkaL3sWVigAAAABJRU5ErkJggg==);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
cursor: sw-resize;
}
.alertify.ajs-no-overflow .ajs-body .ajs-content {
overflow: hidden !important;
}
.alertify.ajs-no-padding.ajs-maximized .ajs-body .ajs-content {
right: 0;
left: 0;
padding: 0;
}
.alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body {
margin-right: -24px;
margin-left: -24px;
}
.alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body .ajs-content {
padding: 0;
}
.alertify.ajs-no-padding.ajs-resizable .ajs-body .ajs-content {
right: 0;
left: 0;
}
.alertify.ajs-maximizable .ajs-commands button.ajs-maximize,
.alertify.ajs-maximizable .ajs-commands button.ajs-restore {
display: inline-block;
}
.alertify.ajs-closable .ajs-commands button.ajs-close {
display: inline-block;
}
.alertify.ajs-maximized .ajs-dialog {
width: 100% !important;
height: 100% !important;
max-width: none !important;
margin: 0 auto !important;
top: 0 !important;
right: 0 !important;
}
.alertify.ajs-maximized.ajs-modeless .ajs-modal {
position: fixed !important;
min-height: 100% !important;
max-height: none !important;
margin: 0 !important;
}
.alertify.ajs-maximized .ajs-commands button.ajs-maximize {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAASklEQVQYlZWQ0QkAMQhDtXRincOZX78KVtrDCwgqJNEoIB3MPLj7lRUROlpyVXGzby6zWuY+kz6tj5sBMTMAyVV3/595RbOh3cAXsww1raeiOcoAAAAASUVORK5CYII=);
}
.alertify.ajs-resizable .ajs-dialog,
.alertify.ajs-maximized .ajs-dialog {
padding: 0;
}
.alertify.ajs-resizable .ajs-commands,
.alertify.ajs-maximized .ajs-commands {
margin: 14px 0 0 24px;
}
.alertify.ajs-resizable .ajs-header,
.alertify.ajs-maximized .ajs-header {
position: absolute;
top: 0;
right: 0;
left: 0;
margin: 0;
padding: 16px 24px;
}
.alertify.ajs-resizable .ajs-body,
.alertify.ajs-maximized .ajs-body {
min-height: 224px;
display: inline-block;
}
.alertify.ajs-resizable .ajs-body .ajs-content,
.alertify.ajs-maximized .ajs-body .ajs-content {
position: absolute;
top: 50px;
left: 24px;
bottom: 50px;
right: 24px;
overflow: auto;
}
.alertify.ajs-resizable .ajs-footer,
.alertify.ajs-maximized .ajs-footer {
position: absolute;
right: 0;
left: 0;
bottom: 0;
margin: 0;
}
.alertify.ajs-resizable:not(.ajs-maximized) .ajs-dialog {
min-width: 548px;
}
.alertify.ajs-resizable:not(.ajs-maximized) .ajs-handle {
display: block;
}
.alertify.ajs-movable:not(.ajs-maximized) .ajs-header {
cursor: move;
}
.alertify.ajs-modeless .ajs-dimmer,
.alertify.ajs-modeless .ajs-reset {
display: none;
}
.alertify.ajs-modeless .ajs-modal {
overflow: visible;
max-width: none;
max-height: 0;
}
.alertify.ajs-modeless.ajs-pinnable .ajs-commands button.ajs-pin {
display: inline-block;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQklEQVQYlcWPMQ4AIAwCqU9u38GbcbHRWN1MvKQDhQFMEpKImGJA0gCgnYw0V0rwxseg5erT4oSkQVI5d9f+e9+xA0NbLpWfitPXAAAAAElFTkSuQmCC);
}
.alertify.ajs-modeless.ajs-unpinned .ajs-modal {
position: absolute;
}
.alertify.ajs-modeless.ajs-unpinned .ajs-commands button.ajs-pin {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAO0lEQVQYlWP8//8/AzGAiShV6AqLi4txGs+CLoBLMYbC3t5eRmyaWfBZhwwYkX2NTxPRvibKjRhW4wMAhxkYGbLu3pEAAAAASUVORK5CYII=);
}
.alertify.ajs-modeless:not(.ajs-unpinned) .ajs-body {
max-height: 500px;
overflow: auto;
}
.alertify.ajs-basic .ajs-header {
opacity: 0;
}
.alertify.ajs-basic .ajs-footer {
visibility: hidden;
}
.alertify.ajs-frameless .ajs-header {
position: absolute;
top: 0;
right: 0;
left: 0;
min-height: 60px;
margin: 0;
padding: 0;
opacity: 0;
z-index: 1;
}
.alertify.ajs-frameless .ajs-footer {
display: none;
}
.alertify.ajs-frameless .ajs-body .ajs-content {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog {
padding-top: 0;
}
.alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog .ajs-commands {
margin-top: 0;
}
.ajs-no-overflow {
overflow: hidden !important;
outline: none;
}
.ajs-no-overflow.ajs-fixed {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow-y: scroll!important;
}
.ajs-no-selection,
.ajs-no-selection * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media screen and (max-width: 568px) {
.alertify .ajs-dialog {
min-width: 150px;
}
.alertify:not(.ajs-maximized) .ajs-modal {
padding: 0 5%;
}
.alertify:not(.ajs-maximized).ajs-resizable .ajs-dialog {
min-width: initial;
min-width: auto /*IE fallback*/;
}
}
@-moz-document url-prefix() {
.alertify button:focus {
outline: 1px dotted #3593D2;
}
}
.alertify .ajs-dimmer,
.alertify .ajs-modal {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-property: opacity, visibility;
transition-property: opacity, visibility;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
-webkit-transition-duration: 250ms;
transition-duration: 250ms;
}
.alertify.ajs-hidden .ajs-dimmer,
.alertify.ajs-hidden .ajs-modal {
visibility: hidden;
opacity: 0;
}
.alertify.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-duration: 500ms;
animation-duration: 500ms;
}
.alertify.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-duration: 250ms;
animation-duration: 250ms;
}
.alertify .ajs-dialog.ajs-shake {
-webkit-animation-name: ajs-shake;
animation-name: ajs-shake;
-webkit-animation-duration: .1s;
animation-duration: .1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
@-webkit-keyframes ajs-shake {
0%,
100% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
10%,
30%,
50%,
70%,
90% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
20%,
40%,
60%,
80% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
}
@keyframes ajs-shake {
0%,
100% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
10%,
30%,
50%,
70%,
90% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
20%,
40%,
60%,
80% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
}
.alertify.ajs-slide.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-slideIn;
animation-name: ajs-slideIn;
-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.alertify.ajs-slide.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-slideOut;
animation-name: ajs-slideOut;
-webkit-animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
}
.alertify.ajs-zoom.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-zoomIn;
animation-name: ajs-zoomIn;
}
.alertify.ajs-zoom.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-zoomOut;
animation-name: ajs-zoomOut;
}
.alertify.ajs-fade.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-fadeIn;
animation-name: ajs-fadeIn;
}
.alertify.ajs-fade.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-fadeOut;
animation-name: ajs-fadeOut;
}
.alertify.ajs-pulse.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-pulseIn;
animation-name: ajs-pulseIn;
}
.alertify.ajs-pulse.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-pulseOut;
animation-name: ajs-pulseOut;
}
.alertify.ajs-flipx.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-flipInX;
animation-name: ajs-flipInX;
}
.alertify.ajs-flipx.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-flipOutX;
animation-name: ajs-flipOutX;
}
.alertify.ajs-flipy.ajs-in:not(.ajs-hidden) .ajs-dialog {
-webkit-animation-name: ajs-flipInY;
animation-name: ajs-flipInY;
}
.alertify.ajs-flipy.ajs-out.ajs-hidden .ajs-dialog {
-webkit-animation-name: ajs-flipOutY;
animation-name: ajs-flipOutY;
}
@-webkit-keyframes ajs-pulseIn {
0%,
20%,
40%,
60%,
80%,
100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
-webkit-transform: scale3d(0.97, 0.97, 0.97);
transform: scale3d(0.97, 0.97, 0.97);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes ajs-pulseIn {
0%,
20%,
40%,
60%,
80%,
100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
-webkit-transform: scale3d(0.97, 0.97, 0.97);
transform: scale3d(0.97, 0.97, 0.97);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@-webkit-keyframes ajs-pulseOut {
20% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
50%,
55% {
opacity: 1;
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
}
@keyframes ajs-pulseOut {
20% {
-webkit-transform: scale3d(0.9, 0.9, 0.9);
transform: scale3d(0.9, 0.9, 0.9);
}
50%,
55% {
opacity: 1;
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.3, 0.3, 0.3);
transform: scale3d(0.3, 0.3, 0.3);
}
}
@-webkit-keyframes ajs-zoomIn {
0% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes ajs-zoomIn {
0% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@-webkit-keyframes ajs-zoomOut {
0% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
}
@keyframes ajs-zoomOut {
0% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
100% {
opacity: 0;
-webkit-transform: scale3d(0.25, 0.25, 0.25);
transform: scale3d(0.25, 0.25, 0.25);
}
}
@-webkit-keyframes ajs-fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes ajs-fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes ajs-fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes ajs-fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-webkit-keyframes ajs-flipInX {
0% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -10deg);
transform: perspective(400px) rotate3d(1, 0, 0, -10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 5deg);
transform: perspective(400px) rotate3d(1, 0, 0, 5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@keyframes ajs-flipInX {
0% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -10deg);
transform: perspective(400px) rotate3d(1, 0, 0, -10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 5deg);
transform: perspective(400px) rotate3d(1, 0, 0, 5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@-webkit-keyframes ajs-flipOutX {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
opacity: 0;
}
}
@keyframes ajs-flipOutX {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
opacity: 0;
}
}
@-webkit-keyframes ajs-flipInY {
0% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, 20deg);
transform: perspective(400px) rotate3d(0, -1, 0, 20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, -10deg);
transform: perspective(400px) rotate3d(0, -1, 0, -10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, 5deg);
transform: perspective(400px) rotate3d(0, -1, 0, 5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@keyframes ajs-flipInY {
0% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
opacity: 0;
}
40% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, 20deg);
transform: perspective(400px) rotate3d(0, -1, 0, 20deg);
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
60% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, -10deg);
transform: perspective(400px) rotate3d(0, -1, 0, -10deg);
opacity: 1;
}
80% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, 5deg);
transform: perspective(400px) rotate3d(0, -1, 0, 5deg);
}
100% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}
@-webkit-keyframes ajs-flipOutY {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, 15deg);
transform: perspective(400px) rotate3d(0, -1, 0, 15deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
opacity: 0;
}
}
@keyframes ajs-flipOutY {
0% {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, 15deg);
transform: perspective(400px) rotate3d(0, -1, 0, 15deg);
opacity: 1;
}
100% {
-webkit-transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
transform: perspective(400px) rotate3d(0, -1, 0, -90deg);
opacity: 0;
}
}
@-webkit-keyframes ajs-slideIn {
0% {
margin-top: -100%;
}
100% {
margin-top: 5%;
}
}
@keyframes ajs-slideIn {
0% {
margin-top: -100%;
}
100% {
margin-top: 5%;
}
}
@-webkit-keyframes ajs-slideOut {
0% {
margin-top: 5%;
}
100% {
margin-top: -100%;
}
}
@keyframes ajs-slideOut {
0% {
margin-top: 5%;
}
100% {
margin-top: -100%;
}
}
.alertify-notifier {
position: fixed;
width: 0;
overflow: visible;
z-index: 1982;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
.alertify-notifier .ajs-message {
position: relative;
width: 260px;
max-height: 0;
padding: 0;
opacity: 0;
margin: 0;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-duration: 250ms;
transition-duration: 250ms;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
.alertify-notifier .ajs-message.ajs-visible {
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
-webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: 1;
max-height: 100%;
padding: 15px;
margin-top: 10px;
}
.alertify-notifier .ajs-message.ajs-success {
background: rgba(91, 189, 114, 0.95);
}
.alertify-notifier .ajs-message.ajs-error {
background: rgba(217, 92, 92, 0.95);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
}
.alertify-notifier .ajs-message .ajs-close {
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 16px;
cursor: pointer;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAABGdBTUEAALGPC/xhBQAAAFBJREFUGBl1j0EKADEIA+ve/P9f9bh1hEihNBfjVCO1v7RKVqJK4h8gM5cAPR42AkQEpSXPwMTyoi13n5N9YqJehm3Fnr7nL1D0ZEbD5OubGyC7a9gx+9eNAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center center;
background-color: rgba(0, 0, 0, 0.5);
border-top-left-radius: 2px;
}
.alertify-notifier.ajs-top {
top: 10px;
}
.alertify-notifier.ajs-bottom {
bottom: 10px;
}
.alertify-notifier.ajs-right {
left: 10px;
}
.alertify-notifier.ajs-right .ajs-message {
left: -320px;
}
.alertify-notifier.ajs-right .ajs-message.ajs-visible {
left: 290px;
}
.alertify-notifier.ajs-left {
right: 10px;
}
.alertify-notifier.ajs-left .ajs-message {
right: -300px;
}
.alertify-notifier.ajs-left .ajs-message.ajs-visible {
right: 0;
}
.alertify-notifier.ajs-center {
right: 50%;
}
.alertify-notifier.ajs-center .ajs-message {
-webkit-transform: translateX(50%);
transform: translateX(50%);
}
.alertify-notifier.ajs-center .ajs-message.ajs-visible {
right: 50%;
-webkit-transition-timing-function: cubic-bezier(0.57, 0.43, 0.1, 0.65);
transition-timing-function: cubic-bezier(0.57, 0.43, 0.1, 0.65);
}
.alertify-notifier.ajs-center.ajs-top .ajs-message {
top: -300px;
}
.alertify-notifier.ajs-center.ajs-top .ajs-message.ajs-visible {
top: 0;
}
.alertify-notifier.ajs-center.ajs-bottom .ajs-message {
bottom: -300px;
}
.alertify-notifier.ajs-center.ajs-bottom .ajs-message.ajs-visible {
bottom: 0;
}
.ajs-no-transition.alertify .ajs-dimmer,
.ajs-no-transition.alertify .ajs-modal,
.ajs-no-transition.alertify .ajs-dialog {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
.ajs-no-transition.alertify-notifier .ajs-message {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
@media (prefers-reduced-motion: reduce) {
.alertify .ajs-dimmer,
.alertify .ajs-modal,
.alertify .ajs-dialog {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
.alertify-notifier .ajs-message {
-webkit-transition: none!important;
transition: none!important;
-webkit-animation: none!important;
animation: none!important;
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,61 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer {
background-color: #000;
opacity: .5;
}
.alertify .ajs-dialog {
max-width: 600px;
min-height: 122px;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
border-radius: 6px;
}
.alertify .ajs-header {
color: #333;
border-bottom: 1px solid #e5e5e5;
border-radius: 6px 6px 0 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 18px;
}
.alertify .ajs-body {
font-family: 'Roboto', sans-serif;
color: black;
}
.alertify.ajs-resizable .ajs-content,
.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content {
top: 58px;
bottom: 68px;
}
.alertify .ajs-footer {
background-color: #fff;
padding: 15px;
border-top: 1px solid #e5e5e5;
border-radius: 0 0 6px 6px;
}
.alertify-notifier .ajs-message {
background: rgba(255, 255, 255, 0.95);
color: #000;
text-align: center;
border: solid 1px #ddd;
border-radius: 2px;
}
.alertify-notifier .ajs-message.ajs-success {
color: #fff;
background: rgba(91, 189, 114, 0.95);
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-error {
color: #fff;
background: rgba(217, 92, 92, 0.95);
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
border-color: #999;
}

View file

@ -0,0 +1,6 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer{background-color:#000;opacity:.5}.alertify .ajs-dialog{max-width:600px;min-height:122px;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5);border-radius:6px}.alertify .ajs-header{color:#333;border-bottom:1px solid #e5e5e5;border-radius:6px 6px 0 0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px}.alertify .ajs-body{font-family:Roboto,sans-serif;color:#000}.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content,.alertify.ajs-resizable .ajs-content{top:58px;bottom:68px}.alertify .ajs-footer{background-color:#fff;padding:15px;border-top:1px solid #e5e5e5;border-radius:0 0 6px 6px}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:solid 1px #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999}

View file

@ -0,0 +1,61 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer {
background-color: #000;
opacity: .5;
}
.alertify .ajs-dialog {
max-width: 600px;
min-height: 122px;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
border-radius: 6px;
}
.alertify .ajs-header {
color: #333;
border-bottom: 1px solid #e5e5e5;
border-radius: 6px 6px 0 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 18px;
}
.alertify .ajs-body {
font-family: 'Roboto', sans-serif;
color: black;
}
.alertify.ajs-resizable .ajs-content,
.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content {
top: 58px;
bottom: 68px;
}
.alertify .ajs-footer {
background-color: #fff;
padding: 15px;
border-top: 1px solid #e5e5e5;
border-radius: 0 0 6px 6px;
}
.alertify-notifier .ajs-message {
background: rgba(255, 255, 255, 0.95);
color: #000;
text-align: center;
border: solid 1px #ddd;
border-radius: 2px;
}
.alertify-notifier .ajs-message.ajs-success {
color: #fff;
background: rgba(91, 189, 114, 0.95);
text-shadow: 1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-error {
color: #fff;
background: rgba(217, 92, 92, 0.95);
text-shadow: 1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
border-color: #999;
}

View file

@ -0,0 +1,6 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer{background-color:#000;opacity:.5}.alertify .ajs-dialog{max-width:600px;min-height:122px;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5);border-radius:6px}.alertify .ajs-header{color:#333;border-bottom:1px solid #e5e5e5;border-radius:6px 6px 0 0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px}.alertify .ajs-body{font-family:Roboto,sans-serif;color:#000}.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content,.alertify.ajs-resizable .ajs-content{top:58px;bottom:68px}.alertify .ajs-footer{background-color:#fff;padding:15px;border-top:1px solid #e5e5e5;border-radius:0 0 6px 6px}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:solid 1px #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999}

View file

@ -0,0 +1,69 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dialog {
background-color: white;
-webkit-box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.25);
border-radius: 2px;
}
.alertify .ajs-header {
color: black;
font-weight: bold;
background: #fafafa;
border-bottom: #eee 1px solid;
border-radius: 2px 2px 0 0;
}
.alertify .ajs-body {
color: black;
}
.alertify .ajs-body .ajs-content .ajs-input {
display: block;
width: 100%;
padding: 8px;
margin: 4px;
border-radius: 2px;
border: 1px solid #CCC;
}
.alertify .ajs-body .ajs-content p {
margin: 0;
}
.alertify .ajs-footer {
background: #fbfbfb;
border-top: #eee 1px solid;
border-radius: 0 0 2px 2px;
}
.alertify .ajs-footer .ajs-buttons .ajs-button {
background-color: transparent;
color: #000;
border: 0;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
}
.alertify .ajs-footer .ajs-buttons .ajs-button.ajs-ok {
color: #3593D2;
}
.alertify-notifier .ajs-message {
background: rgba(255, 255, 255, 0.95);
color: #000;
text-align: center;
border: solid 1px #ddd;
border-radius: 2px;
}
.alertify-notifier .ajs-message.ajs-success {
color: #fff;
background: rgba(91, 189, 114, 0.95);
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-error {
color: #fff;
background: rgba(217, 92, 92, 0.95);
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
border-color: #999;
}

View file

@ -0,0 +1,6 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dialog{background-color:#fff;-webkit-box-shadow:0 15px 20px 0 rgba(0,0,0,.25);box-shadow:0 15px 20px 0 rgba(0,0,0,.25);border-radius:2px}.alertify .ajs-header{color:#000;font-weight:700;background:#fafafa;border-bottom:#eee 1px solid;border-radius:2px 2px 0 0}.alertify .ajs-body{color:#000}.alertify .ajs-body .ajs-content .ajs-input{display:block;width:100%;padding:8px;margin:4px;border-radius:2px;border:1px solid #ccc}.alertify .ajs-body .ajs-content p{margin:0}.alertify .ajs-footer{background:#fbfbfb;border-top:#eee 1px solid;border-radius:0 0 2px 2px}.alertify .ajs-footer .ajs-buttons .ajs-button{background-color:transparent;color:#000;border:0;font-size:14px;font-weight:700;text-transform:uppercase}.alertify .ajs-footer .ajs-buttons .ajs-button.ajs-ok{color:#3593d2}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:solid 1px #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999}

View file

@ -0,0 +1,69 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dialog {
background-color: white;
-webkit-box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.25);
border-radius: 2px;
}
.alertify .ajs-header {
color: black;
font-weight: bold;
background: #fafafa;
border-bottom: #eee 1px solid;
border-radius: 2px 2px 0 0;
}
.alertify .ajs-body {
color: black;
}
.alertify .ajs-body .ajs-content .ajs-input {
display: block;
width: 100%;
padding: 8px;
margin: 4px;
border-radius: 2px;
border: 1px solid #CCC;
}
.alertify .ajs-body .ajs-content p {
margin: 0;
}
.alertify .ajs-footer {
background: #fbfbfb;
border-top: #eee 1px solid;
border-radius: 0 0 2px 2px;
}
.alertify .ajs-footer .ajs-buttons .ajs-button {
background-color: transparent;
color: #000;
border: 0;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
}
.alertify .ajs-footer .ajs-buttons .ajs-button.ajs-ok {
color: #3593D2;
}
.alertify-notifier .ajs-message {
background: rgba(255, 255, 255, 0.95);
color: #000;
text-align: center;
border: solid 1px #ddd;
border-radius: 2px;
}
.alertify-notifier .ajs-message.ajs-success {
color: #fff;
background: rgba(91, 189, 114, 0.95);
text-shadow: 1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-error {
color: #fff;
background: rgba(217, 92, 92, 0.95);
text-shadow: 1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
border-color: #999;
}

View file

@ -0,0 +1,6 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dialog{background-color:#fff;-webkit-box-shadow:0 15px 20px 0 rgba(0,0,0,.25);box-shadow:0 15px 20px 0 rgba(0,0,0,.25);border-radius:2px}.alertify .ajs-header{color:#000;font-weight:700;background:#fafafa;border-bottom:#eee 1px solid;border-radius:2px 2px 0 0}.alertify .ajs-body{color:#000}.alertify .ajs-body .ajs-content .ajs-input{display:block;width:100%;padding:8px;margin:4px;border-radius:2px;border:1px solid #ccc}.alertify .ajs-body .ajs-content p{margin:0}.alertify .ajs-footer{background:#fbfbfb;border-top:#eee 1px solid;border-radius:0 0 2px 2px}.alertify .ajs-footer .ajs-buttons .ajs-button{background-color:transparent;color:#000;border:0;font-size:14px;font-weight:700;text-transform:uppercase}.alertify .ajs-footer .ajs-buttons .ajs-button.ajs-ok{color:#3593d2}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:solid 1px #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999}

View file

@ -0,0 +1,89 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer {
background-color: rgba(0, 0, 0, 0.85);
opacity: 1;
}
.alertify .ajs-dialog {
max-width: 50%;
min-height: 137px;
background-color: #F4F4F4;
border: 1px solid #DDD;
-webkit-box-shadow: none;
box-shadow: none;
border-radius: 5px;
}
.alertify .ajs-header {
padding: 1.5rem 2rem;
border-bottom: none;
border-radius: 5px 5px 0 0;
color: #555;
background-color: #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1.6em;
font-weight: 700;
}
.alertify .ajs-body {
font-family: 'Roboto', sans-serif;
color: #555;
}
.alertify .ajs-body .ajs-content .ajs-input {
width: 100%;
margin: 0;
padding: .65em 1em;
font-size: 1em;
background-color: #FFF;
border: 1px solid rgba(0, 0, 0, 0.15);
outline: 0;
color: rgba(0, 0, 0, 0.7);
border-radius: .3125em;
-webkit-transition: background-color 0.3s ease-out, border-color 0.2s ease, -webkit-box-shadow 0.2s ease;
transition: background-color 0.3s ease-out, border-color 0.2s ease, -webkit-box-shadow 0.2s ease;
transition: background-color 0.3s ease-out, box-shadow 0.2s ease, border-color 0.2s ease;
transition: background-color 0.3s ease-out, box-shadow 0.2s ease, border-color 0.2s ease, -webkit-box-shadow 0.2s ease;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.alertify .ajs-body .ajs-content .ajs-input:active {
border-color: rgba(0, 0, 0, 0.3);
background-color: #FAFAFA;
}
.alertify .ajs-body .ajs-content .ajs-input:focus {
border-color: rgba(0, 0, 0, 0.2);
color: rgba(0, 0, 0, 0.85);
}
.alertify.ajs-resizable .ajs-content,
.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content {
top: 64px;
bottom: 74px;
}
.alertify .ajs-footer {
background-color: #fff;
padding: 1rem 2rem;
border-top: none;
border-radius: 0 0 5px 5px;
}
.alertify-notifier .ajs-message {
background: rgba(255, 255, 255, 0.95);
color: #000;
text-align: center;
border: solid 1px #ddd;
border-radius: 2px;
}
.alertify-notifier .ajs-message.ajs-success {
color: #fff;
background: rgba(91, 189, 114, 0.95);
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-error {
color: #fff;
background: rgba(217, 92, 92, 0.95);
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
border-color: #999;
}

View file

@ -0,0 +1,6 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer{background-color:rgba(0,0,0,.85);opacity:1}.alertify .ajs-dialog{max-width:50%;min-height:137px;background-color:#f4f4f4;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none;border-radius:5px}.alertify .ajs-header{padding:1.5rem 2rem;border-bottom:none;border-radius:5px 5px 0 0;color:#555;background-color:#fff;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:1.6em;font-weight:700}.alertify .ajs-body{font-family:Roboto,sans-serif;color:#555}.alertify .ajs-body .ajs-content .ajs-input{width:100%;margin:0;padding:.65em 1em;font-size:1em;background-color:#fff;border:1px solid rgba(0,0,0,.15);outline:0;color:rgba(0,0,0,.7);border-radius:.3125em;-webkit-transition:background-color .3s ease-out,border-color .2s ease,-webkit-box-shadow .2s ease;transition:background-color .3s ease-out,border-color .2s ease,-webkit-box-shadow .2s ease;transition:background-color .3s ease-out,box-shadow .2s ease,border-color .2s ease;transition:background-color .3s ease-out,box-shadow .2s ease,border-color .2s ease,-webkit-box-shadow .2s ease;-webkit-box-sizing:border-box;box-sizing:border-box}.alertify .ajs-body .ajs-content .ajs-input:active{border-color:rgba(0,0,0,.3);background-color:#fafafa}.alertify .ajs-body .ajs-content .ajs-input:focus{border-color:rgba(0,0,0,.2);color:rgba(0,0,0,.85)}.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content,.alertify.ajs-resizable .ajs-content{top:64px;bottom:74px}.alertify .ajs-footer{background-color:#fff;padding:1rem 2rem;border-top:none;border-radius:0 0 5px 5px}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:solid 1px #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999}

View file

@ -0,0 +1,89 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer {
background-color: rgba(0, 0, 0, 0.85);
opacity: 1;
}
.alertify .ajs-dialog {
max-width: 50%;
min-height: 137px;
background-color: #F4F4F4;
border: 1px solid #DDD;
-webkit-box-shadow: none;
box-shadow: none;
border-radius: 5px;
}
.alertify .ajs-header {
padding: 1.5rem 2rem;
border-bottom: none;
border-radius: 5px 5px 0 0;
color: #555;
background-color: #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1.6em;
font-weight: 700;
}
.alertify .ajs-body {
font-family: 'Roboto', sans-serif;
color: #555;
}
.alertify .ajs-body .ajs-content .ajs-input {
width: 100%;
margin: 0;
padding: .65em 1em;
font-size: 1em;
background-color: #FFF;
border: 1px solid rgba(0, 0, 0, 0.15);
outline: 0;
color: rgba(0, 0, 0, 0.7);
border-radius: .3125em;
-webkit-transition: background-color 0.3s ease-out, border-color 0.2s ease, -webkit-box-shadow 0.2s ease;
transition: background-color 0.3s ease-out, border-color 0.2s ease, -webkit-box-shadow 0.2s ease;
transition: background-color 0.3s ease-out, box-shadow 0.2s ease, border-color 0.2s ease;
transition: background-color 0.3s ease-out, box-shadow 0.2s ease, border-color 0.2s ease, -webkit-box-shadow 0.2s ease;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.alertify .ajs-body .ajs-content .ajs-input:active {
border-color: rgba(0, 0, 0, 0.3);
background-color: #FAFAFA;
}
.alertify .ajs-body .ajs-content .ajs-input:focus {
border-color: rgba(0, 0, 0, 0.2);
color: rgba(0, 0, 0, 0.85);
}
.alertify.ajs-resizable .ajs-content,
.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content {
top: 64px;
bottom: 74px;
}
.alertify .ajs-footer {
background-color: #fff;
padding: 1rem 2rem;
border-top: none;
border-radius: 0 0 5px 5px;
}
.alertify-notifier .ajs-message {
background: rgba(255, 255, 255, 0.95);
color: #000;
text-align: center;
border: solid 1px #ddd;
border-radius: 2px;
}
.alertify-notifier .ajs-message.ajs-success {
color: #fff;
background: rgba(91, 189, 114, 0.95);
text-shadow: 1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-error {
color: #fff;
background: rgba(217, 92, 92, 0.95);
text-shadow: 1px -1px 0 rgba(0, 0, 0, 0.5);
}
.alertify-notifier .ajs-message.ajs-warning {
background: rgba(252, 248, 215, 0.95);
border-color: #999;
}

View file

@ -0,0 +1,6 @@
/**
* alertifyjs 1.13.1 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
.alertify .ajs-dimmer{background-color:rgba(0,0,0,.85);opacity:1}.alertify .ajs-dialog{max-width:50%;min-height:137px;background-color:#f4f4f4;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none;border-radius:5px}.alertify .ajs-header{padding:1.5rem 2rem;border-bottom:none;border-radius:5px 5px 0 0;color:#555;background-color:#fff;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:1.6em;font-weight:700}.alertify .ajs-body{font-family:Roboto,sans-serif;color:#555}.alertify .ajs-body .ajs-content .ajs-input{width:100%;margin:0;padding:.65em 1em;font-size:1em;background-color:#fff;border:1px solid rgba(0,0,0,.15);outline:0;color:rgba(0,0,0,.7);border-radius:.3125em;-webkit-transition:background-color .3s ease-out,border-color .2s ease,-webkit-box-shadow .2s ease;transition:background-color .3s ease-out,border-color .2s ease,-webkit-box-shadow .2s ease;transition:background-color .3s ease-out,box-shadow .2s ease,border-color .2s ease;transition:background-color .3s ease-out,box-shadow .2s ease,border-color .2s ease,-webkit-box-shadow .2s ease;-webkit-box-sizing:border-box;box-sizing:border-box}.alertify .ajs-body .ajs-content .ajs-input:active{border-color:rgba(0,0,0,.3);background-color:#fafafa}.alertify .ajs-body .ajs-content .ajs-input:focus{border-color:rgba(0,0,0,.2);color:rgba(0,0,0,.85)}.alertify.ajs-maximized:not(.ajs-resizable) .ajs-content,.alertify.ajs-resizable .ajs-content{top:64px;bottom:74px}.alertify .ajs-footer{background-color:#fff;padding:1rem 2rem;border-top:none;border-radius:0 0 5px 5px}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:solid 1px #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999}

View file

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View file

@ -2,7 +2,9 @@ body{
margin-top: 4em;
}
div.panel-heading, li.list-group-item, a {
div.panel-heading,
li.list-group-item,
a {
cursor: pointer;
}
@ -19,7 +21,7 @@ div.panel-heading, li.list-group-item, a {
}
.checkable:before {
content: '\2713';
content: "\2713";
margin-right: 0.2em;
font-style: italic;
color: #999;
@ -43,12 +45,28 @@ div.panel-heading, li.list-group-item, a {
font-size: 1.5em;
}
.add_thing_label, #edit_thing_label {
.add_thing_label,
#edit_thing_label {
width: 80%;
display: inline-block;
}
.add_thing_nb, #edit_thing_nb {
.add_thing_nb,
#edit_thing_nb {
width: 18%;
display: inline-block;
}
/** Alertify **/
.alertify-notifier .ajs-message.ajs-info {
background: rgba(66, 215, 252, 0.95);
}
.alertify .ajs-header,
.alertify .ajs-footer {
background-color: #4e5d6c;
}
.alertify .ajs-dialog {
background-color: #485563;
}

1579
static/main.js Normal file

File diff suppressed because it is too large Load diff

657
static/mysc_objects.js Normal file
View file

@ -0,0 +1,657 @@
function SCaseList() {
lastChange = 0;
this.importExampleData = function () {
var exampleData = {
Vacances: {
Papier: {
color: "#f00",
things: [
{ label: _("White paper"), nb: 1 },
{ label: _("Pen"), nb: 3 },
{ label: _("ID card/passport"), nb: 1 },
],
},
Multimédia: {
color: "#0f0",
things: [
{ label: _("Watch"), nb: 1 },
{ label: _("Watch charger"), nb: 1 },
{ label: _("Laptop"), nb: 1 },
{ label: _("Laptop charger"), nb: 1 },
],
},
},
};
for (scaseName in exampleData) {
var scase = this.newSCase(scaseName);
for (catName in exampleData[scaseName]) {
var cat = scase.cats.newCat(catName);
for (idx in exampleData[scaseName][catName].things) {
cat.newThing(
exampleData[scaseName][catName].things[idx]["label"],
exampleData[scaseName][catName].things[idx]["nb"]
);
}
}
}
};
this.loadFromLocalStorage = function (data) {
if (jQuery.type(localStorage.scases) != "undefined") {
try {
return this.loadFromJsonData(JSON.parse(localStorage.scases));
} catch (e) {
return false;
}
}
return null;
};
this.loadFromJsonData = function (data) {
try {
this.lastChange = data.lastChange;
for (el in data.scases) {
this[el] = new SCase(false, false, data.scases[el]);
}
return true;
} catch (e) {
for (el in this) {
if (this.isSCase(this[el])) {
delete this[el];
}
}
}
return false;
};
this.export = function () {
return {
lastChange: this.lastChange,
scases: this.each(function (idx, scase) {
return scase.export();
}),
};
};
this.import = function (data) {
ret = {};
for (el in this) {
if (this.isSCase(this[el])) {
delete ret[el];
}
}
this.lastChange = data.lastChange;
for (el in data.scases) {
this[el] = new SCase(false, false, data.scases[el]);
}
return true;
};
this.save = function () {
localStorage.scases = JSON.stringify(this.export());
};
this.each = function (fct) {
var idx = 0;
var ret = {};
for (el in this) {
if (this.isSCase(this[el])) {
ret[el] = fct(idx++, this[el]);
}
}
return ret;
};
this.count = function () {
len = 0;
this.each(function (idx, scase) {
len = len + 1;
});
return len;
};
this.isSCase = function (el) {
return (
jQuery.type(el) == "object" &&
jQuery.type(el.isSCase) == "function" &&
el.isSCase()
);
};
this.byName = function (name) {
for (el in this) {
if (this.isSCase(this[el])) {
if (this[el].name == name) {
return this[el];
}
}
}
return false;
};
this.byUUID = function (uuid) {
return this.isCase(this[uuid]) ? this[uuid] : null;
};
this.removeSCase = function (name) {
for (el in this) {
if (this.isSCase(this[el]) && this[el].name == name) {
this[el].remove();
return true;
}
}
return false;
};
this.newSCase = function (name) {
if (this.byName(this[name])) {
var scase = this.byName(name);
if (scase.removed) {
scase.restore();
return true;
}
} else {
var uuid = uuid || generate_uuid();
this[uuid] = new SCase(uuid, name);
return this[uuid];
}
return false;
};
this.renameSCase = function (name, newname) {
var scase = this.byName(name);
if (scase && !this.byName(newname)) {
scase.name = newname;
scase.lastChange = new Date().getTime();
return scase;
}
return false;
};
this.copySCase = function (name, newname) {
var orig_scase = this.byName(name);
if (this.isSCase(orig_scase) && !this.byName(newname)) {
var uuid = uuid || generate_uuid();
this[uuid] = new SCase(false, false, orig_scase.export());
this[uuid].uuid = uuid;
this[uuid].lastChange = new Date().getTime();
this[uuid].name = newname;
return this[uuid];
}
return false;
};
this.resetSCase = function (name) {
for (el in this) {
if (this.isSCase(this[el]) && this[el].name == name) {
return this[el].reset();
}
}
return false;
};
}
function SCase(uuid, name, data) {
this.uuid = uuid || generate_uuid();
this.name = name;
this.cats = new CatList();
this.lastChange = new Date().getTime();
this.removed = false;
this.isSCase = function () {
return true;
};
this.import = function (data) {
this.uuid = data.uuid || generate_uuid();
this.lastChange = data.lastChange || new Date().getTime();
this.name = decodeURIComponent(data.name);
this.removed = data.removed || false;
if (jQuery.type(data.cats) == "object") {
this.cats = new CatList(data.cats);
}
return true;
};
this.export = function () {
return {
uuid: this.uuid,
lastChange: this.lastChange,
name: encodeURIComponent(this.name),
removed: this.removed,
cats: this.cats.export(),
};
};
this.byName = function (name) {
for (idx in this.cats) {
if (name == this.cats[idx].name) {
return this.cats[idx];
}
}
return false;
};
this.stats = function () {
var cats = 0;
var things = 0;
var things_done = 0;
this.cats.each(function (cidx, cat) {
if (cat.removed) {
return true;
}
cats++;
for (idx in cat.things) {
if (cat.things[idx].removed) {
continue;
}
things++;
if (cat.things[idx].checked) {
things_done++;
}
}
});
return {
cats: cats,
things: things,
done: things_done,
};
};
this.reset = function () {
this.cats.each(function (idx, cat) {
for (idx in cat.things) {
if (cat.things[idx].checked) {
cat.things[idx].checked = false;
}
}
});
this.lastChange = new Date().getTime();
return true;
};
this.remove = function () {
this.removed = true;
this.lastChange = new Date().getTime();
};
this.restore = function () {
this.removed = false;
this.lastChange = new Date().getTime();
};
/*
* Constructor
*/
if (jQuery.type(data) == "object") {
try {
this.import(data);
} catch (e) {
console.log(e);
alert(
_(
"An error occurred while loading the %s suitcase from the cache.",
this.name
)
);
}
}
}
function CatList(data) {
this.export = function () {
return this.each(function (idx, cat) {
return cat.export();
});
};
this.import = function (data) {
for (el in this) {
if (this.isCat(this[el])) {
delete this[el];
}
}
for (el in data) {
this[el] = new Cat(el, false, false, data[el]);
}
return true;
};
this.each = function (fct) {
var idx = 0;
var ret = {};
for (el in this) {
if (this.isCat(this[el])) {
ret[el] = fct(idx++, this[el]);
}
}
return ret;
};
this.count = function () {
len = 0;
this.each(function (idx, cat) {
len = len + 1;
});
return len;
};
this.isCat = function (el) {
return (
jQuery.type(el) == "object" &&
jQuery.type(el.isCat) == "function" &&
el.isCat()
);
};
this.byName = function (name) {
for (el in this) {
if (this.isCat(this[el])) {
if (this[el].name == name) {
return this[el];
}
}
}
return false;
};
this.byUUID = function (uuid) {
return this.isCas(this[uuid]) ? this[uuid] : null;
};
this.newCat = function (name) {
if (this.byName(name)) {
var cat = this.byName(name);
if (cat.removed) {
cat.restore();
return true;
}
} else {
var uuid = uuid || generate_uuid();
this[uuid] = new Cat(uuid, name);
return this[uuid];
}
return false;
};
this.renameCat = function (name, newname) {
var cat = this.byName(name);
if (cat && !this.byName(newname)) {
cat.name = newname;
cat.lastChange = new Date().getTime();
return cat;
}
return false;
};
this.removeCat = function (name) {
for (el in this) {
if (this.isCat(this[el]) && this[el].name == name) {
this[el].remove();
return true;
}
}
return false;
};
this.restoreCat = function (name) {
for (el in this) {
if (this.isCat(this[el]) && this[el].name == name && this[el].removed) {
this[el].restore();
return true;
}
}
return false;
};
/*
* Constructor
*/
if (jQuery.type(data) == "object") {
try {
this.import(data);
} catch (e) {
console.log(e);
alert(
_("An error occurred while loading the category list from the cache.")
);
}
}
}
function Cat(uuid, name, color, data) {
this.uuid = generate_uuid();
this.lastChange = new Date().getTime();
this.name = name;
this.color =
color ||
"#" + (0x1000000 + Math.random() * 0xffffff).toString(16).substr(1, 6);
this.things = {};
this.removed = false;
this.isCat = function () {
return true;
};
this.import = function (data) {
this.uuid = data.uuid || generate_uuid();
this.lastChange = data.lastChange || new Date().getTime();
this.name = decodeURIComponent(data.name);
this.color = data.color;
this.removed = data.removed || false;
if (jQuery.type(data.things) == "object") {
for (tuuid in data.things) {
this.things[tuuid] = new Thing(tuuid);
this.things[tuuid].import(data.things[tuuid]);
}
}
return true;
};
this.export = function () {
var things = {};
for (tuuid in this.things) {
things[tuuid] = this.things[tuuid].export();
}
return {
uuid: this.uuid,
lastChange: this.lastChange,
name: encodeURIComponent(this.name),
color: this.color,
removed: this.removed,
things: things,
};
};
this.byLabel = function (label) {
for (idx in this.things) {
if (label == this.things[idx].label) {
return this.things[idx];
}
}
return false;
};
this.count = function () {
return keys(this.things).length;
};
this.stats = function () {
var count = 0;
var done = 0;
for (idx in this.things) {
if (this.things[idx].removed) {
continue;
}
if (this.things[idx].checked) {
done += 1;
}
count += 1;
}
return {
things: count,
done: done,
};
};
this.newThing = function (label, nb) {
if (this.byLabel(label)) {
var thing = this.byLabel(label);
if (thing.removed) {
thing.restore();
thing.setChecked(false);
thing.setNb(nb);
return true;
}
} else {
var uuid = generate_uuid();
this.things[uuid] = new Thing(uuid, label, nb);
return true;
}
return false;
};
this.renameThing = function (label, newlabel) {
var thing = this.byLabel(label);
if (thing && !this.byLabel(newlabel)) {
thing.label = newlabel;
thing.lastChange = new Date().getTime();
return thing;
}
return false;
};
this.removeThing = function (label) {
for (idx in this.things) {
if (this.things[idx].label == label) {
this.things[idx].remove();
return true;
}
}
return false;
};
this.restoreThing = function (label) {
for (idx in this.things) {
if (this.things[idx].label == label && this.things[idx].removed) {
this.things[idx].restore();
return true;
}
}
return false;
};
this.remove = function () {
this.removed = true;
this.lastChange = new Date().getTime();
};
this.restore = function () {
this.removed = false;
this.lastChange = new Date().getTime();
};
/*
* Constructor
*/
if (jQuery.type(data) == "object") {
try {
this.import(data);
} catch (e) {
console.log(e);
alert(
_(
"An error occurred while loading the %s category from the cache.",
this.name
)
);
}
}
}
function Thing(uuid, label, nb, checked) {
this.uuid = uuid || generate_uuid();
this.lastChange = new Date().getTime();
this.label = label;
this.nb = nb || 1;
this.checked = checked;
this.removed = false;
this.import = function (data) {
this.uuid = data.uuid || generate_uuid();
this.lastChange = data.lastChange || new Date().getTime();
(this.label = decodeURIComponent(data.label)), (this.nb = data.nb || 1);
this.checked = data.checked;
this.removed = data.removed || false;
};
this.export = function () {
return {
uuid: this.uuid,
lastChange: this.lastChange,
label: encodeURIComponent(this.label),
nb: this.nb,
checked: this.checked,
removed: this.removed,
};
};
this.setNb = function (nb) {
this.nb = nb;
this.lastChange = new Date().getTime();
};
this.setChecked = function (value) {
this.checked = value;
this.lastChange = new Date().getTime();
console.log(
`Thing<${this.uuid}>.setChecked(${this.checked}): ${this.lastChange}`
);
};
this.remove = function () {
this.removed = true;
this.lastChange = new Date().getTime();
};
this.restore = function () {
this.removed = false;
this.lastChange = new Date().getTime();
};
}
function User() {
this.username = null;
this.name = null;
this.token = null;
this.loadFromLocalStorage = function () {
if (jQuery.type(localStorage.user) == "undefined") return;
try {
var data = JSON.parse(localStorage.user);
this.username = data.username;
this.name = data.name;
this.token = data.token;
} catch (e) {
alert(_("Error loading your login information. Please log in again."));
}
};
this.connected = function () {
return this.username && this.token;
};
this.reset = function () {
this.username = null;
this.name = null;
this.token = null;
};
this.save = function () {
localStorage.user = JSON.stringify({
username: this.username,
name: this.name,
token: this.token,
});
};
}

0
templates/.gitignore vendored Normal file
View file

View file

@ -0,0 +1,8 @@
CACHE MANIFEST
# Last updated: {$last_updated} GMT
CACHE:
{"\n"|implode:$cache}
NETWORK:
*

View file

@ -5,20 +5,21 @@
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0">
<link rel="shortcut icon" href="favicon.png">
<link rel="shortcut icon" sizes="196x196" href="icon-196x196.png">
<link rel="shortcut icon" sizes="128x128" href="icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="icon-128x128.png">
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="icon-128x128.png">
<link rel="shortcut icon" href="{static_url path="favicon.png"}">
<link rel="shortcut icon" sizes="196x196" href="{static_url path="icon-196x196.png"}">
<link rel="shortcut icon" sizes="128x128" href="{static_url path="icon-128x128.png"}">
<link rel="apple-touch-icon" sizes="128x128" href="{static_url path="icon-128x128.png"}">
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="{static_url path="icon-128x128.png"}">
<!-- Bootstrap -->
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="inc/lib/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="{static_url path="lib/alertify/css/alertify.min.css"}">
<link rel="stylesheet" href="{static_url path="lib/bootstrap/css/bootstrap.min.css"}">
<!-- Optional theme -->
<link rel="stylesheet" href="inc/lib/bootstrap/css/bootstrap.superhero.min.css">
<link rel="stylesheet" href="{static_url path="lib/bootstrap/css/bootstrap.superhero.min.css"}">
<link rel="stylesheet" href="inc/main.css">
<link rel="stylesheet" href="{static_url path="main.css"}">
</head>
<body>
@ -35,29 +36,43 @@
</div>
<div class="collapse navbar-collapse" id="navbar-top-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="menu menu-scases"><a href="#add_scase" id="add_scase_btn"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter une valise</a></li>
<li class="menu menu-scases"><a href="#scases_trash" id="scases_trash_btn"><span class="glyphicon glyphicon-trash"></span> Voir la corbeille</a></li>
<li class="menu menu-scase"><a href="#scases" id="back_to_scases"><span class="glyphicon glyphicon-briefcase"></span> Liste des valises</a></li>
<li class="menu menu-scases"><a href="#add_scase" id="add_scase_btn"><span class="glyphicon glyphicon-plus-sign"></span> {t}Add a suitcase{/t}</a></li>
<li class="menu menu-scases"><a href="#scases_trash" id="scases_trash_btn"><span class="glyphicon glyphicon-trash"></span> {t}Show trash{/t}</a></li>
<li class="menu menu-scase"><a href="#scases" id="back_to_scases"><span class="glyphicon glyphicon-briefcase"></span> {t}Suitcases list{/t}</a></li>
<li class="menu menu-scase dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class='glyphicon glyphicon-tag'></span> Gérer la valise <b class="caret"></b></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class='glyphicon glyphicon-tag'></span> {t}Manage the suitcase{/t} <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#add_cat" id="add_cat_btn"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter une catégorie</a></li>
<li><a href="#add_cat" id="add_cat_btn"><span class="glyphicon glyphicon-plus-sign"></span> {t}Add a category{/t}</a></li>
<li class="divider"></li>
<li><a href="#rename_scase" id="rename_scase_btn"><span class="glyphicon glyphicon-edit"></span> Renommer la valise</a></li>
<li><a href="#copy_scase" id="copy_scase_btn"><span class="glyphicon glyphicon-duplicate"></span> Copier la valise</a></li>
<li><a href="#reset_scase" id="reset_scase_btn"><span class="glyphicon glyphicon-cog"></span> Réinitialiser la valise</a></li>
<li><a href="#delete_scase" id="delete_scase_btn"><span class="glyphicon glyphicon-trash"></span> Supprimer la valise</a></li>
<li><a href="#rename_scase" id="rename_scase_btn"><span class="glyphicon glyphicon-edit"></span> {t}Rename the suitcase{/t}</a></li>
<li><a href="#copy_scase" id="copy_scase_btn"><span class="glyphicon glyphicon-duplicate"></span> {t}Copy the suitcase{/t}</a></li>
<li><a href="#reset_scase" id="reset_scase_btn"><span class="glyphicon glyphicon-cog"></span> {t}Reset the suitcase{/t}</a></li>
<li><a href="#delete_scase" id="delete_scase_btn"><span class="glyphicon glyphicon-trash"></span> {t}Delete the suitcase{/t}</a></li>
<li class="divider"></li>
<li><a href="#scase_trash" id="scase_trash_btn"><span class="glyphicon glyphicon-trash"></span> Voir la corbeille de la valise</a></li>
<li><a href="#scase_trash" id="scase_trash_btn"><span class="glyphicon glyphicon-trash"></span> {t}Show suitcase's trash{/t}</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class='glyphicon glyphicon-hdd'></span> Gérer vos données <b class="caret"></b></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class='glyphicon glyphicon-hdd'></span> {t}Manage data{/t} <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a id='export_local_data' href='#' download='mysc_export.json'><span class='glyphicon glyphicon-save'></span> Sauvegarder vos données</a></li>
<li><a id='import_local_data' href='#' download='mysc_export.json'><span class='glyphicon glyphicon-open'></span> Restaurer vos données</a></li>
<li><a id='export_local_data' href='#' download='mysc_export.json'><span class='glyphicon glyphicon-save'></span> {t}Backup your data{/t}</a></li>
<li><a id='import_local_data' href='#' download='mysc_export.json'><span class='glyphicon glyphicon-open'></span> {t}Restaure your data{/t}</a></li>
<li class="divider"></li>
<li><a id='clear_local_data'><span class='glyphicon glyphicon-trash'></span> Purger les données locales</a></li>
<li><a id='sync_local_data'><span class='glyphicon glyphicon-refresh'></span> {t}Synchronize local data{/t}</a></li>
<li class="divider"></li>
<li><a id='clear_local_data'><span class='glyphicon glyphicon-trash'></span> {t}Purge local data{/t}</a></li>
<li><a id='load_example_data'><span class='glyphicon glyphicon-trash'></span> {t}Load example data{/t}</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class='glyphicon glyphicon-user'></span>
<span id="username"></span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a id='login' href='#'><span class='glyphicon glyphicon-loging'></span> {t}Login{/t}</a></li>
<li><a id='logout' href='#'><span class='glyphicon glyphicon-logout'></span> {t}Logout{/t}</a></li>
</ul>
</li>
</ul>
@ -73,18 +88,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Ajouter une valise</h4>
<h4 class="modal-title">{t}Add a suitcase{/t}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<input type='text' id='add_scase_name' class="form-control" placeholder="Nom de la valise"/>
<input type='text' id='add_scase_name' class="form-control" placeholder="{t}Suitcase name{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='add_scase_submit'>Ajouter</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='add_scase_submit'>{t}Add{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -95,18 +110,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Copier une valise</h4>
<h4 class="modal-title">{t}Copy the suitcase{/t}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<input type='text' id='copy_scase_name' class="form-control" placeholder="Nom de la nouvelle valise"/>
<input type='text' id='copy_scase_name' class="form-control" placeholder="{t}Name of the new suitcase{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='copy_scase_submit'>Copier</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='copy_scase_submit'>{t}Copy{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -117,18 +132,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Renomer la valise</h4>
<h4 class="modal-title">{t}Rename the suitcase{/t}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<input type='text' id='rename_scase_name' class="form-control" placeholder="Nom de la nouvelle valise"/>
<input type='text' id='rename_scase_name' class="form-control" placeholder="{t}New suitcase name{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='rename_scase_submit'>Renomer</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='rename_scase_submit'>{t}Rename{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -139,18 +154,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Ajouter une catégorie</h4>
<h4 class="modal-title">{t}Add a category{/t}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<input type='text' id='add_cat_name' class="form-control" placeholder="Nom de la catégorie"/>
<input type='text' id='add_cat_name' class="form-control" placeholder="{t}Category name{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='add_cat_submit'>Ajouter</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='add_cat_submit'>{t}Add{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -161,18 +176,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Renommer une catégorie</h4>
<h4 class="modal-title">{t}Rename the category{/t}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<input type='text' id='rename_cat_name' class="form-control" placeholder="Nouveau nom de la catégorie"/>
<input type='text' id='rename_cat_name' class="form-control" placeholder="{t}New category's name{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='rename_cat_submit'>Renommer</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='rename_cat_submit'>{t}Rename{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -184,23 +199,23 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Ajouter un élément</h4>
<h4 class="modal-title">{t}Add an element{/t}</h4>
</div>
<div class="modal-body">
<form role="form">
<div class="form-group">
<input type='text' class='form-control add_thing_label' placeholder="Nom de l'élément"/>
<input type='number' class='form-control add_thing_nb' placeholder="Nb"/>
<input type='text' class='form-control add_thing_label' placeholder="{t}Element name{/t}"/>
<input type='number' class='form-control add_thing_nb' placeholder="{t}Nb{/t}"/>
</div>
<div class="form-group">
<input type='text' class='form-control add_thing_label' placeholder="Un autre ?"/>
<input type='number' class='form-control add_thing_nb' placeholder="Nb"/>
<input type='text' class='form-control add_thing_label' placeholder="{t}Another?{/t}"/>
<input type='number' class='form-control add_thing_nb' placeholder="{t}Nb{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='add_thing_submit'>Ajouter</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='add_thing_submit'>{t}Add{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -211,19 +226,19 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Modifier un élément</h4>
<h4 class="modal-title">{t}Edit the element{/t}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<input type='text' id='edit_thing_label' class="form-control" placeholder="Nouveau nom de l'élément"/>
<input type='number' id='edit_thing_nb' class="form-control" placeholder="Nb"/>
<input type='text' id='edit_thing_label' class="form-control" placeholder="{t}Element name{/t}"/>
<input type='number' id='edit_thing_nb' class="form-control" placeholder="{t}Nb{/t}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id='edit_thing_submit'>Modifier</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='edit_thing_submit'>{t}Modify{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@ -234,12 +249,12 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Chargement...</h2>
<h2 class="modal-title">{t}Loading...{/t}</h2>
</div>
<div class="modal-body">
<div class="progress progress-striped active">
<div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
<span class="sr-only">Chargement...</span>
<span class="sr-only">{t}Loading...{/t}</span>
</div>
</div>
</div>
@ -247,37 +262,88 @@
</div><!-- /.modal-dialog -->
</div>
<div class="modal" id="confirm_modal" tabindex="-1" role="dialog" aria-labelledby="confirmModal" aria-hidden="true">
<div class="modal" id="login_modal" tabindex="-1" role="dialog" aria-labelledby="loginModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close cancel" aria-hidden="true">&times;</button>
<h4 class="modal-title">Confirmation</h4>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{t}Connection{/t}</h4>
</div>
<div class="modal-body">
<p id='question'></p>
<form class="form-horizontal" role="form" action="login">
<div class="form-group">
<input type='text' id='login_username' class="form-control" placeholder="{t}Username{/t}" required/>
<input type='password' id='login_password' class="form-control" placeholder="{t}Password{/t}" required/>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default cancel">Annuler</button>
<button type="button" class="btn btn-primary" id='confirm_modal_submit'>Valider</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{t}Cancel{/t}</button>
<button type="button" class="btn btn-primary" id='login_submit'>{t}Connect{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal" id="welcome_modal" tabindex="-1" role="dialog" aria-labelledby="welcomeModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{t}Welcome{/t}</h4>
</div>
<div class="modal-body">
{t escape=false}<p>
This application allows you to manage lists of things not to forget to pack in your
suitcase before your departure:
<ul>
<li>
To get started, create a suitcase, add the categories of things you will need to
pack, and add all these things to it;
</li>
<li>
Before your departure, you can then gradually check off all the things you have
already gathered and ensure you don't forget anything!
</li>
</ul>
</p>
<p>
<strong>Note:</strong> This application has been designed to work completely locally.
The data manipulated (your suitcases, etc.) is stored only in your internet browser.
It is also possible to export/import this information in JSON format.<br/>
However, to facilitate the management of your suitcases from multiple devices, it's
possible to synchronize this information on the server. For this, you need an account,
and at this time, registrations are not open.
</p>
<p>
If you have an account, you can log in by clicking the <em>Login</em> button below.
If not, click the <em>Anonymous usage</em> button to start using the application.
</p>{/t}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id='welcome_connect'>{t}Connect{/t}</button>
<button type="button" class="btn btn-default" id='welcome_annonymous'>{t}Anonymous usage{/t}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="inc/lib/jquery.min.js"></script>
<script src="{static_url path="lib/jquery.min.js"}"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="inc/lib/bootstrap/js/bootstrap.js"></script>
<script src="{static_url path="lib/alertify/alertify.min.js"}"></script>
<script src="{static_url path="lib/bootstrap/js/bootstrap.js"}"></script>
<script>
<!-- Other libs & JavaScript scripts -->
{foreach $js as $path}
<script language="javascript" src="{$path|escape:"quotes"}"></script>
{/foreach}
</script>
<script src="inc/lib/uuid.js"></script>
<script src="inc/mydialog.js"></script>
<script src="inc/mysc_objects.js"></script>
<script src="inc/main.js"></script>
<script src="{static_url path="lib/uuid.js"}"></script>
<script src="{static_url path="mysc_objects.js"}"></script>
<script src="{static_url path="main.js"}"></script>
</body>
</html>

View file

@ -0,0 +1,13 @@
{extends file='Tpl:empty.tpl'}
{block name="pagetitle"}{/block}
{block name="content"}
<h1>{t}Helpdesk page{/t}</h1>
<p>{t}Upon request, please download and forward the following information to the support service:{/t}</p>
<div class="text-center mb-2">
<a href="{$request->current_url}?download" class="btn btn-primary">
<i class="fa fa-download" aria-hidden="true"></i>
{t}Download{/t}
</a>
</div>
<pre class="text-bg-light p-3 copyable">{include file="Tpl:support_info_content.tpl"}</pre>
{/block}

View file

@ -0,0 +1,8 @@
{t}Application URL:{/t} {$public_root_url}
{t}Current page URL:{/t} {$public_root_url}/{$request->current_url}
{t}Connected user:{/t} {$auth_user->username}
{t}Extra user information:{/t}
================================================
{var_dump data=$auth_user->info}