Reorganization.

This commit is contained in:
Alexis Lahouze 2015-06-05 23:34:04 +02:00
parent 85c915e02b
commit c5741a018a
32 changed files with 3 additions and 3235 deletions

View File

@ -1,6 +0,0 @@
from flask import Blueprint
api = Blueprint('api', __name__)
from .controller import *

View File

@ -1,12 +0,0 @@
import pkgutil
from flask.ext.httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
__all__.append(module_name)
from . import *

View File

@ -1,134 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import auth
from .. import api
from ..model import db, session_scope
from ..model.accounts import Account
from ..model.entries import Entry
from ..model.operations import Operation
from flask import json, request
from sqlalchemy import func, case, cast, extract, distinct
from forms.accounts import AccountIdForm, AccountForm
@api.route("/accounts", methods=["GET"])
@auth.login_required
def get_accounts():
"""
Returns accounts with their solds.
"""
with session_scope() as session:
query = session.query(
Account.id.label("id"),
Account.name.label("name"),
Account.authorized_overdraft.label("authorized_overdraft"),
func.sum(Entry.value).label("future"),
func.sum(case([(Entry.pointed, Entry.value,)], else_=cast(0, db.Numeric(15, 2)))).label("pointed"),
func.sum(case([(Entry.operation_date < func.now(), Entry.value,)], else_=cast(0, db.Numeric(15, 2)))).label("current")
).outerjoin(Entry).group_by(Account.id).order_by(Account.id)
return json.dumps([{
"id": i.id,
"name": i.name,
"authorized_overdraft": i.authorized_overdraft,
"current": str(i.current),
"pointed": str(i.pointed),
"future": str(i.future)
} for i in query.all()])
@api.route("/accounts/<account_id>/<year>/<month>/")
@auth.login_required
def get_account_status(account_id, year, month):
with session_scope() as session:
query = session.query(
func.sum(case([(func.sign(Operation.value) == -1, Operation.value)], else_=0)).label("expenses"),
func.sum(case([(func.sign(Operation.value) == 1, Operation.value)], else_=0)).label("revenues"),
func.sum(Operation.value).label("balance")
).filter(
Operation.account_id == account_id
).filter(
func.date_trunc('month', Operation.operation_date) == "%s-%s-01" % (year, month)
).group_by(Operation.account_id)
if query.count() == 1:
result = query.one()
revenues = result.revenues
expenses = result.expenses
balance = result.balance
else:
revenues = 0.0
expenses = 0.0
balance = 0.0
return json.dumps({
"expenses": str(expenses),
"revenues": str(revenues),
"balance": str(balance)
})
@api.route("/accounts/<account_id>/months")
@auth.login_required
def get_months(account_id):
with session_scope() as session:
query = session.query(
distinct(func.lpad(cast(extract("year", Entry.operation_date), db.String), 4, '0')).label("year"),
func.lpad(cast(extract("month", Entry.operation_date), db.String), 2, '0').label("month")
).filter(Entry.account_id == account_id).order_by("year", "month")
return json.dumps([{
"year": i.year,
"month": i.month.rjust(2, '0')
} for i in query.all()])
@api.route("/accounts", methods=["PUT"])
@auth.login_required
def add_account():
with session_scope() as session:
account = Account(request.json['name'], request.json['authorized_overdraft'])
session.add(account)
return json.dumps("Account added.")
@api.route("/accounts/<account_id>", methods=["PUT"])
@auth.login_required
def update_account(account_id):
account_form = AccountForm()
if account_form.validate():
with session_scope() as session:
account = session.query(Account).filter(Account.id == account_id).first()
account.name = request.json['name']
account.authorized_overdraft = request.json['authorized_overdraft']
session.merge(account)
return json.dumps("Account #%s updated." % account_id)
else:
return json.dumps({'ok': False, 'error_type': 'validation', 'errors': account_form.errors})
@api.route("/accounts/<account_id>", methods=["DELETE"])
@auth.login_required
def delete_account(account_id):
with session_scope() as session:
account = session.query(Account).filter(Account.id == account_id).first()
session.delete(account)
return json.dumps("Account #%s deleted." % account_id)

View File

@ -1,101 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from .. import api
from ..model import db, session_scope
from ..model.entries import Entry
from ..model.operations import Operation
from ..model.scheduled_operations import ScheduledOperation
from flask import json, request
from sqlalchemy import func, desc
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.orm import sessionmaker, column_property, aliased
from sqlalchemy.sql import func, select, case
@api.route("/entries/<account_id>/<year>/<month>")
def get_entries(account_id, year, month):
"""
Return entries for an account, year, and month.
"""
with session_scope() as session:
base_query = session.query(
Operation,
case(whens={Operation.canceled: None}, else_=func.sum(Operation.value).over(partition_by="canceled", order_by="operation_date, value desc, label desc")).label("sold")
).filter(Operation.account_id == account_id).order_by(
desc(Operation.operation_date),
Operation.value,
Operation.label,
).subquery()
query = session.query(base_query).select_from(base_query).filter(func.date_trunc('month', base_query.c.operation_date) == "%s-%s-01" % (year, month))
return json.dumps([{
"id": i.id,
"pointed": i.pointed,
"operation_date": i.operation_date.strftime("%Y-%m-%d"),
"label": i.label,
"value": str(i.value),
"category": i.category,
"sold": str(i.sold) if not i.canceled else None,
"account_id": i.account_id,
"canceled": i.canceled,
"scheduled_operation_id": i.scheduled_operation_id
} for i in query.all()])
@api.route("/entries", methods=["PUT"])
def add_entry():
with session_scope() as session:
entry = Entry(
operation_date = request.json['operation_date'],
pointed = request.json['pointed'],
label = request.json['label'],
value = request.json['value'],
category = request.json['category'],
account_id = request.json['account_id'],
scheduled_operation_id = request.json['scheduled_operation_id']
)
session.add(entry)
return json.dumps("Entry added.")
@api.route("/entries/<entry_id>", methods=["PUT"])
def update_entry(entry_id):
with session_scope() as session:
entry = session.query(Entry).filter(Entry.id == entry_id).first()
entry.id = entry_id
entry.operation_date = request.json['operation_date']
entry.pointed = request.json['pointed']
entry.label = request.json['label']
entry.value = request.json['value']
entry.category = request.json['category']
entry.account_id = request.json['account_id']
entry.scheduled_operation_id = request.json['scheduled_operation_id']
session.merge(entry)
return json.dumps("Entry #%s updated." % entry_id)
@api.route("/entries/<entry_id>", methods=["DELETE"])
def delete_entry(entry_id):
with session_scope() as session:
entry = session.query(Entry).filter(Entry.id == entry_id).first()
session.delete(entry)
return json.dumps("Entry #%s deleted." % entry_id)

View File

@ -1,85 +0,0 @@
from .. import api
from ..model import db, session_scope
from ..model.scheduled_operations import ScheduledOperation
from flask import json, request
from sqlalchemy import func, desc
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.orm import sessionmaker, column_property
from sqlalchemy.sql import func, select
@api.route("/scheduled_operations/<account_id>")
def get_scheduled_operations(account_id):
"""
Return entries for an account, year, and month.
"""
with session_scope() as session:
query = session.query(
ScheduledOperation
).select_from(
session.query(ScheduledOperation)
.filter(ScheduledOperation.account_id == account_id)
.order_by(
desc(ScheduledOperation.day),
ScheduledOperation.value,
ScheduledOperation.label,
).subquery()
)
return json.dumps([{
"id": i.id,
"start_date": i.start_date.strftime("%Y-%m-%d"),
"stop_date": i.stop_date.strftime("%Y-%m-%d"),
"day": str(i.day),
"frequency": str(i.frequency),
"label": i.label,
"value": str(i.value),
"category": i.category,
"account_id": i.account_id
} for i in query.all()])
@api.route("/scheduled_operations", methods=["PUT"])
def add_scheduled_operation():
with session_scope() as session:
scheduledOperation = ScheduledOperation(
start_date = request.json['start_date'],
stop_date = request.json['stop_date'],
day = request.json['day'],
frequency = request.json['frequency'],
label = request.json['label'],
value = request.json['value'],
category = request.json['category'],
account_id = request.json['account_id']
)
session.add(scheduledOperation)
return json.dumps("Scheduled operation added.")
@api.route("/scheduled_operations/<scheduled_operation_id>", methods=["PUT"])
def update_scheduled_operation(scheduled_operation_id):
with session_scope() as session:
scheduledOperation = session.query(ScheduledOperation).filter(ScheduledOperation.id == scheduled_operation_id).first()
scheduledOperation.id = scheduled_operation_id
scheduledOperation.start_date = request.json['start_date'],
scheduledOperation.stop_date = request.json['stop_date'],
scheduledOperation.day = request.json['day'],
scheduledOperation.frequency = request.json['frequency'],
scheduledOperation.label = request.json['label']
scheduledOperation.value = request.json['value']
scheduledOperation.category = request.json['category']
scheduledOperation.account_id = request.json['account_id']
session.merge(scheduledOperation)
return json.dumps("Scheduled operation #%s updated." % scheduled_operation_id)
@api.route("/scheduled_operations/<scheduled_operation_id>", methods=["DELETE"])
def delete_scheduled_operation(scheduled_operation_id):
with session_scope() as session:
scheduledOperation = session.query(ScheduledOperation).filter(ScheduledOperation.id == scheduled_operation_id).first()
session.delete(scheduledOperation)
return json.dumps("Scheduled operation #%s deleted." % scheduled_operation_id)

View File

@ -1,29 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Accountant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import auth
from .. import api
from ..model import db, session_scope
@auth.verify_password
def verify_password(username, password):
if username == 'titi' and password == 'toto':
return True
# Update principal identity
return False

View File

@ -1,31 +0,0 @@
from contextlib import contextmanager
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
db = SQLAlchemy()
@contextmanager
def session_scope():
#session = scoped_session(sessionmaker(autocommit = False, autoflush = False, bind = engine))
session = db.session
#Base.query = session.query_property()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
import pkgutil
__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
__all__.append(module_name)
from . import *

View File

@ -1,27 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import db
class Account(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(200), nullable = False)
authorized_overdraft = db.Column(db.Integer, nullable = True, default = 0)
def __init__(self, name, authorized_overdraft):
self.name = name
self.authorized_overdraft = authorized_overdraft

View File

@ -1,46 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import db
from .accounts import Account
from .scheduled_operations import ScheduledOperation
from sqlalchemy import func, desc
from sqlalchemy.orm import column_property
from sqlalchemy.sql import func, select
class Entry(db.Model):
id = db.Column(db.Integer, primary_key=True)
pointed = db.Column(db.Boolean, nullable = False, default = False)
operation_date = db.Column(db.Date, nullable = False)
label = db.Column(db.String(500), nullable = False)
value = db.Column(db.Numeric(15, 2), nullable = False)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
scheduled_operation_id = db.Column(db.Integer, db.ForeignKey('scheduled_operation.id'))
account = db.relationship(Account, backref = db.backref('entry', lazy="dynamic"))
scheduled_operation = db.relationship(ScheduledOperation, backref = db.backref('entry', lazy="dynamic"))
category = db.Column(db.String(100), nullable = True)
def __init__(self, pointed, label, value, account_id, operation_date = None, category = None, scheduled_operation_id = None):
self.pointed = pointed
self.operation_date = operation_date
self.label = label
self.value = value
self.account_id = account_id
self.category = category
self.scheduled_operation_id = scheduled_operation_id

View File

@ -1,48 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from . import db
from .accounts import Account
from .scheduled_operations import ScheduledOperation
from sqlalchemy import func, desc
from sqlalchemy.orm import column_property
from sqlalchemy.sql import func, select
class Operation(db.Model):
id = db.Column(db.Integer, primary_key=True)
pointed = db.Column(db.Boolean, nullable = False, default = False)
operation_date = db.Column(db.Date, nullable = False)
label = db.Column(db.String(500), nullable = False)
value = db.Column(db.Numeric(15, 2), nullable = False)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
scheduled_operation_id = db.Column(db.Integer, db.ForeignKey('scheduled_operation.id'))
account = db.relationship(Account, backref = db.backref('operation', lazy="dynamic"))
category = db.Column(db.String(100), nullable = True)
canceled = db.Column(db.Boolean, nullable = False)
def __init__(self, pointed, label, value, account_id, operation_date = None, category = None):
self.pointed = pointed
self.operation_date = operation_date
self.label = label
self.value = value
self.account_id = account_id
self.category = category

View File

@ -1,30 +0,0 @@
from . import db
from .accounts import Account
from sqlalchemy import func, desc
from sqlalchemy.orm import column_property
from sqlalchemy.sql import func, select
class ScheduledOperation(db.Model):
id = db.Column(db.Integer, primary_key=True)
start_date = db.Column(db.Date, nullable = False)
stop_date = db.Column(db.Date, nullable = False)
day = db.Column(db.Integer, nullable = False)
frequency = db.Column(db.Integer, nullable = False)
label = db.Column(db.String(500), nullable = False)
value = db.Column(db.Numeric(15, 2), nullable = False)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
account = db.relationship(Account, backref = db.backref('scheduled_operation', lazy="dynamic"))
category = db.Column(db.String(100), nullable = True)
def __init__(self, start_date, stop_date, day, frequency, label, value, account_id, category = None):
self.start_date = start_date
self.stop_date = stop_date
self.day = day
self.frequency = frequency
self.label = label
self.value = value
self.account_id = account_id
self.category = category

40
app.py
View File

@ -1,40 +0,0 @@
"""
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Accountant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
"""
from api import api
#from api.controller import login_manager
from api.model import db
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from frontend import frontend
from sqlalchemy.orm import sessionmaker
import config
# The app
app = Flask(__name__, static_folder = None)
app.config['SQLALCHEMY_DATABASE_URI'] = config.db_uri
app.config['SQLALCHEMY_RECORD_QUERIES'] = config.debug
app.config['WTF_CSRF_ENABLED'] = False
app.config['SECRET_KEY'] = 'my_secret_key'
db.init_app(app)
#login_manager.init_app(app)
app.register_blueprint(frontend, url_prefix='')
app.register_blueprint(api, url_prefix='/api')

View File

@ -1,3 +0,0 @@
db_uri='postgresql://accountant:accountant@localhost/accountant'
debug=True

View File

@ -1,19 +0,0 @@
from flask import Blueprint, redirect, render_template, jsonify
frontend = Blueprint('frontend', __name__, template_folder='templates', static_folder='static')
@frontend.route('/')
def root():
return redirect('index.html')
@frontend.route('/index.html')
def index():
return render_template('index.html')
@frontend.route('/scheduler.html')
def scheduler():
return render_template('scheduler.html')
@frontend.errorhandler(BaseException)
def default_errorhandler(error):
return jsonify(title="Error", text="Error %s" % str(error)), 500

View File

@ -1,7 +0,0 @@
.italic {
font-style: italic
}
.stroke {
text-decoration: line-through
}

View File

@ -1,197 +0,0 @@
/*
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
*/
// Account object
function account() {
this.id=ko.observable();
this.name=ko.observable();
this.authorized_overdraft=ko.observable();
this.future=ko.observable();
this.current=ko.observable();
this.pointed=ko.observable();
};
// The AccountController.
var AccountController = function($scope, $http, $rootScope, $window) {
// Account store and selection
$scope.account = null;
$scope.accounts = [];
$scope.savedAccount = null;
$scope.editingAccount = null;
$scope.removedAccount = null;
$scope.accountClass = function(account) {
if(account == $scope.account) {
return "active";
}
};
$scope.valueClass = function(account, value) {
if(!account) {
return
}
if(value < account.authorized_overdraft) {
return "text-error";
} else if(value < 0) {
return "text-warning";
}
};
$scope.addAccount = function() {
$scope.editingAccount = {
id: null,
name: null,
authorized_overdraft: null
};
angular.element("#edit-account").modal();
};
$scope.show = function() {
console.debug("show");
};
$scope.editAccount = function(account) {
if(account) {
$scope.editingAccount = account;
$scope.savedAccount = angular.copy(account);
}
};
$scope.cancelEditAccount = function(account, modalScope) {
if($scope.savedAccount) {
account.name = $scope.savedAccount.name;
account.authorized_overdraft = $scope.savedAccount.authorized_overdraft;
}
$scope.editingAccount = null;
$scope.savedAccount = null;
if(modalScope && modalScope.dismiss) {
modalScope.dismiss();
}
};
$scope.saveAccount = function(account, modalScope) {
// Ajax call to save the entry.
var type;
var url = "/api/accounts";
if(account.id) {
url += "/" + account.id;
}
$http.put(url, angular.toJson(account)).success(function(data) {
$.pnotify({
type: "success",
title: "Save",
text: data
});
$scope.editingAccount = null;
$scope.savedAccount = null;
if(modalScope && modalScope.dismiss) {
modalScope.dismiss();
}
// Reload accounts to update solds.
$scope.loadAccounts();
}).error(function(data) {
if(data.error_type == 'validation') {
angular.forEach(data.errors, function(errors, field) {
// $scope.form[field].$setValidity('server', false);
//$scope.errors[field] = errors.join(', ');
});
} else {
$.pnotify({
type: "error",
title: "Save",
text: data.errors
});
}
});
};
// Function to remove of an entry.
$scope.accountRemove = function(account, modalScope) {
$http.delete("/api/accounts/" + account.id).success(function (data) {
$.pnotify({
type: "success",
title: "Save",
text: data
});
// Reload accounts to update solds.
$scope.loadAccounts();
if(modalScope && modalScope.dismiss) {
modalScope.dismiss();
}
});
};
// Function to load accounts
$scope.loadAccounts = function() {
$http.get("/api/accounts").success($scope.loadAccounts_success);
};
$scope.loadAccounts_success = function (data) {
// Update accounts
$scope.accounts = angular.fromJson(data);
var accountToSelect = null
// Reset selected account to the new instance corresponding to the old one.
if($scope.account) {
// Find the new instance of the previously selected account.
angular.forEach($scope.accounts, function(account) {
if(account.id == $scope.account.id) {
accountToSelect = account;
}
});
}
// Set selected account to first one if not yet selected
if(!accountToSelect && $scope.accounts.length > 0){
accountToSelect = $scope.accounts[0];
}
// Reset to account to select
$scope.account = accountToSelect;
$scope.$emit("accountsLoadedEvent", {account: $scope.account});
};
// Callback function to select a new account.
$scope.selectAccount = function(account) {
if(account) {
$scope.account = account;
$scope.$emit("accountsLoadedEvent", {account: $scope.account});
}
};
$rootScope.$on("entrySavedEvent", $scope.loadAccounts);
$rootScope.$on("entryRemovedEvent", $scope.loadAccounts);
$rootScope.$on("operationSavedEvent", $scope.loadAccounts);
$rootScope.$on("operationRemovedEvent", $scope.loadAccounts);
$scope.loadAccounts();
};

View File

@ -1,451 +0,0 @@
/*
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
*/
var EntryController = function($scope, $http, $rootScope, $filter) {
// Entry store and selection
$scope.entries = [];
$scope.categories = [];
$scope.chartValues = [];
$scope.pieChartValues = [];
$scope.selectedItem = null;
$scope.account = null;
// Placeholder for saved value to cancel entry edition
$scope.savedItem = null;
$scope.entriesLoaded = function(entries) {
var entriesReversed = entries.slice().reverse();
var categories = [];
var chartValues = [];
var pieChartValuesTmp = {};
var pieChartValues = [];
angular.forEach(entriesReversed, function(entry) {
var category = entry.category;
var value = entry.value ? Number(entry.value) : null;
var sold = entry.sold ? Number(entry.sold) : null;
var operation_date = entry.operation_date;
if(operation_date && sold) {
chartValues.push([entry.operation_date, sold]);
}
if(category && category != '') {
if(categories.indexOf(category) == -1) {
categories.push(category);
}
if(value && value < 0.0) {
var oldValue = 0.0;
if(pieChartValuesTmp[category]) {
oldValue = pieChartValuesTmp[category];
}
pieChartValuesTmp[category] = oldValue - value;
}
}
});
$scope.chartValues = chartValues;
// Second pass: transform to an array readable by jqplot.
var pieChartValues = [];
angular.forEach(pieChartValuesTmp, function(value, key) {
pieChartValues.push([key, value]);
});
$scope.pieChartValues = pieChartValues;
//$scope.categories.length = 0;
//$scope.categories.concat(categories);
$scope.categories = categories;
nv.addGraph($scope.drawChart);
nv.addGraph($scope.drawPieChart);
//$scope.drawPieChart(pieChartValues, "#expense-categories-chart-placeholder");
//$scope.drawPieChart();
};
$scope.getAccountStatus = function(account, month) {
if(account != null && month != null) {
$http.get("/api/accounts/" + account.id + "/" + month.year + "/" + month.month).
success($scope.getAccountStatus_success);
}
};
$scope.getAccountStatus_success = function(status) {
$scope.accountStatus = status;
};
// Function to load entries from server for a specific account and month.
$scope.loadEntries = function(account, month) {
if(account) {
$scope.account = account;
}
// Clean up selected entry.
$scope.selectedItem = null;
$scope.savedItem = null;
if(account && month) {
$http.get("/api/entries/" + account.id + "/" + month.year + "/" + month.month).success($scope.loadEntries_success);
} else {
$scope.loadEntries_success(null);
}
};
// Load entries success callback
$scope.loadEntries_success = function(data) {
var entries = [{
id: null,
pointed: false,
operation_date: null,
label: null,
value: null,
sold: null,
pointedsold: null,
category: null,
account_id: null,
state: 'new',
canceled: false,
scheduled_operation_id: null
}];
if(data) {
entries = entries.concat(angular.fromJson(data));
}
$scope.entries = entries;
$scope.$emit("entriesLoadedEvent", {entries: entries});
};
// Returns the CSS class for a pointed entry.
$scope.pointedEntryClass = function(entry) {
if(entry.pointed) {
return "active";
}
};
// Returns the CSS class for an entry row.
$scope.entryRowClass = function(entry) {
if($scope.isSaved(entry)) {
cssclass="";
} else {
cssclass="italic";
}
if(entry.canceled) {
cssclass += " stroke";
}
if(entry.sold < $scope.account.authorized_overdraft) {
cssclass += " danger";
} else if (entry.sold < 0) {
cssclass += " warning";
}
return cssclass;
};
// Returns the CSS class for an entry sold.
$scope.entryValueClass = function(sold) {
if(sold && sold < $scope.account.authorized_overdraft) {
return 'text-danger';
} else if (sold && sold < 0) {
return 'text-warning';
}
};
// Starts editing an entry
$scope.editEntry = function(entry) {
// Cancel previous editing.
if($scope.selectedItem) {
$scope.cancelEditEntry($scope.selectedItem);
}
// Save current entry values.
$scope.savedItem = angular.copy(entry);
$scope.selectedItem = entry;
// Enter edit state.
entry.state='edit';
};
// Returns true if the entry is a new one.
$scope.isNew = function(entry) {
return !$scope.isSaved(entry) && (entry.state === 'edit' || entry.state === 'new');
};
// Returns true if the entry is in editing state.
$scope.isEditing = function(entry) {
return entry.state === 'edit' || entry.state === 'new';
};
// Returns true if the entry is in displaying state.
$scope.isDisplaying = function(entry) {
return !entry.state || entry.state === 'display';
};
// Returns true if the entry is a scheduled one.
$scope.isSaved = function(entry) {
return entry.id != null;
}
$scope.iconSaveClass = function(entry) {
if(!$scope.isSaved(entry)) {
return "fa fa-plus";
} else if ($scope.isEditing(entry)) {
return "fa fa-floppy-o";
}
};
$scope.iconCancelClass = function(entry) {
if($scope.isNew(entry)) {
return "fa fa-times";
} else if ($scope.isEditing(entry)) {
return "fa fa-ban";
}
};
// Cancel current editing entry or clears field if a new one.
$scope.cancelEditEntry = function(entry) {
if ($scope.savedItem == null) {
// We are cancelling the new entry, just reset it.
entry.id = null; // id should not change, but just in case...
entry.pointed = false;
entry.operation_date = null;
entry.label = null;
entry.value = null;
entry.account_id = $scope.account.id; // account_id should not change, but just in case...
entry.category = null;
entry.canceled = false;
entry.scheduled_operation_id = null;
// Reset state to new.
entry.state = 'new';
} else {
// Reset selected item fields to saved item ones.
entry.id = $scope.savedItem.id; // id should not change, but just in case...
entry.pointed = $scope.savedItem.pointed;
entry.operation_date = $scope.savedItem.operation_date;
entry.label = $scope.savedItem.label;
entry.value = $scope.savedItem.value;
entry.account_id = $scope.savedItem.account_id; // account_id should not change, but just in case...
entry.category = $scope.savedItem.category;
entry.canceled = $scope.savedItem.canceled;
entry.scheduled_operation_id = $scope.savedItem.scheduled_operation_id;
// Reset saved and selected items to null.
$scope.savedItem = null;
$scope.selectedItem = null;
// Enter display state.
entry.state = 'display';
}
};
// Points an entry.
$scope.pointEntry = function(entry) {
if(entry.pointed) {
entry.pointed = false;
} else {
entry.pointed = true;
}
// Save the entry if not new.
if(!$scope.isNew(entry)) {
$scope.saveEntry(entry);
}
};
// Saves an entry.
$scope.saveEntry = function(entry) {
// Affects the account ID if not (should never happen)
if(!entry.account_id) {
entry.account_id = $scope.account.id;
}
// Prepare the Ajax call to save the entry.
var type;
var url = "/api/entries";
if($scope.isSaved(entry)) {
url += "/" + entry.id;
}
// Ajax call to save an entry
$http.put(url, angular.toJson(entry)).success(function(data) {
$.pnotify({
type: "success",
title: "Save",
text: data
});
// $scope.savedItem = null;
// Send the "entry saved" event.
$scope.$emit("entrySavedEvent", entry);
});
};
$scope.removeEntry = function(entry) {
$scope.removingEntry = entry;
$("#remove_entry").modal({
keyboard: false,
});
};
$scope.hideRemoveEntryPopup = function() {
$scope.removingEntry = null;
$("#remove_entry").modal("hide");
};
// Removes an entry.
$scope.confirmRemoveEntry = function() {
// Cancel current editing.
if ($scope.removingEntry) {
$http.delete("/api/entries/" + $scope.removingEntry.id).success(function (result) {
$.pnotify({
type: "success",
title: "Delete",
text: result
});
// Send the "entry removed" event.
$scope.$emit("entryRemovedEvent", $scope.removingEntry);
$scope.hideRemoveEntryPopup();
});
}
};
$scope.closeModal = function(modalScope) {
// Close the modal dialog
if(modalScope && modalScope.dismiss) {
modalScope.dismiss();
}
};
// Function to draw the sold evolution chart.
$scope.drawChart = function() {
// Clear previous chart
var entries = $scope.chartValues;
console.debug("drawChart", entries);
var width = 700;
var height = 300;
//if(entries && entries.length > 1) {
// Prepare for today vertical line.
var today = new Date();
today.setHours(0);
today.setMinutes(0);
// Find first and last days to set limits of the x axis.
var day = 24 * 60 * 60 * 1000;
var firstDate = $filter('date')(new Date(Date.parse(entries[0][0]).valueOf() - day), 'yyyy-MM-dd');
var lastDate = $filter('date')(new Date(Date.parse(entries[entries.length -1][0]).valueOf() + day), 'yyyy-MM-dd');
var chart = nv.models.lineChart().options({
x: function(d) { return d3.time.format("%Y-%m-%d").parse(d[0]); },
y: function(d) { return new Number(d[1]); },
transitionDuration: 250,
showXAxis: true,
showYAxis: true,
width: width,
height: height,
});
chart.lines.interpolate("monotone");
chart.xAxis
.axisLabel("Date")
.tickFormat(function(d) {
return d3.time.format("%Y-%m-%d")(new Date(d));
});
//chart.xAxis.scale().range([firstDate, lastDate]);
chart.yAxis
.axisLabel("Solde")
.tickFormat(d3.format('.02f'));
// FIXME add vertical line for today
graph = d3.select("#entries-chart-placeholder").datum([
{ color: "orange", key: "Zero", values:[
[firstDate, "0"],
[lastDate, "0"]
]},
{ color: "red", key: "Authorized overdraft", values : [
[firstDate, new Number($scope.account.authorized_overdraft)],
[lastDate, new Number($scope.account.authorized_overdraft)]
]},
{ color: "darkblue", key: "Sold evolution", values: entries},
])
.transition().duration(1200)
.attr("width", width)
.attr("height", height)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
};
// Function to draw the expense category pie chart.
$scope.drawPieChart = function() {
// FIXME retrieve width and height from DOM
var width = 300;
var height = 300;
var chart = nv.models.pieChart()
.x(function(d) { console.debug(d); return d[0]; })
.y(function(d) { return d[1]; })
.width(width)
.height(height)
.showLabels(true);
d3.select("#expense-categories-chart-placeholder")
.datum($scope.pieChartValues)
.transition().duration(1200)
.attr('width', width)
.attr('height', height)
.call(chart);
nv.utils.windowResize(chart.update);
return chart
};
$rootScope.$on("monthsLoadedEvent", function(event, args){
$scope.loadEntries(args.account, args.month);
});
$rootScope.$on("monthsLoadedEvent", function(event, args){
$scope.getAccountStatus(args.account, args.month);
});
$scope.$on("entriesLoadedEvent", function(event, args) {
$scope.entriesLoaded(args.entries);
});
};

View File

@ -1,116 +0,0 @@
/*
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
*/
// Month object
function month() {
this.year = null;
this.month = null;
}
var MonthController = function($scope, $http, $rootScope) {
// Month store and selection
$scope.months = null;
$scope.month = null;
$scope.account = null;
$scope.monthClass = function(month) {
if(month == $scope.month) {
return "active";
}
};
// Function to load months
$scope.loadMonths = function(account) {
if(account) {
$scope.account = account;
}
if($scope.account) {
$http.get("/api/accounts/" + $scope.account.id + "/months").success($scope.loadMonths_success);
} else {
$scope.$emit("monthsLoadedEvent", {account: null, month: null});
}
};
$scope.loadMonths_success = function (data) {
// Update months
$scope.months = angular.fromJson(data);
var monthToSelect = null;
var today = new Date();
var currentYear = today.getFullYear().toString();
var currentMonth = (today.getMonth() + 1 < 10 ? "0" : "") + (today.getMonth() + 1);
var found = false;
var lastIndex = 0;
// Add current month if not present.
angular.forEach($scope.months, function(month) {
if(!found) {
if (month.year == currentYear && month.month == currentMonth) {
found = true;
}
if (parseInt(month.year) < parseInt(currentYear) || (month.year == currentYear && parseInt(month.month) < parseInt(currentMonth))) {
lastIndex++;
}
}
});
if(!found) {
$scope.months.splice(lastIndex, 0, {year: currentYear, month: currentMonth});
}
// Find the new instance of the previously selected month.
angular.forEach($scope.months, function(month) {
// Reset selected month to the new instance corresponding to the old one
if($scope.month) {
if(month.year == $scope.month.year && month.month == $scope.month.month) {
monthToSelect = month;
}
}
if(!monthToSelect && month.year == currentYear && month.month == currentMonth) {
monthToSelect = month;
}
});
// Set selected month to the last one if not yet selected.
if(!monthToSelect && $scope.months.length > 0) {
monthToSelect = $scope.months[$scope.months.length - 1];
}
// Reset to month to select
$scope.month = monthToSelect;
$scope.$emit("monthsLoadedEvent", {account: $scope.account, month: $scope.month});
};
// Callback function to select a new month.
$scope.selectMonth = function(month) {
if(month) {
$scope.month = month;
$scope.$emit("monthsLoadedEvent", {account: $scope.account, month: month});
}
};
$rootScope.$on("accountsLoadedEvent", function(event, args) {
$scope.loadMonths(args.account);
});
};

View File

@ -1,197 +0,0 @@
/*
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
*/
var SchedulerController = function($scope, $http, $rootScope, $filter) {
// Operations store and selection
$scope.operations = [];
$scope.selectedOperation = null;
$scope.account = null;
// Placeholder for saved value to cancel entry edition
$scope.savedOperation = null;
$scope.loadOperations = function(account) {
// Clean up selected entry.
$scope.selectedOperation = null;
$scope.savedOperation = null;
if(account) {
$scope.account = account;
$http.get("/api/scheduled_operations/" + account.id).success($scope.loadOperations_success);
} else {
$scope.loadOperations_success(null);
}
};
$scope.loadOperations_success = function(data) {
var operations = [{
id: null,
start_date: null,
stop_date: null,
day: null,
frequency: null,
label: null,
account_id: null,
category: null,
state: "edit"
}];
if(data) {
operations = operations.concat(angular.fromJson(data));
}
$scope.operations = operations;
$scope.$emit("operationsLoadedEvent", {operations: operations});
};
$scope.iconSaveClass = function(operation) {
if($scope.isNew(operation)) {
return "fa fa-plus";
} else if ($scope.isEditing(operation)) {
return "fa fa-floppy-o";
}
};
$scope.iconCancelClass = function(operation) {
if($scope.isNew(operation)) {
return "fa fa-times";
} else if ($scope.isEditing(operation)) {
return "fa fa-ban";
}
};
$scope.isNew = function(operation) {
return !operation.id;
};
// Returns true if the entry is in editing state.
$scope.isEditing = function(operation) {
return operation.state === 'edit';
};
$scope.isDisplaying = function(operation) {
return operation.id && (!operation.state || operation.state === 'display');
};
$scope.saveOperation = function(operation) {
if(!operation.account_id) {
operation.account_id = $scope.account.id;
}
// prepare the Ajax xall to save the sceduled operation.
var type;
var url = "/api/scheduled_operations";
if(!$scope.isNew(operation)) {
url += "/" + operation.id;
}
$http.put(url, angular.toJson(operation)).success(function(data) {
$.pnotify({
type: "success",
title: "Save",
text: data
});
$scope.$emit("operationSavedEvent", operation);
});
};
$scope.editOperation = function(operation) {
// Cancel previous editing.
if($scope.selectedOperation) {
$scope.cancelEditOperation($scope.selectedItem);
}
// Save current entry values.
$scope.savedOperation = angular.copy(operation);
$scope.selectedOperation = operation;
// Enter edit state.
operation.state='edit';
};
$scope.cancelEditOperation = function(operation) {
if ($scope.isNew(operation)) {
operation.id = null;
operation.start_date = null;
operation.stop_date = null;
operation.day = null;
operation.frequency = null;
operation.label = null;
operation.value = null;
operation.category = null;
operation.account_id = $scope.account.id;
} else if ($scope.isEditing(operation)) {
if($scope.savedOperation) {
operation.id = $scope.savedOperation.id;
operation.start_date = $scope.savedOperation.start_date;
operation.stop_date = $scope.savedOperation.stop_date;
operation.day = $scope.savedOperation.day;
operation.frequency = $scope.savedOperation.frequency;
operation.label = $scope.savedOperation.label;
operation.value = $scope.savedOperation.value;
operation.category = $scope.savedOperation.category;
operation.account_id = $scope.savedOperation.account_id;
}
$scope.savedOperation = null;
$scope.selectedOperation = null;
operation.state = 'display';
}
};
$scope.removeOperation = function(operation, modalScope) {
// Cancel current editing.
if (!$scope.isNew(operation)) {
$http.delete("/api/scheduled_operations/" + operation.id).success(function (result) {
$.pnotify({
type: "success",
title: "Delete",
text: result
});
// Send the "entry removed" event.
$scope.$emit("operationRemovedEvent", operation);
$scope.closeModal(modalScope);
}).error(function (data) {
$.pnotify({
type: "error",
title: "Delete",
text: data
});
$scope.closeModal(modalScope);
});
}
};
$scope.closeModal = function(modalScope) {
// Close the modal dialog
if(modalScope && modalScope.dismiss) {
modalScope.dismiss();
}
};
$rootScope.$on("accountsLoadedEvent", function(event, args){
$scope.loadOperations(args.account);
});
}

View File

@ -1,51 +0,0 @@
<!--
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
-->
<!-- Dialog header with title -->
<div class="modal-header">
<h3>&Eacute;diter le compte [[account.name]]</h3>
</div>
<!-- Dialog body -->
<div class="modal-body">
<form class="form-horizontal" role="form">
<div class="form-group">
<!-- Account name field -->
<label class="control-label" for="inputName">Nom du compte</label>
<input type="text" class="form-control" id="inputName" ng-model="account.name"/>
<div class="errors" ng-show="form.inputName.$dirty && form.inputName.$invalid">
<span ng-show="form.inputName.$error.server">{{ errors.name }}</span>
</div>
<!-- Authorized overdraft field -->
<label class="control-label" for="inputAuthorizedOverdraft">D&eacute;couvert authoris&eacute;</label>
<input type="text" class="form-control" id="inputAuthorizedOverdraft" ng-model="account.authorized_overdraft"/>
<div class="errors" ng-show="form.inputAuthorizedOverdraft.$dirty && form.inputAuthorizedOverdraft.$invalid">
<span ng-show="form.inputAuthorizedOverdraft.$error.server">{{ errors.authorized_overdraft }}</span>
</div>
</div>
</form>
</div>
<!-- Dialog footer with buttons -->
<div class="modal-footer">
<a href="#" class="btn btn-primary" ng-click="saveAccount(account, this)">OK</a>
<a href="#" class="btn" ng-click="cancelEditAccount(account, this)">Annuler</a>
</div>

View File

@ -1,47 +0,0 @@
<!--
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
-->
<!-- Dialog header with title -->
<div class="modal-header">
<h3>Nouveau compte</h3>
</div>
<!-- Dialog body -->
<div class="modal-body">
<form name="account" class="form-horizontal">
<div class="control-group">
<!-- Account name field -->
<label class="control-label" for="inputName">Nom du compte</label>
<div class="controls">
<input type="text" id="inputName" ng-model="account.name"></input>
</div>
<!-- Authorized overdraft field -->
<label class="control-label" for="inputAuthorizedOverdraft">D&eacute;couvert authoris&eacute;</label>
<div class="controls">
<input type="text" id="inputAuthorizedOverdraft" ng-model="account.authorized_overdraft"></input>
</div>
</div>
</form>
</div>
<!-- Dialog footer with buttons -->
<div class="modal-footer">
<a href="#" class="btn btn-primary" ng-click="saveAccount(account, this)">OK</a>
<a href="#" class="btn" ng-click="cancelEditAccount(account, this)">Annuler</a>
</div>

View File

@ -1,32 +0,0 @@
<!--
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
-->
<!-- Dialog header with title -->
<div class="modal-header">
<h3>Supprimer le compte [[account.name]]</h3>
</div>
<!-- Dialog body -->
<div class="modal-body">
<p>Confirmez-vous la suppression du compte [[account.name]] ?</p>
</div>
<!-- Dialog footer with buttons -->
<div class="modal-footer">
<a href="#" class="btn btn-primary" data-dismiss="modal" aria-hidden="true">Non</a>
<a href="#" class="btn" ng-click="accountRemove(account, this)">Oui</a>
</div>

View File

@ -1,32 +0,0 @@
<!--
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
-->
<!-- Dialog header with title -->
<div class="modal-header">
<h3>Supprimer l'op&eacute;ration [[operation.label]]</h3>
</div>
<!-- Dialog body -->
<div class="modal-body">
<p>Confirmez-vous la suppression de cette op&eacute;ration ?</p>
</div>
<!-- Dialog footer with buttons -->
<div class="modal-footer">
<a href="#" class="btn btn-primary" ng-click="dismiss()">Non</a>
<a href="#" class="btn" ng-click="removeOperation(operation, this)">Oui</a>
</div>

View File

@ -1,83 +0,0 @@
/*
Document : jquery.pnotify.default.css
Created on : Nov 23, 2009, 3:14:10 PM
Author : Hunter Perrin
Version : 1.2.0
Link : http://pinesframework.org/pnotify/
Description:
Default styling for Pines Notify jQuery plugin.
*/
/* -- Notice */
.ui-pnotify {
top: 25px;
right: 25px;
position: absolute;
height: auto;
/* Ensures notices are above everything */
z-index: 9999;
}
/* Hides position: fixed from IE6 */
html > body .ui-pnotify {
position: fixed;
}
.ui-pnotify .ui-pnotify-shadow {
-webkit-box-shadow: 0px 2px 10px rgba(50, 50, 50, 0.5);
-moz-box-shadow: 0px 2px 10px rgba(50, 50, 50, 0.5);
box-shadow: 0px 2px 10px rgba(50, 50, 50, 0.5);
}
.ui-pnotify-container {
background-position: 0 0;
padding: .8em;
height: 100%;
margin: 0;
}
.ui-pnotify-sharp {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.ui-pnotify-closer, .ui-pnotify-sticker {
float: right;
margin-left: .2em;
}
.ui-pnotify-title {
display: block;
margin-bottom: .4em;
}
.ui-pnotify-text {
display: block;
}
.ui-pnotify-icon, .ui-pnotify-icon span {
display: block;
float: left;
margin-right: .2em;
}
/* -- History Pulldown */
.ui-pnotify-history-container {
position: absolute;
top: 0;
right: 18px;
width: 70px;
border-top: none;
padding: 0;
-webkit-border-top-left-radius: 0;
-moz-border-top-left-radius: 0;
border-top-left-radius: 0;
-webkit-border-top-right-radius: 0;
-moz-border-top-right-radius: 0;
border-top-right-radius: 0;
/* Ensures history container is above notices. */
z-index: 10000;
}
.ui-pnotify-history-container .ui-pnotify-history-header {
padding: 2px;
}
.ui-pnotify-history-container button {
cursor: pointer;
display: block;
width: 100%;
}
.ui-pnotify-history-container .ui-pnotify-history-pulldown {
display: block;
margin: 0 auto;
}

View File

@ -1,912 +0,0 @@
/*
* jQuery Pines Notify (pnotify) Plugin 1.2.0
*
* http://pinesframework.org/pnotify/
* Copyright (c) 2009-2012 Hunter Perrin
*
* Triple license under the GPL, LGPL, and MPL:
* http://www.gnu.org/licenses/gpl.html
* http://www.gnu.org/licenses/lgpl.html
* http://www.mozilla.org/MPL/MPL-1.1.html
*/
(function($) {
var history_handle_top,
timer,
body,
jwindow = $(window),
styling = {
jqueryui: {
container: "ui-widget ui-widget-content ui-corner-all",
notice: "ui-state-highlight",
// (The actual jQUI notice icon looks terrible.)
notice_icon: "ui-icon ui-icon-info",
info: "",
info_icon: "ui-icon ui-icon-info",
success: "ui-state-default",
success_icon: "ui-icon ui-icon-circle-check",
error: "ui-state-error",
error_icon: "ui-icon ui-icon-alert",
closer: "ui-icon ui-icon-close",
pin_up: "ui-icon ui-icon-pin-w",
pin_down: "ui-icon ui-icon-pin-s",
hi_menu: "ui-state-default ui-corner-bottom",
hi_btn: "ui-state-default ui-corner-all",
hi_btnhov: "ui-state-hover",
hi_hnd: "ui-icon ui-icon-grip-dotted-horizontal"
},
bootstrap: {
container: "alert",
notice: "",
notice_icon: "icon-exclamation-sign",
info: "alert-info",
info_icon: "icon-info-sign",
success: "alert-success",
success_icon: "icon-ok-sign",
error: "alert-error",
error_icon: "icon-warning-sign",
closer: "icon-remove",
pin_up: "icon-pause",
pin_down: "icon-play",
hi_menu: "well",
hi_btn: "btn",
hi_btnhov: "",
hi_hnd: "icon-chevron-down"
}
};
// Set global variables.
var do_when_ready = function(){
body = $("body");
jwindow = $(window);
// Reposition the notices when the window resizes.
jwindow.bind('resize', function(){
if (timer)
clearTimeout(timer);
timer = setTimeout($.pnotify_position_all, 10);
});
};
if (document.body)
do_when_ready();
else
$(do_when_ready);
$.extend({
pnotify_remove_all: function () {
var notices_data = jwindow.data("pnotify");
/* POA: Added null-check */
if (notices_data && notices_data.length) {
$.each(notices_data, function(){
if (this.pnotify_remove)
this.pnotify_remove();
});
}
},
pnotify_position_all: function () {
// This timer is used for queueing this function so it doesn't run
// repeatedly.
if (timer)
clearTimeout(timer);
timer = null;
// Get all the notices.
var notices_data = jwindow.data("pnotify");
if (!notices_data || !notices_data.length)
return;
// Reset the next position data.
$.each(notices_data, function(){
var s = this.opts.stack;
if (!s) return;
s.nextpos1 = s.firstpos1;
s.nextpos2 = s.firstpos2;
s.addpos2 = 0;
s.animation = true;
});
$.each(notices_data, function(){
this.pnotify_position();
});
},
pnotify: function(options) {
// Stores what is currently being animated (in or out).
var animating;
// Build main options.
var opts;
if (typeof options != "object") {
opts = $.extend({}, $.pnotify.defaults);
opts.text = options;
} else {
opts = $.extend({}, $.pnotify.defaults, options);
}
// Translate old pnotify_ style options.
for (var i in opts) {
if (typeof i == "string" && i.match(/^pnotify_/))
opts[i.replace(/^pnotify_/, "")] = opts[i];
}
if (opts.before_init) {
if (opts.before_init(opts) === false)
return null;
}
// This keeps track of the last element the mouse was over, so
// mouseleave, mouseenter, etc can be called.
var nonblock_last_elem;
// This is used to pass events through the notice if it is non-blocking.
var nonblock_pass = function(e, e_name){
pnotify.css("display", "none");
var element_below = document.elementFromPoint(e.clientX, e.clientY);
pnotify.css("display", "block");
var jelement_below = $(element_below);
var cursor_style = jelement_below.css("cursor");
pnotify.css("cursor", cursor_style != "auto" ? cursor_style : "default");
// If the element changed, call mouseenter, mouseleave, etc.
if (!nonblock_last_elem || nonblock_last_elem.get(0) != element_below) {
if (nonblock_last_elem) {
dom_event.call(nonblock_last_elem.get(0), "mouseleave", e.originalEvent);
dom_event.call(nonblock_last_elem.get(0), "mouseout", e.originalEvent);
}
dom_event.call(element_below, "mouseenter", e.originalEvent);
dom_event.call(element_below, "mouseover", e.originalEvent);
}
dom_event.call(element_below, e_name, e.originalEvent);
// Remember the latest element the mouse was over.
nonblock_last_elem = jelement_below;
};
// Get our styling object.
var styles = styling[opts.styling];
// Create our widget.
// Stop animation, reset the removal timer, and show the close
// button when the user mouses over.
var pnotify = $("<div />", {
"class": "ui-pnotify "+opts.addclass,
"css": {"display": "none"},
"mouseenter": function(e){
if (opts.nonblock) e.stopPropagation();
if (opts.mouse_reset && animating == "out") {
// If it's animating out, animate back in really quickly.
pnotify.stop(true);
animating = "in";
pnotify.css("height", "auto").animate({"width": opts.width, "opacity": opts.nonblock ? opts.nonblock_opacity : opts.opacity}, "fast");
}
if (opts.nonblock) {
// If it's non-blocking, animate to the other opacity.
pnotify.animate({"opacity": opts.nonblock_opacity}, "fast");
}
// Stop the close timer.
if (opts.hide && opts.mouse_reset) pnotify.pnotify_cancel_remove();
// Show the buttons.
if (opts.sticker && !opts.nonblock) pnotify.sticker.trigger("pnotify_icon").css("visibility", "visible");
if (opts.closer && !opts.nonblock) pnotify.closer.css("visibility", "visible");
},
"mouseleave": function(e){
if (opts.nonblock) e.stopPropagation();
nonblock_last_elem = null;
pnotify.css("cursor", "auto");
// Animate back to the normal opacity.
if (opts.nonblock && animating != "out")
pnotify.animate({"opacity": opts.opacity}, "fast");
// Start the close timer.
if (opts.hide && opts.mouse_reset) pnotify.pnotify_queue_remove();
// Hide the buttons.
if (opts.sticker_hover)
pnotify.sticker.css("visibility", "hidden");
if (opts.closer_hover)
pnotify.closer.css("visibility", "hidden");
$.pnotify_position_all();
},
"mouseover": function(e){
if (opts.nonblock) e.stopPropagation();
},
"mouseout": function(e){
if (opts.nonblock) e.stopPropagation();
},
"mousemove": function(e){
if (opts.nonblock) {
e.stopPropagation();
nonblock_pass(e, "onmousemove");
}
},
"mousedown": function(e){
if (opts.nonblock) {
e.stopPropagation();
e.preventDefault();
nonblock_pass(e, "onmousedown");
}
},
"mouseup": function(e){
if (opts.nonblock) {
e.stopPropagation();
e.preventDefault();
nonblock_pass(e, "onmouseup");
}
},
"click": function(e){
if (opts.nonblock) {
e.stopPropagation();
nonblock_pass(e, "onclick");
}
},
"dblclick": function(e){
if (opts.nonblock) {
e.stopPropagation();
nonblock_pass(e, "ondblclick");
}
}
});
pnotify.opts = opts;
// Create a container for the notice contents.
pnotify.container = $("<div />", {"class": styles.container+" ui-pnotify-container "+(opts.type == "error" ? styles.error : (opts.type == "info" ? styles.info : (opts.type == "success" ? styles.success : styles.notice)))})
.appendTo(pnotify);
if (opts.cornerclass != "")
pnotify.container.removeClass("ui-corner-all").addClass(opts.cornerclass);
// Create a drop shadow.
if (opts.shadow)
pnotify.container.addClass("ui-pnotify-shadow");
// The current version of Pines Notify.
pnotify.pnotify_version = "1.2.0";
// This function is for updating the notice.
pnotify.pnotify = function(options) {
// Update the notice.
var old_opts = opts;
if (typeof options == "string")
opts.text = options;
else
opts = $.extend({}, opts, options);
// Translate old pnotify_ style options.
for (var i in opts) {
if (typeof i == "string" && i.match(/^pnotify_/))
opts[i.replace(/^pnotify_/, "")] = opts[i];
}
pnotify.opts = opts;
// Update the corner class.
if (opts.cornerclass != old_opts.cornerclass)
pnotify.container.removeClass("ui-corner-all").addClass(opts.cornerclass);
// Update the shadow.
if (opts.shadow != old_opts.shadow) {
if (opts.shadow)
pnotify.container.addClass("ui-pnotify-shadow");
else
pnotify.container.removeClass("ui-pnotify-shadow");
}
// Update the additional classes.
if (opts.addclass === false)
pnotify.removeClass(old_opts.addclass);
else if (opts.addclass !== old_opts.addclass)
pnotify.removeClass(old_opts.addclass).addClass(opts.addclass);
// Update the title.
if (opts.title === false)
pnotify.title_container.slideUp("fast");
else if (opts.title !== old_opts.title) {
if (opts.title_escape)
pnotify.title_container.text(opts.title).slideDown(200);
else
pnotify.title_container.html(opts.title).slideDown(200);
}
// Update the text.
if (opts.text === false) {
pnotify.text_container.slideUp("fast");
} else if (opts.text !== old_opts.text) {
if (opts.text_escape)
pnotify.text_container.text(opts.text).slideDown(200);
else
pnotify.text_container.html(opts.insert_brs ? String(opts.text).replace(/\n/g, "<br />") : opts.text).slideDown(200);
}
// Update values for history menu access.
pnotify.pnotify_history = opts.history;
pnotify.pnotify_hide = opts.hide;
// Change the notice type.
if (opts.type != old_opts.type)
pnotify.container.removeClass(styles.error+" "+styles.notice+" "+styles.success+" "+styles.info).addClass(opts.type == "error" ? styles.error : (opts.type == "info" ? styles.info : (opts.type == "success" ? styles.success : styles.notice)));
if (opts.icon !== old_opts.icon || (opts.icon === true && opts.type != old_opts.type)) {
// Remove any old icon.
pnotify.container.find("div.ui-pnotify-icon").remove();
if (opts.icon !== false) {
// Build the new icon.
$("<div />", {"class": "ui-pnotify-icon"})
.append($("<span />", {"class": opts.icon === true ? (opts.type == "error" ? styles.error_icon : (opts.type == "info" ? styles.info_icon : (opts.type == "success" ? styles.success_icon : styles.notice_icon))) : opts.icon}))
.prependTo(pnotify.container);
}
}
// Update the width.
if (opts.width !== old_opts.width)
pnotify.animate({width: opts.width});
// Update the minimum height.
if (opts.min_height !== old_opts.min_height)
pnotify.container.animate({minHeight: opts.min_height});
// Update the opacity.
if (opts.opacity !== old_opts.opacity)
pnotify.fadeTo(opts.animate_speed, opts.opacity);
// Update the sticker and closer buttons.
if (!opts.closer || opts.nonblock)
pnotify.closer.css("display", "none");
else
pnotify.closer.css("display", "block");
if (!opts.sticker || opts.nonblock)
pnotify.sticker.css("display", "none");
else
pnotify.sticker.css("display", "block");
// Update the sticker icon.
pnotify.sticker.trigger("pnotify_icon");
// Update the hover status of the buttons.
if (opts.sticker_hover)
pnotify.sticker.css("visibility", "hidden");
else if (!opts.nonblock)
pnotify.sticker.css("visibility", "visible");
if (opts.closer_hover)
pnotify.closer.css("visibility", "hidden");
else if (!opts.nonblock)
pnotify.closer.css("visibility", "visible");
// Update the timed hiding.
if (!opts.hide)
pnotify.pnotify_cancel_remove();
else if (!old_opts.hide)
pnotify.pnotify_queue_remove();
pnotify.pnotify_queue_position();
return pnotify;
};
// Position the notice. dont_skip_hidden causes the notice to
// position even if it's not visible.
pnotify.pnotify_position = function(dont_skip_hidden){
// Get the notice's stack.
var s = pnotify.opts.stack;
if (!s) return;
if (!s.nextpos1)
s.nextpos1 = s.firstpos1;
if (!s.nextpos2)
s.nextpos2 = s.firstpos2;
if (!s.addpos2)
s.addpos2 = 0;
var hidden = pnotify.css("display") == "none";
// Skip this notice if it's not shown.
if (!hidden || dont_skip_hidden) {
var curpos1, curpos2;
// Store what will need to be animated.
var animate = {};
// Calculate the current pos1 value.
var csspos1;
switch (s.dir1) {
case "down":
csspos1 = "top";
break;
case "up":
csspos1 = "bottom";
break;
case "left":
csspos1 = "right";
break;
case "right":
csspos1 = "left";
break;
}
curpos1 = parseInt(pnotify.css(csspos1));
if (isNaN(curpos1))
curpos1 = 0;
// Remember the first pos1, so the first visible notice goes there.
if (typeof s.firstpos1 == "undefined" && !hidden) {
s.firstpos1 = curpos1;
s.nextpos1 = s.firstpos1;
}
// Calculate the current pos2 value.
var csspos2;
switch (s.dir2) {
case "down":
csspos2 = "top";
break;
case "up":
csspos2 = "bottom";
break;
case "left":
csspos2 = "right";
break;
case "right":
csspos2 = "left";
break;
}
curpos2 = parseInt(pnotify.css(csspos2));
if (isNaN(curpos2))
curpos2 = 0;
// Remember the first pos2, so the first visible notice goes there.
if (typeof s.firstpos2 == "undefined" && !hidden) {
s.firstpos2 = curpos2;
s.nextpos2 = s.firstpos2;
}
// Check that it's not beyond the viewport edge.
if ((s.dir1 == "down" && s.nextpos1 + pnotify.height() > jwindow.height()) ||
(s.dir1 == "up" && s.nextpos1 + pnotify.height() > jwindow.height()) ||
(s.dir1 == "left" && s.nextpos1 + pnotify.width() > jwindow.width()) ||
(s.dir1 == "right" && s.nextpos1 + pnotify.width() > jwindow.width()) ) {
// If it is, it needs to go back to the first pos1, and over on pos2.
s.nextpos1 = s.firstpos1;
s.nextpos2 += s.addpos2 + (typeof s.spacing2 == "undefined" ? 25 : s.spacing2);
s.addpos2 = 0;
}
// Animate if we're moving on dir2.
if (s.animation && s.nextpos2 < curpos2) {
switch (s.dir2) {
case "down":
animate.top = s.nextpos2+"px";
break;
case "up":
animate.bottom = s.nextpos2+"px";
break;
case "left":
animate.right = s.nextpos2+"px";
break;
case "right":
animate.left = s.nextpos2+"px";
break;
}
} else
pnotify.css(csspos2, s.nextpos2+"px");
// Keep track of the widest/tallest notice in the column/row, so we can push the next column/row.
switch (s.dir2) {
case "down":
case "up":
if (pnotify.outerHeight(true) > s.addpos2)
s.addpos2 = pnotify.height();
break;
case "left":
case "right":
if (pnotify.outerWidth(true) > s.addpos2)
s.addpos2 = pnotify.width();
break;
}
// Move the notice on dir1.
if (s.nextpos1) {
// Animate if we're moving toward the first pos.
if (s.animation && (curpos1 > s.nextpos1 || animate.top || animate.bottom || animate.right || animate.left)) {
switch (s.dir1) {
case "down":
animate.top = s.nextpos1+"px";
break;
case "up":
animate.bottom = s.nextpos1+"px";
break;
case "left":
animate.right = s.nextpos1+"px";
break;
case "right":
animate.left = s.nextpos1+"px";
break;
}
} else
pnotify.css(csspos1, s.nextpos1+"px");
}
// Run the animation.
if (animate.top || animate.bottom || animate.right || animate.left)
pnotify.animate(animate, {duration: 500, queue: false});
// Calculate the next dir1 position.
switch (s.dir1) {
case "down":
case "up":
s.nextpos1 += pnotify.height() + (typeof s.spacing1 == "undefined" ? 25 : s.spacing1);
break;
case "left":
case "right":
s.nextpos1 += pnotify.width() + (typeof s.spacing1 == "undefined" ? 25 : s.spacing1);
break;
}
}
};
// Queue the positiona all function so it doesn't run repeatedly and
// use up resources.
pnotify.pnotify_queue_position = function(milliseconds){
if (timer)
clearTimeout(timer);
if (!milliseconds)
milliseconds = 10;
timer = setTimeout($.pnotify_position_all, milliseconds);
};
// Display the notice.
pnotify.pnotify_display = function() {
// If the notice is not in the DOM, append it.
if (!pnotify.parent().length)
pnotify.appendTo(body);
// Run callback.
if (opts.before_open) {
if (opts.before_open(pnotify) === false)
return;
}
// Try to put it in the right position.
if (opts.stack.push != "top")
pnotify.pnotify_position(true);
// First show it, then set its opacity, then hide it.
if (opts.animation == "fade" || opts.animation.effect_in == "fade") {
// If it's fading in, it should start at 0.
pnotify.show().fadeTo(0, 0).hide();
} else {
// Or else it should be set to the opacity.
if (opts.opacity != 1)
pnotify.show().fadeTo(0, opts.opacity).hide();
}
pnotify.animate_in(function(){
if (opts.after_open)
opts.after_open(pnotify);
pnotify.pnotify_queue_position();
// Now set it to hide.
if (opts.hide)
pnotify.pnotify_queue_remove();
});
};
// Remove the notice.
pnotify.pnotify_remove = function() {
if (pnotify.timer) {
window.clearTimeout(pnotify.timer);
pnotify.timer = null;
}
// Run callback.
if (opts.before_close) {
if (opts.before_close(pnotify) === false)
return;
}
pnotify.animate_out(function(){
if (opts.after_close) {
if (opts.after_close(pnotify) === false)
return;
}
pnotify.pnotify_queue_position();
// If we're supposed to remove the notice from the DOM, do it.
if (opts.remove)
pnotify.detach();
});
};
// Animate the notice in.
pnotify.animate_in = function(callback){
// Declare that the notice is animating in. (Or has completed animating in.)
animating = "in";
var animation;
if (typeof opts.animation.effect_in != "undefined")
animation = opts.animation.effect_in;
else
animation = opts.animation;
if (animation == "none") {
pnotify.show();
callback();
} else if (animation == "show")
pnotify.show(opts.animate_speed, callback);
else if (animation == "fade")
pnotify.show().fadeTo(opts.animate_speed, opts.opacity, callback);
else if (animation == "slide")
pnotify.slideDown(opts.animate_speed, callback);
else if (typeof animation == "function")
animation("in", callback, pnotify);
else
pnotify.show(animation, (typeof opts.animation.options_in == "object" ? opts.animation.options_in : {}), opts.animate_speed, callback);
};
// Animate the notice out.
pnotify.animate_out = function(callback){
// Declare that the notice is animating out. (Or has completed animating out.)
animating = "out";
var animation;
if (typeof opts.animation.effect_out != "undefined")
animation = opts.animation.effect_out;
else
animation = opts.animation;
if (animation == "none") {
pnotify.hide();
callback();
} else if (animation == "show")
pnotify.hide(opts.animate_speed, callback);
else if (animation == "fade")
pnotify.fadeOut(opts.animate_speed, callback);
else if (animation == "slide")
pnotify.slideUp(opts.animate_speed, callback);
else if (typeof animation == "function")
animation("out", callback, pnotify);
else
pnotify.hide(animation, (typeof opts.animation.options_out == "object" ? opts.animation.options_out : {}), opts.animate_speed, callback);
};
// Cancel any pending removal timer.
pnotify.pnotify_cancel_remove = function() {
if (pnotify.timer)
window.clearTimeout(pnotify.timer);
};
// Queue a removal timer.
pnotify.pnotify_queue_remove = function() {
// Cancel any current removal timer.
pnotify.pnotify_cancel_remove();
pnotify.timer = window.setTimeout(function(){
pnotify.pnotify_remove();
}, (isNaN(opts.delay) ? 0 : opts.delay));
};
// Provide a button to close the notice.
pnotify.closer = $("<div />", {
"class": "ui-pnotify-closer",
"css": {"cursor": "pointer", "visibility": opts.closer_hover ? "hidden" : "visible"},
"click": function(){
pnotify.pnotify_remove();
pnotify.sticker.css("visibility", "hidden");
pnotify.closer.css("visibility", "hidden");
}
})
.append($("<span />", {"class": styles.closer}))
.appendTo(pnotify.container);
if (!opts.closer || opts.nonblock)
pnotify.closer.css("display", "none");
// Provide a button to stick the notice.
pnotify.sticker = $("<div />", {
"class": "ui-pnotify-sticker",
"css": {"cursor": "pointer", "visibility": opts.sticker_hover ? "hidden" : "visible"},
"click": function(){
opts.hide = !opts.hide;
if (opts.hide)
pnotify.pnotify_queue_remove();
else
pnotify.pnotify_cancel_remove();
$(this).trigger("pnotify_icon");
}
})
.bind("pnotify_icon", function(){
$(this).children().removeClass(styles.pin_up+" "+styles.pin_down).addClass(opts.hide ? styles.pin_up : styles.pin_down);
})
.append($("<span />", {"class": styles.pin_up}))
.appendTo(pnotify.container);
if (!opts.sticker || opts.nonblock)
pnotify.sticker.css("display", "none");
// Add the appropriate icon.
if (opts.icon !== false) {
$("<div />", {"class": "ui-pnotify-icon"})
.append($("<span />", {"class": opts.icon === true ? (opts.type == "error" ? styles.error_icon : (opts.type == "info" ? styles.info_icon : (opts.type == "success" ? styles.success_icon : styles.notice_icon))) : opts.icon}))
.prependTo(pnotify.container);
}
// Add a title.
pnotify.title_container = $("<h4 />", {
"class": "ui-pnotify-title"
})
.appendTo(pnotify.container);
if (opts.title === false)
pnotify.title_container.hide();
else if (opts.title_escape)
pnotify.title_container.text(opts.title);
else
pnotify.title_container.html(opts.title);
// Add text.
pnotify.text_container = $("<div />", {
"class": "ui-pnotify-text"
})
.appendTo(pnotify.container);
if (opts.text === false)
pnotify.text_container.hide();
else if (opts.text_escape)
pnotify.text_container.text(opts.text);
else
pnotify.text_container.html(opts.insert_brs ? String(opts.text).replace(/\n/g, "<br />") : opts.text);
// Set width and min height.
if (typeof opts.width == "string")
pnotify.css("width", opts.width);
if (typeof opts.min_height == "string")
pnotify.container.css("min-height", opts.min_height);
// The history variable controls whether the notice gets redisplayed
// by the history pull down.
pnotify.pnotify_history = opts.history;
// The hide variable controls whether the history pull down should
// queue a removal timer.
pnotify.pnotify_hide = opts.hide;
// Add the notice to the notice array.
var notices_data = jwindow.data("pnotify");
if (notices_data == null || typeof notices_data != "object")
notices_data = [];
if (opts.stack.push == "top")
notices_data = $.merge([pnotify], notices_data);
else
notices_data = $.merge(notices_data, [pnotify]);
jwindow.data("pnotify", notices_data);
// Now position all the notices if they are to push to the top.
if (opts.stack.push == "top")
pnotify.pnotify_queue_position(1);
// Run callback.
if (opts.after_init)
opts.after_init(pnotify);
if (opts.history) {
// If there isn't a history pull down, create one.
var history_menu = jwindow.data("pnotify_history");
if (typeof history_menu == "undefined") {
history_menu = $("<div />", {
"class": "ui-pnotify-history-container "+styles.hi_menu,
"mouseleave": function(){
history_menu.animate({top: "-"+history_handle_top+"px"}, {duration: 100, queue: false});
}
})
.append($("<div />", {"class": "ui-pnotify-history-header", "text": "Redisplay"}))
.append($("<button />", {
"class": "ui-pnotify-history-all "+styles.hi_btn,
"text": "All",
"mouseenter": function(){
$(this).addClass(styles.hi_btnhov);
},
"mouseleave": function(){
$(this).removeClass(styles.hi_btnhov);
},
"click": function(){
// Display all notices. (Disregarding non-history notices.)
$.each(notices_data, function(){
if (this.pnotify_history) {
if (this.is(":visible")) {
if (this.pnotify_hide)
this.pnotify_queue_remove();
} else if (this.pnotify_display)
this.pnotify_display();
}
});
return false;
}
}))
.append($("<button />", {
"class": "ui-pnotify-history-last "+styles.hi_btn,
"text": "Last",
"mouseenter": function(){
$(this).addClass(styles.hi_btnhov);
},
"mouseleave": function(){
$(this).removeClass(styles.hi_btnhov);
},
"click": function(){
// Look up the last history notice, and display it.
var i = -1;
var notice;
do {
if (i == -1)
notice = notices_data.slice(i);
else
notice = notices_data.slice(i, i+1);
if (!notice[0])
break;
i--;
} while (!notice[0].pnotify_history || notice[0].is(":visible"));
if (!notice[0])
return false;
if (notice[0].pnotify_display)
notice[0].pnotify_display();
return false;
}
}))
.appendTo(body);
// Make a handle so the user can pull down the history tab.
var handle = $("<span />", {
"class": "ui-pnotify-history-pulldown "+styles.hi_hnd,
"mouseenter": function(){
history_menu.animate({top: "0"}, {duration: 100, queue: false});
}
})
.appendTo(history_menu);
// Get the top of the handle.
history_handle_top = handle.offset().top + 2;
// Hide the history pull down up to the top of the handle.
history_menu.css({top: "-"+history_handle_top+"px"});
// Save the history pull down.
jwindow.data("pnotify_history", history_menu);
}
}
// Mark the stack so it won't animate the new notice.
opts.stack.animation = false;
// Display the notice.
pnotify.pnotify_display();
return pnotify;
}
});
// Some useful regexes.
var re_on = /^on/,
re_mouse_events = /^(dbl)?click$|^mouse(move|down|up|over|out|enter|leave)$|^contextmenu$/,
re_ui_events = /^(focus|blur|select|change|reset)$|^key(press|down|up)$/,
re_html_events = /^(scroll|resize|(un)?load|abort|error)$/;
// Fire a DOM event.
var dom_event = function(e, orig_e){
var event_object;
e = e.toLowerCase();
if (document.createEvent && this.dispatchEvent) {
// FireFox, Opera, Safari, Chrome
e = e.replace(re_on, '');
if (e.match(re_mouse_events)) {
// This allows the click event to fire on the notice. There is
// probably a much better way to do it.
$(this).offset();
event_object = document.createEvent("MouseEvents");
event_object.initMouseEvent(
e, orig_e.bubbles, orig_e.cancelable, orig_e.view, orig_e.detail,
orig_e.screenX, orig_e.screenY, orig_e.clientX, orig_e.clientY,
orig_e.ctrlKey, orig_e.altKey, orig_e.shiftKey, orig_e.metaKey, orig_e.button, orig_e.relatedTarget
);
} else if (e.match(re_ui_events)) {
event_object = document.createEvent("UIEvents");
event_object.initUIEvent(e, orig_e.bubbles, orig_e.cancelable, orig_e.view, orig_e.detail);
} else if (e.match(re_html_events)) {
event_object = document.createEvent("HTMLEvents");
event_object.initEvent(e, orig_e.bubbles, orig_e.cancelable);
}
if (!event_object) return;
this.dispatchEvent(event_object);
} else {
// Internet Explorer
if (!e.match(re_on)) e = "on"+e;
event_object = document.createEventObject(orig_e);
this.fireEvent(e, event_object);
}
};
$.pnotify.defaults = {
// The notice's title.
title: false,
// Whether to escape the content of the title. (Not allow HTML.)
title_escape: false,
// The notice's text.
text: false,
// Whether to escape the content of the text. (Not allow HTML.)
text_escape: false,
// What styling classes to use. (Can be either jqueryui or bootstrap.)
styling: "bootstrap",
// Additional classes to be added to the notice. (For custom styling.)
addclass: "",
// Class to be added to the notice for corner styling.
cornerclass: "",
// Create a non-blocking notice. It lets the user click elements underneath it.
nonblock: false,
// The opacity of the notice (if it's non-blocking) when the mouse is over it.
nonblock_opacity: .2,
// Display a pull down menu to redisplay previous notices, and place the notice in the history.
history: true,
// Width of the notice.
width: "300px",
// Minimum height of the notice. It will expand to fit content.
min_height: "16px",
// Type of the notice. "notice", "info", "success", or "error".
type: "notice",
// Set icon to true to use the default icon for the selected style/type, false for no icon, or a string for your own icon class.
icon: true,
// The animation to use when displaying and hiding the notice. "none", "show", "fade", and "slide" are built in to jQuery. Others require jQuery UI. Use an object with effect_in and effect_out to use different effects.
animation: "fade",
// Speed at which the notice animates in and out. "slow", "def" or "normal", "fast" or number of milliseconds.
animate_speed: "slow",
// Opacity of the notice.
opacity: 1,
// Display a drop shadow.
shadow: true,
// Provide a button for the user to manually close the notice.
closer: true,
// Only show the closer button on hover.
closer_hover: true,
// Provide a button for the user to manually stick the notice.
sticker: true,
// Only show the sticker button on hover.
sticker_hover: true,
// After a delay, remove the notice.
hide: true,
// Delay in milliseconds before the notice is removed.
delay: 8000,
// Reset the hide timer if the mouse moves over the notice.
mouse_reset: true,
// Remove the notice's elements from the DOM after it is removed.
remove: true,
// Change new lines to br tags.
insert_brs: true,
// The stack on which the notices will be placed. Also controls the direction the notices stack.
stack: {"dir1": "down", "dir2": "left", "push": "bottom", "spacing1": 25, "spacing2": 25}
};
})(jQuery);

View File

@ -1,40 +0,0 @@
/*
* jQuery Pines Notify (pnotify) Plugin 1.2.0
*
* http://pinesframework.org/pnotify/
* Copyright (c) 2009-2012 Hunter Perrin
*
* Triple license under the GPL, LGPL, and MPL:
* http://www.gnu.org/licenses/gpl.html
* http://www.gnu.org/licenses/lgpl.html
* http://www.mozilla.org/MPL/MPL-1.1.html
*/
(function(d){var q,j,r,i=d(window),u={jqueryui:{container:"ui-widget ui-widget-content ui-corner-all",notice:"ui-state-highlight",notice_icon:"ui-icon ui-icon-info",info:"",info_icon:"ui-icon ui-icon-info",success:"ui-state-default",success_icon:"ui-icon ui-icon-circle-check",error:"ui-state-error",error_icon:"ui-icon ui-icon-alert",closer:"ui-icon ui-icon-close",pin_up:"ui-icon ui-icon-pin-w",pin_down:"ui-icon ui-icon-pin-s",hi_menu:"ui-state-default ui-corner-bottom",hi_btn:"ui-state-default ui-corner-all",
hi_btnhov:"ui-state-hover",hi_hnd:"ui-icon ui-icon-grip-dotted-horizontal"},bootstrap:{container:"alert",notice:"",notice_icon:"icon-exclamation-sign",info:"alert-info",info_icon:"icon-info-sign",success:"alert-success",success_icon:"icon-ok-sign",error:"alert-error",error_icon:"icon-warning-sign",closer:"icon-remove",pin_up:"icon-pause",pin_down:"icon-play",hi_menu:"well",hi_btn:"btn",hi_btnhov:"",hi_hnd:"icon-chevron-down"}},s=function(){r=d("body");i=d(window);i.bind("resize",function(){j&&clearTimeout(j);
j=setTimeout(d.pnotify_position_all,10)})};document.body?s():d(s);d.extend({pnotify_remove_all:function(){var e=i.data("pnotify");e&&e.length&&d.each(e,function(){this.pnotify_remove&&this.pnotify_remove()})},pnotify_position_all:function(){j&&clearTimeout(j);j=null;var e=i.data("pnotify");e&&e.length&&(d.each(e,function(){var d=this.opts.stack;if(d)d.nextpos1=d.firstpos1,d.nextpos2=d.firstpos2,d.addpos2=0,d.animation=true}),d.each(e,function(){this.pnotify_position()}))},pnotify:function(e){var g,
a;typeof e!="object"?(a=d.extend({},d.pnotify.defaults),a.text=e):a=d.extend({},d.pnotify.defaults,e);for(var p in a)typeof p=="string"&&p.match(/^pnotify_/)&&(a[p.replace(/^pnotify_/,"")]=a[p]);if(a.before_init&&a.before_init(a)===false)return null;var k,o=function(a,c){b.css("display","none");var f=document.elementFromPoint(a.clientX,a.clientY);b.css("display","block");var e=d(f),g=e.css("cursor");b.css("cursor",g!="auto"?g:"default");if(!k||k.get(0)!=f)k&&(n.call(k.get(0),"mouseleave",a.originalEvent),
n.call(k.get(0),"mouseout",a.originalEvent)),n.call(f,"mouseenter",a.originalEvent),n.call(f,"mouseover",a.originalEvent);n.call(f,c,a.originalEvent);k=e},f=u[a.styling],b=d("<div />",{"class":"ui-pnotify "+a.addclass,css:{display:"none"},mouseenter:function(l){a.nonblock&&l.stopPropagation();a.mouse_reset&&g=="out"&&(b.stop(true),g="in",b.css("height","auto").animate({width:a.width,opacity:a.nonblock?a.nonblock_opacity:a.opacity},"fast"));a.nonblock&&b.animate({opacity:a.nonblock_opacity},"fast");
a.hide&&a.mouse_reset&&b.pnotify_cancel_remove();a.sticker&&!a.nonblock&&b.sticker.trigger("pnotify_icon").css("visibility","visible");a.closer&&!a.nonblock&&b.closer.css("visibility","visible")},mouseleave:function(l){a.nonblock&&l.stopPropagation();k=null;b.css("cursor","auto");a.nonblock&&g!="out"&&b.animate({opacity:a.opacity},"fast");a.hide&&a.mouse_reset&&b.pnotify_queue_remove();a.sticker_hover&&b.sticker.css("visibility","hidden");a.closer_hover&&b.closer.css("visibility","hidden");d.pnotify_position_all()},
mouseover:function(b){a.nonblock&&b.stopPropagation()},mouseout:function(b){a.nonblock&&b.stopPropagation()},mousemove:function(b){a.nonblock&&(b.stopPropagation(),o(b,"onmousemove"))},mousedown:function(b){a.nonblock&&(b.stopPropagation(),b.preventDefault(),o(b,"onmousedown"))},mouseup:function(b){a.nonblock&&(b.stopPropagation(),b.preventDefault(),o(b,"onmouseup"))},click:function(b){a.nonblock&&(b.stopPropagation(),o(b,"onclick"))},dblclick:function(b){a.nonblock&&(b.stopPropagation(),o(b,"ondblclick"))}});
b.opts=a;b.container=d("<div />",{"class":f.container+" ui-pnotify-container "+(a.type=="error"?f.error:a.type=="info"?f.info:a.type=="success"?f.success:f.notice)}).appendTo(b);a.cornerclass!=""&&b.container.removeClass("ui-corner-all").addClass(a.cornerclass);a.shadow&&b.container.addClass("ui-pnotify-shadow");b.pnotify_version="1.2.0";b.pnotify=function(l){var c=a;typeof l=="string"?a.text=l:a=d.extend({},a,l);for(var e in a)typeof e=="string"&&e.match(/^pnotify_/)&&(a[e.replace(/^pnotify_/,"")]=
a[e]);b.opts=a;a.cornerclass!=c.cornerclass&&b.container.removeClass("ui-corner-all").addClass(a.cornerclass);a.shadow!=c.shadow&&(a.shadow?b.container.addClass("ui-pnotify-shadow"):b.container.removeClass("ui-pnotify-shadow"));a.addclass===false?b.removeClass(c.addclass):a.addclass!==c.addclass&&b.removeClass(c.addclass).addClass(a.addclass);a.title===false?b.title_container.slideUp("fast"):a.title!==c.title&&(a.title_escape?b.title_container.text(a.title).slideDown(200):b.title_container.html(a.title).slideDown(200));
a.text===false?b.text_container.slideUp("fast"):a.text!==c.text&&(a.text_escape?b.text_container.text(a.text).slideDown(200):b.text_container.html(a.insert_brs?String(a.text).replace(/\n/g,"<br />"):a.text).slideDown(200));b.pnotify_history=a.history;b.pnotify_hide=a.hide;a.type!=c.type&&b.container.removeClass(f.error+" "+f.notice+" "+f.success+" "+f.info).addClass(a.type=="error"?f.error:a.type=="info"?f.info:a.type=="success"?f.success:f.notice);if(a.icon!==c.icon||a.icon===true&&a.type!=c.type)b.container.find("div.ui-pnotify-icon").remove(),
a.icon!==false&&d("<div />",{"class":"ui-pnotify-icon"}).append(d("<span />",{"class":a.icon===true?a.type=="error"?f.error_icon:a.type=="info"?f.info_icon:a.type=="success"?f.success_icon:f.notice_icon:a.icon})).prependTo(b.container);a.width!==c.width&&b.animate({width:a.width});a.min_height!==c.min_height&&b.container.animate({minHeight:a.min_height});a.opacity!==c.opacity&&b.fadeTo(a.animate_speed,a.opacity);!a.closer||a.nonblock?b.closer.css("display","none"):b.closer.css("display","block");
!a.sticker||a.nonblock?b.sticker.css("display","none"):b.sticker.css("display","block");b.sticker.trigger("pnotify_icon");a.sticker_hover?b.sticker.css("visibility","hidden"):a.nonblock||b.sticker.css("visibility","visible");a.closer_hover?b.closer.css("visibility","hidden"):a.nonblock||b.closer.css("visibility","visible");a.hide?c.hide||b.pnotify_queue_remove():b.pnotify_cancel_remove();b.pnotify_queue_position();return b};b.pnotify_position=function(a){var c=b.opts.stack;if(c){if(!c.nextpos1)c.nextpos1=
c.firstpos1;if(!c.nextpos2)c.nextpos2=c.firstpos2;if(!c.addpos2)c.addpos2=0;var d=b.css("display")=="none";if(!d||a){var f,e={},g;switch(c.dir1){case "down":g="top";break;case "up":g="bottom";break;case "left":g="right";break;case "right":g="left"}a=parseInt(b.css(g));isNaN(a)&&(a=0);if(typeof c.firstpos1=="undefined"&&!d)c.firstpos1=a,c.nextpos1=c.firstpos1;var h;switch(c.dir2){case "down":h="top";break;case "up":h="bottom";break;case "left":h="right";break;case "right":h="left"}f=parseInt(b.css(h));
isNaN(f)&&(f=0);if(typeof c.firstpos2=="undefined"&&!d)c.firstpos2=f,c.nextpos2=c.firstpos2;if(c.dir1=="down"&&c.nextpos1+b.height()>i.height()||c.dir1=="up"&&c.nextpos1+b.height()>i.height()||c.dir1=="left"&&c.nextpos1+b.width()>i.width()||c.dir1=="right"&&c.nextpos1+b.width()>i.width())c.nextpos1=c.firstpos1,c.nextpos2+=c.addpos2+(typeof c.spacing2=="undefined"?25:c.spacing2),c.addpos2=0;if(c.animation&&c.nextpos2<f)switch(c.dir2){case "down":e.top=c.nextpos2+"px";break;case "up":e.bottom=c.nextpos2+
"px";break;case "left":e.right=c.nextpos2+"px";break;case "right":e.left=c.nextpos2+"px"}else b.css(h,c.nextpos2+"px");switch(c.dir2){case "down":case "up":if(b.outerHeight(true)>c.addpos2)c.addpos2=b.height();break;case "left":case "right":if(b.outerWidth(true)>c.addpos2)c.addpos2=b.width()}if(c.nextpos1)if(c.animation&&(a>c.nextpos1||e.top||e.bottom||e.right||e.left))switch(c.dir1){case "down":e.top=c.nextpos1+"px";break;case "up":e.bottom=c.nextpos1+"px";break;case "left":e.right=c.nextpos1+"px";
break;case "right":e.left=c.nextpos1+"px"}else b.css(g,c.nextpos1+"px");(e.top||e.bottom||e.right||e.left)&&b.animate(e,{duration:500,queue:false});switch(c.dir1){case "down":case "up":c.nextpos1+=b.height()+(typeof c.spacing1=="undefined"?25:c.spacing1);break;case "left":case "right":c.nextpos1+=b.width()+(typeof c.spacing1=="undefined"?25:c.spacing1)}}}};b.pnotify_queue_position=function(a){j&&clearTimeout(j);a||(a=10);j=setTimeout(d.pnotify_position_all,a)};b.pnotify_display=function(){b.parent().length||
b.appendTo(r);a.before_open&&a.before_open(b)===false||(a.stack.push!="top"&&b.pnotify_position(true),a.animation=="fade"||a.animation.effect_in=="fade"?b.show().fadeTo(0,0).hide():a.opacity!=1&&b.show().fadeTo(0,a.opacity).hide(),b.animate_in(function(){a.after_open&&a.after_open(b);b.pnotify_queue_position();a.hide&&b.pnotify_queue_remove()}))};b.pnotify_remove=function(){if(b.timer)window.clearTimeout(b.timer),b.timer=null;a.before_close&&a.before_close(b)===false||b.animate_out(function(){a.after_close&&
a.after_close(b)===false||(b.pnotify_queue_position(),a.remove&&b.detach())})};b.animate_in=function(d){g="in";var c;c=typeof a.animation.effect_in!="undefined"?a.animation.effect_in:a.animation;c=="none"?(b.show(),d()):c=="show"?b.show(a.animate_speed,d):c=="fade"?b.show().fadeTo(a.animate_speed,a.opacity,d):c=="slide"?b.slideDown(a.animate_speed,d):typeof c=="function"?c("in",d,b):b.show(c,typeof a.animation.options_in=="object"?a.animation.options_in:{},a.animate_speed,d)};b.animate_out=function(d){g=
"out";var c;c=typeof a.animation.effect_out!="undefined"?a.animation.effect_out:a.animation;c=="none"?(b.hide(),d()):c=="show"?b.hide(a.animate_speed,d):c=="fade"?b.fadeOut(a.animate_speed,d):c=="slide"?b.slideUp(a.animate_speed,d):typeof c=="function"?c("out",d,b):b.hide(c,typeof a.animation.options_out=="object"?a.animation.options_out:{},a.animate_speed,d)};b.pnotify_cancel_remove=function(){b.timer&&window.clearTimeout(b.timer)};b.pnotify_queue_remove=function(){b.pnotify_cancel_remove();b.timer=
window.setTimeout(function(){b.pnotify_remove()},isNaN(a.delay)?0:a.delay)};b.closer=d("<div />",{"class":"ui-pnotify-closer",css:{cursor:"pointer",visibility:a.closer_hover?"hidden":"visible"},click:function(){b.pnotify_remove();b.sticker.css("visibility","hidden");b.closer.css("visibility","hidden")}}).append(d("<span />",{"class":f.closer})).appendTo(b.container);(!a.closer||a.nonblock)&&b.closer.css("display","none");b.sticker=d("<div />",{"class":"ui-pnotify-sticker",css:{cursor:"pointer",visibility:a.sticker_hover?
"hidden":"visible"},click:function(){a.hide=!a.hide;a.hide?b.pnotify_queue_remove():b.pnotify_cancel_remove();d(this).trigger("pnotify_icon")}}).bind("pnotify_icon",function(){d(this).children().removeClass(f.pin_up+" "+f.pin_down).addClass(a.hide?f.pin_up:f.pin_down)}).append(d("<span />",{"class":f.pin_up})).appendTo(b.container);(!a.sticker||a.nonblock)&&b.sticker.css("display","none");a.icon!==false&&d("<div />",{"class":"ui-pnotify-icon"}).append(d("<span />",{"class":a.icon===true?a.type=="error"?
f.error_icon:a.type=="info"?f.info_icon:a.type=="success"?f.success_icon:f.notice_icon:a.icon})).prependTo(b.container);b.title_container=d("<h4 />",{"class":"ui-pnotify-title"}).appendTo(b.container);a.title===false?b.title_container.hide():a.title_escape?b.title_container.text(a.title):b.title_container.html(a.title);b.text_container=d("<div />",{"class":"ui-pnotify-text"}).appendTo(b.container);a.text===false?b.text_container.hide():a.text_escape?b.text_container.text(a.text):b.text_container.html(a.insert_brs?
String(a.text).replace(/\n/g,"<br />"):a.text);typeof a.width=="string"&&b.css("width",a.width);typeof a.min_height=="string"&&b.container.css("min-height",a.min_height);b.pnotify_history=a.history;b.pnotify_hide=a.hide;var h=i.data("pnotify");if(h==null||typeof h!="object")h=[];h=a.stack.push=="top"?d.merge([b],h):d.merge(h,[b]);i.data("pnotify",h);a.stack.push=="top"&&b.pnotify_queue_position(1);a.after_init&&a.after_init(b);if(a.history){var m=i.data("pnotify_history");typeof m=="undefined"&&(m=
d("<div />",{"class":"ui-pnotify-history-container "+f.hi_menu,mouseleave:function(){m.animate({top:"-"+q+"px"},{duration:100,queue:false})}}).append(d("<div />",{"class":"ui-pnotify-history-header",text:"Redisplay"})).append(d("<button />",{"class":"ui-pnotify-history-all "+f.hi_btn,text:"All",mouseenter:function(){d(this).addClass(f.hi_btnhov)},mouseleave:function(){d(this).removeClass(f.hi_btnhov)},click:function(){d.each(h,function(){this.pnotify_history&&(this.is(":visible")?this.pnotify_hide&&
this.pnotify_queue_remove():this.pnotify_display&&this.pnotify_display())});return false}})).append(d("<button />",{"class":"ui-pnotify-history-last "+f.hi_btn,text:"Last",mouseenter:function(){d(this).addClass(f.hi_btnhov)},mouseleave:function(){d(this).removeClass(f.hi_btnhov)},click:function(){var a=-1,b;do{b=a==-1?h.slice(a):h.slice(a,a+1);if(!b[0])break;a--}while(!b[0].pnotify_history||b[0].is(":visible"));if(!b[0])return false;b[0].pnotify_display&&b[0].pnotify_display();return false}})).appendTo(r),
q=d("<span />",{"class":"ui-pnotify-history-pulldown "+f.hi_hnd,mouseenter:function(){m.animate({top:"0"},{duration:100,queue:false})}}).appendTo(m).offset().top+2,m.css({top:"-"+q+"px"}),i.data("pnotify_history",m))}a.stack.animation=false;b.pnotify_display();return b}});var t=/^on/,v=/^(dbl)?click$|^mouse(move|down|up|over|out|enter|leave)$|^contextmenu$/,w=/^(focus|blur|select|change|reset)$|^key(press|down|up)$/,x=/^(scroll|resize|(un)?load|abort|error)$/,n=function(e,g){var a,e=e.toLowerCase();
document.createEvent&&this.dispatchEvent?(e=e.replace(t,""),e.match(v)?(d(this).offset(),a=document.createEvent("MouseEvents"),a.initMouseEvent(e,g.bubbles,g.cancelable,g.view,g.detail,g.screenX,g.screenY,g.clientX,g.clientY,g.ctrlKey,g.altKey,g.shiftKey,g.metaKey,g.button,g.relatedTarget)):e.match(w)?(a=document.createEvent("UIEvents"),a.initUIEvent(e,g.bubbles,g.cancelable,g.view,g.detail)):e.match(x)&&(a=document.createEvent("HTMLEvents"),a.initEvent(e,g.bubbles,g.cancelable)),a&&this.dispatchEvent(a)):
(e.match(t)||(e="on"+e),a=document.createEventObject(g),this.fireEvent(e,a))};d.pnotify.defaults={title:false,title_escape:false,text:false,text_escape:false,styling:"bootstrap",addclass:"",cornerclass:"",nonblock:false,nonblock_opacity:0.2,history:true,width:"300px",min_height:"16px",type:"notice",icon:true,animation:"fade",animate_speed:"slow",opacity:1,shadow:true,closer:true,closer_hover:true,sticker:true,sticker_hover:true,hide:true,delay:8E3,mouse_reset:true,remove:true,insert_brs:true,stack:{dir1:"down",
dir2:"left",push:"bottom",spacing1:25,spacing2:25}}})(jQuery);

View File

@ -1,166 +0,0 @@
{#
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends "layout.html" %}
{% block body %}
<div ng-controller="EntryController">
<!-- Chart row -->
<div class="row">
<!-- Sold evolution chart placeholder -->
<div class="col-md-7">
<svg id="entries-chart-placeholder" style="stroke-width: 1px"/>
</div>
<!-- Expense category piechart -->
<div class="col-md-3">
<svg id="expense-categories-chart-placeholder" style='height:300px'/>
</div>
<!-- Balance -->
<div class="col-md-2">
<div class="row">
<table class="table">
<tr><td>Dépenses&nbsp;:</td><td>[[accountStatus.expenses]]</td></tr>
<tr><td>Recettes&nbsp;:</td><td>[[accountStatus.revenues]]</td></tr>
<tr><td>Balance&nbsp;:</td><td><span ng-class="entryValueClass(accountStatus.balance)">[[accountStatus.balance]]</span></td></tr>
</table>
</div>
</div>
</div>
<!-- Row with entry table -->
<div class="row">
<table class="table table-condensed table-hover">
<!-- Head of the table containing column headers and size -->
<thead>
<tr>
<th style="width: 100px">Date d'op.</th>
<th>Libell&eacute; de l'op&eacute;ration</th>
<th style="width: 50px">Montant</th>
<th style="width: 50px">Solde</th>
<th style="width: 100px">Cat&eacute;gorie</th>
<th style="width: 80px">Actions</th>
</tr>
</thead>
<!-- Body of the table containing the entries -->
<tbody>
<tr id="entry_[[entry.id]]" class="form-inline" ng-class="entryRowClass(entry)" ng-repeat="entry in entries">
<td>
<small>
<input ng-show="isEditing(entry)" type="text" class="form-control input-sm" ng-model="entry.operation_date" data-date-format="yyyy-mm-dd" bs-datepicker/>
<span ng-show="isDisplaying(entry)">[[entry.operation_date]]</span>
</small>
</td>
<td>
<small>
<input ng-show="isEditing(entry)" type="text" class="form-control input-sm" ng-model="entry.label"/>
<span ng-show="isDisplaying(entry)">[[entry.label]]</span>
</small>
</td>
<td>
<small>
<input ng-show="isEditing(entry)" type="text" class="form-control input-sm" ng-model="entry.value"/>
<span ng-show="isDisplaying(entry)">[[entry.value]]</span>
</small>
</td>
<td ng-class="entryValueClass(entry.sold)">
<small>
[[entry.sold]]
</small>
</td>
<td>
<small>
<!--<input ng-show="isEditing(entry)" type="text" class="form-control input-sm" ng-model="entry.category" bs-typeahead="categories"/>-->
<input ng-show="isEditing(entry)" type="text" class="form-control input-sm" ng-model="entry.category"/>
<span ng-show="isDisplaying(entry)">[[entry.category]]</span>
</small>
</td>
<td>
<div class="btn-group" ng-show="isEditing(entry)">
<button type="button" class="btn btn-xs btn-success" ng-click="saveEntry(entry)" title="Save">
<span ng-class="iconSaveClass(entry)"></span>
</button>
<button type="button" class="btn btn-xs btn-default" ng-click="cancelEditEntry(entry)" title="Cancel">
<span ng-class="iconCancelClass(entry)"></span>
</button>
<button type="button" class="btn btn-xs btn-default" ng-click="pointEntry(entry)" ng-class="pointedEntryClass(entry)" title="point">
<span class="fa fa-check"></span>
</button>
</div>
<div class="btn-group" ng-show="isDisplaying(entry) && isSaved(entry)">
<button class="btn btn-xs btn-default" ng-click="editEntry(entry)" title="edit">
<span class="fa fa-pencil-square-o"></span>
</button>
<button class="btn btn-xs btn-default" ng-click="removeEntry(entry)" title="remove">
<span class="fa fa-trash-o"></span>
</button>
<button class="btn btn-xs btn-default" ng-click="pointEntry(entry)" ng-class="pointedEntryClass(entry)" title="point">
<span class="fa fa-check"></span>
</button>
</div>
<div class="btn-group" ng-show="isDisplaying(entry) && !isSaved(entry)">
<button class="btn btn-xs btn-success" ng-click="saveEntry(entry)" title="Save">
<span ng-class="iconSaveClass(entry)"></span>
</button>
<button class="btn btn-xs btn-default" ng-click="editEntry(entry)" title="edit">
<span class="fa fa-pencil-square-o"></span>
</button>
<button class="btn btn-xs btn-default" ng-click="pointEntry(entry)" ng-class="pointedEntryClass(entry)" title="point">
<span class="fa fa-check"></span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
{% include "remove_entry.html" %}
</div>
{% endblock %}
{% block footer %}
<!-- Navbar with the months of the selected account -->
<div class="navbar navbar-default navbar-fixed-bottom" role="navigation" ng-controller="MonthController">
<ul class="nav navbar-nav">
<li ng-repeat="month in months" ng-class="monthClass(month)"><a href="#" ng-click="selectMonth(month)">[[month.year]]-[[month.month]]</a></li>
</ul>
</div>
{% endblock %}
<!-- Custom Javascript library for entries -->
{% block js %}
<script type="text/javascript" src="{{ url_for('frontend.static', filename='js/months.js') }}"></script>
<script type="text/javascript" src="{{ url_for('frontend.static', filename='js/accounts.js') }}"></script>
<script type="text/javascript" src="{{ url_for('frontend.static', filename='js/entries.js') }}"></script>
{% endblock %}

View File

@ -1,146 +0,0 @@
{#
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
#}
<!DOCTYPE html>
<html lang="fr" ng-app="$strap">
<head>
<!-- Title -->
<title>Entries</title>
<!-- Awesome fonts -->
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css" rel="stylesheet">
<!-- Bootstrap datepicker plugin CSS -->
<link href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.1.3/css/bootstrap-datepicker.min.css" rel="stylesheet">
<!-- NVD3 CSS -->
<link href="//cdnjs.cloudflare.com/ajax/libs/nvd3/1.1.13-beta/nv.d3.css" rel="stylesheet">
<!-- Pines Notify JQuery plugin -->
<link href="{{ url_for('frontend.static', filename='third-party/pines-notify/jquery.pnotify.default.css') }}" rel="stylesheet">
<!-- main css -->
<link href="{{ url_for('frontend.static', filename='css/main.css') }}" rel="stylesheet">
</head>
<body style="padding-bottom: 50px; padding-top: 70px">
<div class="navbar navbar-fixed-top navbar-inverse" role="navigation" ng-controller="AccountController">
<div class="navbar-header">
<a class="navbar-brand" href="/">&nbsp;Accountant</a>
</div>
<!-- Navbar with accounts and menu -->
<ul class="nav navbar-nav">
<li class="{% if request.path == '/index.html' %}active{% endif %}"><a href="index.html">Opérations</a></li>
<li class="{% if request.path == '/scheduler.html' %}active{% endif %}"><a href="scheduler.html">Planification</a></li>
<li class="divider-vertical"></li>
<!-- Account list -->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">[[account.name]]&nbsp;(<span ng-class="valueClass(account, account.current)">[[account.current]]</span>&nbsp;/&nbsp;<span ng-class="valueClass(account, account.pointed)">[[account.pointed]]</span>)<b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-submenu" ng-class="accountClass(account)" ng-repeat="account in accounts">
<a ng-click="selectAccount(account)" href="#">[[account.name]](<span ng-class="valueClass(account, account.current)">[[account.current]]</span>&nbsp;/&nbsp;<span ng-class="valueClass(account, account.pointed)">[[account.pointed]]</span>)
<ul class="dropdown-menu">
<li><a href="#" ng-click="editAccount(account)" bs-modal="'{{ url_for('frontend.static', filename='templates/account_edit.html') }}'">Modifier</a></li>
<li><a href="#" bs-modal="'{{ url_for('frontend.static', filename='templates/account_remove.html') }}'">Supprimer</a></li>
</ul>
</a>
</li>
<li class="divider"></li>
<!-- New account button -->
<li><a bs-modal="'{{ url_for('frontend.static', filename='templates/account_new.html') }}'" href="#">Ajouter un compte</a></li>
</ul>
</li>
</ul><!-- nav -->
</div>
</div>
<div class="container">
<div class="row">
{% block body %}{% endblock %}
</div>
</div>
{% block footer %}{% endblock %}
<!-- JQuery Javascript library -->
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<!-- Bootstrap Javascript library -->
<script type="text/javascript" src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<!-- Bootstrap datepicker module -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.0.2/js/bootstrap-datepicker.min.js"></script>
<!-- Angular Javascript library -->
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular-strap/0.7.4/angular-strap.js"></script>
<!-- D3 Plotting framework -->
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<!-- NVD3 framework -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/nvd3/1.1.13-beta/nv.d3.js"></script>
<!-- Pines Notify JQuery plugin -->
<script type="text/javascript" src="{{ url_for('frontend.static', filename='third-party/pines-notify/jquery.pnotify.min.js') }}"></script>
<!-- Custom Javascript library for entries -->
{% block js %}{% endblock %}
<script type="text/javascript">
angular.module('$strap').config(function($interpolateProvider, $httpProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
$httpProvider.responseInterceptors.push(['$rootScope', '$q', function(scope, $q) {
function success(response) {
console.debug(response)
if(response.data.ok == false) {
return $q.reject(response)
}
// TODO Intercept validation error.
return response;
}
function error(response) {
// TODO Intercept Authentication Required error
$.pnotify({
type: "error",
title: response.data.title,
text: response.data.text,
width: 300
})
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
}
}]);
});
</script>
</body>
</html>

View File

@ -1,39 +0,0 @@
<!--
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
-->
<div class="modal fade" id="remove_entry" role="dialog" aria-labeled-by="remove_entry_header" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<!-- Dialog header with title -->
<div class="modal-header">
<button class="close" ng-click="hideRemoveEntryPopup()" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="remove_entry_header">Supprimer l'entr&eacute;e #[[removingEntry.id]]</h4>
</div>
<!-- Dialog body -->
<div class="modal-body">
<p>Confirmez-vous la suppression de l'entr&eacute;e « [[ removingEntry.label ]] » ?</p>
</div>
<!-- Dialog footer with buttons -->
<div class="modal-footer">
<button href="#" class="btn btn-primary" ng-click="hideRemoveEntryPopup()">Non</button>
<button href="#" class="btn btn-default" ng-click="confirmRemoveEntry()">Oui</button>
</div>
</div>
</div>
</div>

View File

@ -1,106 +0,0 @@
{#
This file is part of Accountant.
Accountant is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends "layout.html" %}
{% block body %}
<!-- Row with entry table -->
<div class="row" ng-controller="SchedulerController">
<table class="table table-striped table-condensed table-hover">
<!-- Head of the table containing column headers and size -->
<thead>
<tr>
<th style="width: 120px">Date de d&eacute;but</th>
<th style="width: 120px">Date de fin</th>
<th style="width: 20px">Jour</th>
<th style="width: 20px">Fr&eacute;q.</th>
<th>Libell&eacute; de l'op&eacute;ration</th>
<th style="width: 50px">Montant</th>
<th style="width: 100px">Cat&eacute;gorie</th>
<th style="width: 60px">Actions</th>
</tr>
</thead>
<!-- Body of the table containing the entries -->
<tbody>
<tr id="operation_[[operation.id]]" class="form-inline" ng-class="operationRowClass(operation.sold)" ng-repeat="operation in operations">
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.start_date" data-date-format="yyyy-mm-dd" bs-datepicker/>
<span ng-show="isDisplaying(operation)">[[operation.start_date]]</span>
</td>
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.stop_date" data-date-format="yyyy-mm-dd" bs-datepicker/>
<span ng-show="isDisplaying(operation)">[[operation.stop_date]]</span>
</td>
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.day"/>
<span ng-show="isDisplaying(operation)">[[operation.day]]</span>
</td>
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.frequency"/>
<span ng-show="isDisplaying(operation)">[[operation.frequency]]</span>
</td>
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.label"/>
<span ng-show="isDisplaying(operation)">[[operation.label]]</span>
</td>
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.value"/>
<span ng-show="isDisplaying(operation)">[[operation.value]]</span>
</td>
<td>
<input ng-show="isEditing(operation)" type="text" class="form-control input-sm" ng-model="operation.category" bs-typeahead="categories"/>
<span ng-show="isDisplaying(operation)">[[operation.category]]</span>
</td>
<td>
<div class="btn-group" ng-show="isEditing(operation)">
<button class="btn btn-xs btn-success" ng-click="saveOperation(operation)" title="Save">
<span ng-class="iconSaveClass(operation)"></span>
</button>
<button class="btn btn-xs btn-default" ng-click="cancelEditOperation(operation)" title="Cancel">
<span ng-class="iconCancelClass(operation)"></span>
</button>
</div>
<div class="btn-group" ng-show="isDisplaying(operation)">
<button class="btn btn-xs btn-default" ng-click="editOperation(operation)" title="edit">
<span class="fa fa-pencil-square-o"></span>
</button>
<button class="btn btn-xs btn-default" bs-modal="'{{ url_for('frontend.static', filename='templates/operation_remove.html') }}'" title="remove">
<span class="fa fa-trash-o"></span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript" src="{{ url_for('frontend.static', filename='js/months.js') }}"></script>
<script type="text/javascript" src="{{ url_for('frontend.static', filename='js/accounts.js') }}"></script>
<script type="text/javascript" src="{{ url_for('frontend.static', filename='js/scheduler.js') }}"></script>
{% endblock %}

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python
from flask.ext.script import Manager from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand from flask.ext.migrate import MigrateCommand
from app import app from accountant import app
manager = Manager(app) manager = Manager(app)
manager.add_command('db', MigrateCommand) manager.add_command('db', MigrateCommand)