Initial commit: Backup der Webseiten

- zoesch.de
- blitzkiste.net
- gruene-hassberge (norbert.zoesch.de)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Felix Zösch
2025-12-13 01:17:15 +01:00
commit 07c290a453
4607 changed files with 1202735 additions and 0 deletions

View File

@@ -0,0 +1,404 @@
(function($, exports) {
"use strict";
/**
* Base LocalStorage cache
*
* @param options {object}
* - key (required) identifier of the collection
* - serverId (recommended) identifier of the Piwigo instance
* - serverKey (required) state of collection server-side
* - lifetime (optional) cache lifetime in seconds
* - loader (required) function called to fetch data, takes a callback as first argument
* which must be called with the loaded date
*/
var LocalStorageCache = function(options) {
this._init(options);
};
/*
* Constructor (deported for easy inheritance)
*/
LocalStorageCache.prototype._init = function(options) {
this.key = options.key + '_' + options.serverId;
this.serverKey = options.serverKey;
this.lifetime = options.lifetime ? options.lifetime*1000 : 3600*1000;
this.loader = options.loader;
this.storage = window.localStorage;
this.ready = !!this.storage;
};
/*
* Get the cache content
* @param callback {function} called with the data as first parameter
*/
LocalStorageCache.prototype.get = function(callback) {
var now = new Date().getTime(),
that = this;
if (this.ready && this.storage[this.key] != undefined) {
var cache = JSON.parse(this.storage[this.key]);
if (now - cache.timestamp <= this.lifetime && cache.key == this.serverKey) {
callback(cache.data);
return;
}
}
this.loader(function(data) {
that.set.call(that, data);
callback(data);
});
};
/*
* Manually set the cache content
* @param data {mixed}
*/
LocalStorageCache.prototype.set = function(data) {
if (this.ready) {
this.storage[this.key] = JSON.stringify({
timestamp: new Date().getTime(),
key: this.serverKey,
data: data
});
}
};
/*
* Manually clear the cache
*/
LocalStorageCache.prototype.clear = function() {
if (this.ready) {
this.storage.removeItem(this.key);
}
};
/**
* Abstract class containing common initialization code for selectize
*/
var AbstractSelectizer = function(){};
AbstractSelectizer.prototype = new LocalStorageCache({});
/*
* Load Selectize with cache content
* @param $target {jQuery} may have some data attributes (create, default, value)
* @param options {object}
* - value (optional) list of preselected items (ids, or objects with "id" attribute")
* - default (optional) default value which will be forced if the select is emptyed
* - create (optional) allow item user creation
* - filter (optional) function called for each select before applying the data
* takes two parameters: cache data, options
* must return new data
*/
AbstractSelectizer.prototype._selectize = function($target, globalOptions) {
$target.data('cache', this);
this.get(function(data) {
$target.each(function() {
var filtered, value, defaultValue,
options = $.extend({}, globalOptions);
// apply filter function
if (options.filter != undefined) {
filtered = options.filter.call(this, data, options);
}
else {
filtered = data;
}
this.selectize.settings.maxOptions = filtered.length + 100;
// active creation mode
if (this.hasAttribute('data-create')) {
options.create = true;
}
this.selectize.settings.create = !!options.create;
// load options
this.selectize.load(function(callback) {
if ($.isEmptyObject(this.options)) {
callback(filtered);
}
});
// load items
if ((value = $(this).data('value'))) {
options.value = value;
}
if (options.value != undefined) {
$.each(value, $.proxy(function(i, cat) {
if ($.isNumeric(cat))
this.selectize.addItem(cat);
else
this.selectize.addItem(cat.id);
}, this));
}
// set default
if ((defaultValue = $(this).data('default'))) {
options.default = defaultValue;
}
if (options.default == 'first') {
options.default = filtered[0] ? filtered[0].id : undefined;
}
if (options.default != undefined) {
// add default item
if (this.selectize.getValue() == '') {
this.selectize.addItem(options.default);
}
// if multiple: prevent item deletion
if (this.multiple) {
this.selectize.getItem(options.default).find('.remove').hide();
this.selectize.on('item_remove', function(id) {
if (id == options.default) {
this.addItem(id);
this.getItem(id).find('.remove').hide();
}
});
}
// if single: restore default on blur
else {
this.selectize.on('dropdown_close', function() {
if (this.getValue() == '') {
this.addItem(options.default);
}
});
}
}
});
});
};
// redefine Selectize templates without escape
AbstractSelectizer.getRender = function(field_label, lang) {
lang = lang || { 'Add': 'Add' };
return {
'option': function(data, escape) {
return '<div class="option">' + data[field_label] + '</div>';
},
'item': function(data, escape) {
return '<div class="item">' + data[field_label] + '</div>';
},
'option_create': function(data, escape) {
return '<div class="create">' + lang['Add'] + ' <strong>' + data.input + '</strong>&hellip;</div>';
}
};
};
/**
* Special LocalStorage for admin categories list
*
* @param options {object}
* - serverId (recommended) identifier of the Piwigo instance
* - serverKey (required) state of collection server-side
* - rootUrl (required) used for WS call
*/
var CategoriesCache = function(options) {
options.key = 'categoriesAdminList';
options.loader = function(callback) {
$.getJSON(options.rootUrl + 'ws.php?format=json&method=pwg.categories.getAdminList', function(data) {
var cats = data.result.categories.map(function(c, i) {
c.pos = i;
delete c['comment'];
delete c['uppercats'];
return c;
});
callback(cats);
});
};
this._init(options);
};
CategoriesCache.prototype = new AbstractSelectizer();
/*
* Init Selectize with cache content
* @see AbstractSelectizer._selectize
*/
CategoriesCache.prototype.selectize = function($target, options) {
options = options || {};
$target.selectize({
valueField: 'id',
labelField: 'fullname',
sortField: 'pos',
searchField: ['fullname'],
plugins: ['remove_button'],
render: AbstractSelectizer.getRender('fullname', options.lang)
});
this._selectize($target, options);
};
/**
* Special LocalStorage for admin tags list
*
* @param options {object}
* - serverId (recommended) identifier of the Piwigo instance
* - serverKey (required) state of collection server-side
* - rootUrl (required) used for WS call
*/
var TagsCache = function(options) {
options.key = 'tagsAdminList';
options.loader = function(callback) {
$.getJSON(options.rootUrl + 'ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
var tags = data.result.tags.map(function(t) {
t.id = '~~' + t.id + '~~';
delete t['url_name'];
delete t['lastmodified'];
return t;
});
callback(tags);
});
};
this._init(options);
};
TagsCache.prototype = new AbstractSelectizer();
/*
* Init Selectize with cache content
* @see AbstractSelectizer._selectize
*/
TagsCache.prototype.selectize = function($target, options) {
options = options || {};
$target.selectize({
valueField: 'id',
labelField: 'name',
sortField: 'name',
searchField: ['name'],
plugins: ['remove_button'],
render: AbstractSelectizer.getRender('name', options.lang)
});
this._selectize($target, options);
};
/**
* Special LocalStorage for admin groups list
*
* @param options {object}
* - serverId (recommended) identifier of the Piwigo instance
* - serverKey (required) state of collection server-side
* - rootUrl (required) used for WS call
*/
var GroupsCache = function(options) {
options.key = 'groupsAdminList';
options.loader = function(callback) {
$.getJSON(options.rootUrl + 'ws.php?format=json&method=pwg.groups.getList&per_page=9999', function(data) {
var groups = data.result.groups.map(function(g) {
delete g['lastmodified'];
return g;
});
callback(groups);
});
};
this._init(options);
};
GroupsCache.prototype = new AbstractSelectizer();
/*
* Init Selectize with cache content
* @see AbstractSelectizer._selectize
*/
GroupsCache.prototype.selectize = function($target, options) {
options = options || {};
$target.selectize({
valueField: 'id',
labelField: 'name',
sortField: 'name',
searchField: ['name'],
plugins: ['remove_button'],
render: AbstractSelectizer.getRender('name', options.lang)
});
this._selectize($target, options);
};
/**
* Special LocalStorage for admin users list
*
* @param options {object}
* - serverId (recommended) identifier of the Piwigo instance
* - serverKey (required) state of collection server-side
* - rootUrl (required) used for WS call
*/
var UsersCache = function(options) {
options.key = 'usersAdminList';
options.loader = function(callback) {
var users = [];
// recursive loader
(function load(page){
jQuery.getJSON(options.rootUrl + 'ws.php?format=json&method=pwg.users.getList&display=username&per_page=9999&page='+ page, function(data) {
users = users.concat(data.result.users);
if (data.result.paging.count == data.result.paging.per_page) {
load(++page);
}
else {
callback(users);
}
});
}(0));
};
this._init(options);
};
UsersCache.prototype = new AbstractSelectizer();
/*
* Init Selectize with cache content
* @see AbstractSelectizer._selectize
*/
UsersCache.prototype.selectize = function($target, options) {
options = options || {};
$target.selectize({
valueField: 'id',
labelField: 'username',
sortField: 'username',
searchField: ['username'],
plugins: ['remove_button'],
render: AbstractSelectizer.getRender('username', options.lang)
});
this._selectize($target, options);
};
/**
* Expose classes in global scope
*/
exports.LocalStorageCache = LocalStorageCache;
exports.CategoriesCache = CategoriesCache;
exports.TagsCache = TagsCache;
exports.GroupsCache = GroupsCache;
exports.UsersCache = UsersCache;
}(jQuery, window));

View File

@@ -0,0 +1,117 @@
jQuery.fn.pwgAddAlbum = function(options) {
options = options || {};
var $popup = jQuery('#addAlbumForm'),
$albumParent = $popup.find('[name="category_parent"]')
$button = jQuery(this),
$target = jQuery('[name="'+ $button.data('addAlbum') +'"]'),
cache = $target.data('cache');
if (!$target[0].selectize) {
jQuery.error('pwgAddAlbum: target must use selectize');
}
if (!cache) {
jQuery.error('pwgAddAlbum: missing categories cache');
}
function init() {
$popup.data('init', true);
cache.selectize($albumParent, {
'default': 0,
'filter': function(categories) {
categories.push({
id: 0,
fullname: '------------',
global_rank: 0
});
if (options.filter) {
categories = options.filter.call(this, categories);
}
return categories;
}
});
$popup.find('form').on('submit', function(e) {
e.preventDefault();
var parent_id = $albumParent.val(),
name = $popup.find('[name=category_name]').val();
jQuery('#categoryNameError').toggle(!name);
if (!name) {
return;
}
jQuery.ajax({
url: 'ws.php?format=json',
type: 'POST',
dataType: 'json',
data: {
method: 'pwg.categories.add',
parent: parent_id,
name: name
},
beforeSend: function() {
jQuery('#albumCreationLoading').show();
},
success: function(data) {
jQuery('#albumCreationLoading').hide();
$button.colorbox.close();
var newAlbum = {
id: data.result.id,
name: name,
fullname: name,
global_rank: '0',
dir: null,
nb_images: 0,
pos: 0
};
var parentSelectize = $albumParent[0].selectize;
if (parent_id != 0) {
var parent = parentSelectize.options[parent_id];
newAlbum.fullname = parent.fullname + ' / ' + newAlbum.fullname;
newAlbum.global_rank = parent.global_rank + '.1';
newAlbum.pos = parent.pos + 1;
}
var targetSelectize = $target[0].selectize;
targetSelectize.addOption(newAlbum);
targetSelectize.setValue(newAlbum.id);
parentSelectize.addOption(newAlbum);
if (options.afterSelect) {
options.afterSelect();
}
},
error: function(XMLHttpRequest, textStatus, errorThrows) {
jQuery('#albumCreationLoading').hide();
alert(errorThrows);
}
});
});
}
this.colorbox({
inline: true,
href: '#addAlbumForm',
width: 650, height: 300,
onComplete: function() {
if (!$popup.data('init')) {
init();
}
jQuery('#categoryNameError').hide();
$popup.find('[name=category_name]').val('').focus();
$albumParent[0].selectize.setValue($target.val() || 0);
}
});
return this;
};

View File

@@ -0,0 +1,383 @@
/* ********** Filters*/
function filter_enable(filter) {
/* show the filter*/
$("#"+filter).show();
/* check the checkbox to declare we use this filter */
$("input[type=checkbox][name="+filter+"_use]").prop("checked", true);
/* forbid to select this filter in the addFilter list */
$("#addFilter").children("option[value="+filter+"]").attr("disabled", "disabled");
}
function filter_disable(filter) {
/* hide the filter line */
$("#"+filter).hide();
/* uncheck the checkbox to declare we do not use this filter */
$("input[name="+filter+"_use]").prop("checked", false);
/* give the possibility to show it again */
$("#addFilter").children("option[value="+filter+"]").removeAttr("disabled");
}
$(".removeFilter").addClass("icon-cancel-circled");
$(".removeFilter").click(function () {
var filter = $(this).parent('li').attr("id");
filter_disable(filter);
return false;
});
$("#addFilter").change(function () {
var filter = $(this).prop("value");
filter_enable(filter);
$(this).prop("value", -1);
});
$("#removeFilters").click(function() {
$("#filterList li").each(function() {
var filter = $(this).attr("id");
filter_disable(filter);
});
return false;
});
$('[data-slider=widths]').pwgDoubleSlider(sliders.widths);
$('[data-slider=heights]').pwgDoubleSlider(sliders.heights);
$('[data-slider=ratios]').pwgDoubleSlider(sliders.ratios);
$('[data-slider=filesizes]').pwgDoubleSlider(sliders.filesizes);
/* ********** Thumbs */
/* Shift-click: select all photos between the click and the shift+click */
jQuery(document).ready(function() {
var last_clicked=0,
last_clickedstatus=true;
jQuery.fn.enableShiftClick = function() {
var inputs = [],
count=0;
this.find('input[type=checkbox]').each(function() {
var pos=count;
inputs[count++]=this;
$(this).bind("shclick", function (dummy,event) {
if (event.shiftKey) {
var first = last_clicked;
var last = pos;
if (first > last) {
first=pos;
last=last_clicked;
}
for (var i=first; i<=last;i++) {
input = $(inputs[i]);
$(input).prop('checked', last_clickedstatus).trigger("change");
if (last_clickedstatus)
{
$(input).closest("li").addClass("thumbSelected");
}
else
{
$(input).closest("li").removeClass("thumbSelected");
}
}
}
else {
last_clicked = pos;
last_clickedstatus = this.checked;
}
return true;
});
$(this).click(function(event) { $(this).triggerHandler("shclick",event)});
});
}
$('ul.thumbnails').enableShiftClick();
});
jQuery("a.preview-box").colorbox( {photo: true} );
jQuery('.thumbnails img').tipTip({
'delay' : 0,
'fadeIn' : 200,
'fadeOut' : 200
});
/* ********** Actions*/
jQuery('[data-datepicker]').pwgDatepicker({
showTimepicker: true,
cancelButton: lang.Cancel
});
jQuery('[data-add-album]').pwgAddAlbum();
$("input[name=remove_author]").click(function () {
if ($(this).is(':checked')) {
$("input[name=author]").hide();
}
else {
$("input[name=author]").show();
}
});
$("input[name=remove_title]").click(function () {
if ($(this).is(':checked')) {
$("input[name=title]").hide();
}
else {
$("input[name=title]").show();
}
});
$("input[name=remove_date_creation]").click(function () {
if ($(this).is(':checked')) {
$("#set_date_creation").hide();
}
else {
$("#set_date_creation").show();
}
});
var derivatives = {
elements: null,
done: 0,
total: 0,
finished: function() {
return derivatives.done == derivatives.total && derivatives.elements && derivatives.elements.length==0;
}
};
function progress(success) {
jQuery('#progressBar').progressBar(derivatives.done, {
max: derivatives.total,
textFormat: 'fraction',
boxImage: 'themes/default/images/progressbar.gif',
barImage: 'themes/default/images/progressbg_orange.gif'
});
if (success !== undefined) {
var type = success ? 'regenerateSuccess': 'regenerateError',
s = jQuery('[name="'+type+'"]').val();
jQuery('[name="'+type+'"]').val(++s);
}
if (derivatives.finished()) {
jQuery('#applyAction').click();
}
}
function getDerivativeUrls() {
var ids = derivatives.elements.splice(0, 500);
var params = {max_urls: 100000, ids: ids, types: []};
jQuery("#action_generate_derivatives input").each( function(i, t) {
if ($(t).is(":checked"))
params.types.push( t.value );
} );
jQuery.ajax( {
type: "POST",
url: 'ws.php?format=json&method=pwg.getMissingDerivatives',
data: params,
dataType: "json",
success: function(data) {
if (!data.stat || data.stat != "ok") {
return;
}
derivatives.total += data.result.urls.length;
progress();
for (var i=0; i < data.result.urls.length; i++) {
jQuery.manageAjax.add("queued", {
type: 'GET',
url: data.result.urls[i] + "&ajaxload=true",
dataType: 'json',
success: ( function(data) { derivatives.done++; progress(true) }),
error: ( function(data) { derivatives.done++; progress(false) })
});
}
if (derivatives.elements.length)
setTimeout( getDerivativeUrls, 25 * (derivatives.total-derivatives.done));
}
} );
}
function selectGenerateDerivAll() {
$("#action_generate_derivatives input[type=checkbox]").prop("checked", true);
}
function selectGenerateDerivNone() {
$("#action_generate_derivatives input[type=checkbox]").prop("checked", false);
}
function selectDelDerivAll() {
$("#action_delete_derivatives input[type=checkbox]").prop("checked", true);
}
function selectDelDerivNone() {
$("#action_delete_derivatives input[type=checkbox]").prop("checked", false);
}
/* delete photos by blocks, with progress bar */
jQuery('#applyAction').click(function(e) {
if (typeof(elements) != "undefined") {
return true;
}
if (jQuery('[name="selectAction"]').val() == 'delete') {
if (!jQuery("#action_delete input[name=confirm_deletion]").is(':checked')) {
jQuery("#action_delete span.errors").show();
return false;
}
e.stopPropagation();
}
else {
return true;
}
jQuery('.bulkAction').hide();
jQuery('#regenerationText').html(lang.deleteProgressMessage);
var maxRequests=1;
var queuedManager = jQuery.manageAjax.create('queued', {
queue: true,
cacheResponse: false,
maxRequests: maxRequests
});
elements = Array();
if (jQuery('input[name=setSelected]').is(':checked')) {
elements = all_elements;
}
else {
jQuery('input[name="selection[]"]').filter(':checked').each(function() {
elements.push(jQuery(this).val());
});
}
progressBar_max = elements.length;
var todo = 0;
var deleteBlockSize = Math.min(
Number((elements.length/2).toFixed()),
1000
);
var image_ids = Array();
jQuery('#applyActionBlock').hide();
jQuery('select[name="selectAction"]').hide();
jQuery('#regenerationMsg').show();
jQuery('#progressBar').progressBar(0, {
max: progressBar_max,
textFormat: 'fraction',
boxImage: 'themes/default/images/progressbar.gif',
barImage: 'themes/default/images/progressbg_orange.gif'
});
for (i=0;i<elements.length;i++) {
image_ids.push(elements[i]);
if (i % deleteBlockSize != deleteBlockSize - 1 && i != elements.length - 1) {
continue;
}
(function(ids) {
var thisBatchSize = ids.length;
queuedManager.add({
type: 'POST',
url: 'ws.php?format=json',
data: {
method: "pwg.images.delete",
pwg_token: jQuery("input[name=pwg_token]").val(),
image_id: ids.join(',')
},
dataType: 'json',
success: function(data) {
todo += thisBatchSize;
var isOk = data.stat && "ok" == data.stat;
if (isOk && data.result != thisBatchSize)
/*TODO: user feedback only data.result images out of thisBatchSize were deleted*/;
/*TODO: user feedback if isError*/
progressDelete(todo, progressBar_max, isOk);
},
error: function(data) {
todo += thisBatchSize;
/*TODO: user feedback*/
progressDelete(todo, progressBar_max, false);
}
});
} )(image_ids);
image_ids = Array();
}
return false;
});
function progressDelete(val, max, success) {
jQuery('#progressBar').progressBar(val, {
max: max,
textFormat: 'fraction',
boxImage: 'themes/default/images/progressbar.gif',
barImage: 'themes/default/images/progressbg_orange.gif'
});
if (val == max) {
jQuery('#applyAction').click();
}
}
jQuery("#action_delete input[name=confirm_deletion]").change(function() {
jQuery("#action_delete span.errors").hide();
});
jQuery('#delete_orphans').click(function(e) {
jQuery(this).hide();
jQuery('#orphans_deletion').show();
var deleteBlockSize = Math.min(
Number((jQuery('#orphans_to_delete').data('origin') / 2).toFixed()),
1000
);
delete_orphans_block(deleteBlockSize);
return false;
});
function delete_orphans_block(blockSize) {
jQuery.ajax({
url: "ws.php?format=json&method=pwg.images.deleteOrphans",
type:"POST",
dataType: "json",
data: {
pwg_token: jQuery("input[name=pwg_token").val(),
block_size: blockSize
},
success:function(data) {
jQuery('#orphans_to_delete').html(data.result.nb_orphans);
var percent_remaining = Number(
(data.result.nb_orphans * 100 / jQuery('#orphans_to_delete').data('origin')).toFixed()
);
var percent_done = 100 - percent_remaining;
jQuery('#orphans_deleted').html(percent_done);
if (data.result.nb_orphans > 0) {
delete_orphans_block();
}
else {
// time to refresh the whole page
var redirect_to = 'admin.php?page=batch_manager';
redirect_to += '&action=delete_orphans';
redirect_to += '&nb_orphans_deleted='+jQuery('#orphans_to_delete').data('origin');
document.location = redirect_to;
}
},
error:function(XMLHttpRequest) {
jQuery('#orphans_deletion').hide();
jQuery('#orphans_deletion_error').show().html('error '+XMLHttpRequest.status+' : '+XMLHttpRequest.statusText);
}
});
}

View File

@@ -0,0 +1,123 @@
jQuery.fn.fontCheckbox = function() {
/* checkbox */
this.find('input[type=checkbox]').each(function() {
if (!jQuery(this).is(':checked')) {
jQuery(this).prev().toggleClass('icon-check icon-check-empty');
}
});
this.find('input[type=checkbox]').on('change', function() {
jQuery(this).prev().removeClass();
if (!jQuery(this).is(':checked')) {
jQuery(this).prev().addClass('icon-check-empty');
}
else {
jQuery(this).prev().addClass('icon-check');
}
});
/* radio */
this.find('input[type=radio]').each(function() {
if (!jQuery(this).is(':checked')) {
jQuery(this).prev().toggleClass('icon-dot-circled icon-circle-empty');
}
});
this.find('input[type=radio]').on('change', function() {
jQuery('.font-checkbox input[type=radio][name="'+ jQuery(this).attr('name') +'"]').each(function() {
jQuery(this).prev().removeClass();
if (!jQuery(this).is(':checked')) {
jQuery(this).prev().addClass('icon-circle-empty');
}
else {
jQuery(this).prev().addClass('icon-dot-circled');
}
})
});
};
// init fontChecbox everywhere
jQuery('.font-checkbox').fontCheckbox();
function array_delete(arr, item) {
var i = arr.indexOf(item);
if (i != -1) arr.splice(i, 1);
}
function str_repeat(i, m) {
for (var o = []; m > 0; o[--m] = i);
return o.join('');
}
if (!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(elt /*, from*/)
{
var len = this.length;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
function sprintf() {
var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
while (f) {
if (m = /^[^\x25]+/.exec(f)) {
o.push(m[0]);
}
else if (m = /^\x25{2}/.exec(f)) {
o.push('%');
}
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
throw('Too few arguments.');
}
if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
throw('Expecting number but found ' + typeof(a));
}
switch (m[7]) {
case 'b': a = a.toString(2); break;
case 'c': a = String.fromCharCode(a); break;
case 'd': a = parseInt(a); break;
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
case 'o': a = a.toString(8); break;
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
case 'u': a = Math.abs(a); break;
case 'x': a = a.toString(16); break;
case 'X': a = a.toString(16).toUpperCase(); break;
}
a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
x = m[5] - String(a).length - s.length;
p = m[5] ? str_repeat(c, x) : '';
o.push(s + (m[4] ? a + p : p + a));
}
else {
throw('Huh ?!');
}
f = f.substring(m[0].length);
}
return o.join('');
}

View File

@@ -0,0 +1,184 @@
(function($) {
jQuery.timepicker.log = jQuery.noop; // that's ugly, but the timepicker is acting weird and throws parsing errors
// modify DatePicker internal methods to replace year select by a numeric input
var origGenerateMonthYearHeader = $.datepicker._generateMonthYearHeader,
origSelectMonthYear = $.datepicker._selectMonthYear;
$.datepicker._generateMonthYearHeader = function(inst, drawMonth, drawYear, minDate, maxDate,
secondary, monthNames, monthNamesShort) {
var html = origGenerateMonthYearHeader.call(this, inst, drawMonth, drawYear, minDate, maxDate,
secondary, monthNames, monthNamesShort);
var yearshtml = "<input type='number' class='ui-datepicker-year' data-handler='selectYear' data-event='change keyup' value='"+drawYear+"' style='width:4em;margin-left:2px;'>";
return html.replace(new RegExp('<select class=\'ui-datepicker-year\'.*</select>', 'gm'), yearshtml);
};
$.datepicker._selectMonthYear = debounce(function(id, select, period) {
if (period === 'M') {
origSelectMonthYear.call(this, id, select, period);
}
else {
var target = $(id),
inst = this._getInst(target[0]),
val = parseInt(select.value, 10);
if (isNaN(val)) {
inst['drawYear'] = '';
}
else {
inst['selectedYear'] = inst['drawYear'] = val;
this._notifyChange(inst);
this._adjustDate(target);
$('.ui-datepicker-year').focus();
}
}
}, 500);
// plugin definition
jQuery.fn.pwgDatepicker = function(settings) {
var options = jQuery.extend(true, {
showTimepicker: false,
cancelButton: false,
}, settings || {});
return this.each(function() {
var $this = jQuery(this),
originalValue = $this.val(),
originalDate,
$target = jQuery('[name="'+ $this.data('datepicker') +'"]'),
linked = !!$target.length,
$start, $end;
if (linked) {
originalValue = $target.val();
}
// custom setter
function set(date, init) {
if (date === '') date = null;
$this.datetimepicker('setDate', date);
if ($this.data('datepicker-start') && $start) {
$start.datetimepicker('option', 'maxDate', date);
}
else if ($this.data('datepicker-end') && $end) {
if (!init) { // on init, "end" is not initialized yet (assuming "start" is before "end" in the DOM)
$end.datetimepicker('option', 'minDate', date);
}
}
if (!date && linked) {
$target.val('');
}
}
// and custom cancel button
if (options.cancelButton) {
options.beforeShow = options.onChangeMonthYear = function() {
setTimeout(function() {
var buttonPane = $this.datepicker('widget')
.find('.ui-datepicker-buttonpane');
if (buttonPane.find('.pwg-datepicker-cancel').length == 0) {
$('<button type="button">'+ options.cancelButton +'</button>')
.on('click', function() {
set(originalDate, false);
$this.datepicker('hide').blur();
})
.addClass('pwg-datepicker-cancel ui-state-error ui-corner-all')
.appendTo(buttonPane);
}
}, 1);
};
}
// init picker
$this.datetimepicker(jQuery.extend({
dateFormat: linked ? 'DD d MM yy' : 'yy-mm-dd',
timeFormat: 'HH:mm',
separator: options.showTimepicker ? ' ' : '',
altField: linked ? $target : null,
altFormat: 'yy-mm-dd',
altTimeFormat: options.showTimepicker ? 'HH:mm:ss' : '',
autoSize: true,
changeMonth : true,
changeYear: true,
altFieldTimeOnly: false,
showSecond: false,
alwaysSetTime: false
}, options));
// attach range pickers
if ($this.data('datepicker-start')) {
$start = jQuery('[data-datepicker="'+ $this.data('datepicker-start') +'"]');
$this.datetimepicker('option', 'onClose', function(date) {
$start.datetimepicker('option', 'maxDate', date);
});
$this.datetimepicker('option', 'minDate', $start.datetimepicker('getDate'));
}
else if ($this.data('datepicker-end')) {
$end = jQuery('[data-datepicker="'+ $this.data('datepicker-end') +'"]');
$this.datetimepicker('option', 'onClose', function(date) {
$end.datetimepicker('option', 'minDate', date);
});
}
// attach unset button
if ($this.data('datepicker-unset')) {
jQuery('#'+ $this.data('datepicker-unset')).on('click', function(e) {
e.preventDefault();
set(null, false);
});
}
// set value from linked input
if (linked) {
var splitted = originalValue.split(' ');
if (splitted.length == 2 && options.showTimepicker) {
set(jQuery.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', originalValue), true);
}
else if (splitted[0].length == 10) {
set(jQuery.datepicker.parseDate('yy-mm-dd', splitted[0]), true);
}
else {
set(null, true);
}
}
originalDate = $this.datetimepicker('getDate');
// autoSize not handled by timepicker
if (options.showTimepicker) {
$this.attr('size', parseInt($this.attr('size'))+6);
}
});
};
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
}(jQuery));

View File

@@ -0,0 +1,62 @@
(function($){
/**
* OPTIONS:
* values {mixed[]}
* selected {object} min and max
* text {string}
*/
$.fn.pwgDoubleSlider = function(options) {
var that = this;
function onChange(e, ui) {
that.find('[data-input=min]').val(options.values[ui.values[0]]);
that.find('[data-input=max]').val(options.values[ui.values[1]]);
that.find('.slider-info').html(sprintf(
options.text,
options.values[ui.values[0]],
options.values[ui.values[1]]
));
}
function findClosest(array, value) {
var closest = null, index = -1;
$.each(array, function(i, v){
if (closest == null || Math.abs(v - value) < Math.abs(closest - value)) {
closest = v;
index = i;
}
});
return index;
}
var values = [
options.values.indexOf(options.selected.min),
options.values.indexOf(options.selected.max)
];
if (values[0] == -1) {
values[0] = findClosest(options.values, options.selected.min);
}
if (values[1] == -1) {
values[1] = findClosest(options.values, options.selected.max);
}
var slider = this.find('.slider-slider').slider({
range: true,
min: 0,
max: options.values.length - 1,
values: values,
slide: onChange,
change: onChange
});
this.find('.slider-choice').on('click', function(){
slider.slider('values', 0, options.values.indexOf($(this).data('min')));
slider.slider('values', 1, options.values.indexOf($(this).data('max')));
});
return this;
};
}(jQuery));

View File

@@ -0,0 +1,62 @@
GeoIp = {
cache: {},
pending: {},
get: function(ip, callback){
if (!GeoIp.storageInit && window.localStorage) {
GeoIp.storageInit = true;
var cache = localStorage.getItem("freegeoip");
if (cache) {
cache = JSON.parse(cache);
for (var key in cache) {
var data = cache[key];
if ( (new Date()).getTime() - data.reqTime > 96 * 3600000)
delete cache[key];
}
GeoIp.cache = cache;
}
jQuery(window).on("unload", function() {
localStorage.setItem("freegeoip", JSON.stringify(GeoIp.cache) );
} );
}
if (GeoIp.cache.hasOwnProperty(ip))
callback(GeoIp.cache[ip]);
else if (GeoIp.pending[ip])
GeoIp.pending[ip].push(callback);
else {
GeoIp.pending[ip] = [callback];
jQuery.ajax( {
url: "http://freegeoip.net/json/" + ip,
dataType: "jsonp",
cache: true,
timeout: 5000,
success: function(data) {
data.reqTime = (new Date()).getTime();
var res=[];
if (data.city) res.push(data.city);
if (data.region_name) res.push(data.region_name);
if (data.country_name) res.push(data.country_name);
data.fullName = res.join(", ");
GeoIp.cache[ip] = data;
var callbacks = GeoIp.pending[ip];
delete GeoIp.pending[ip];
for (var i=0; i<callbacks.length; i++)
callbacks[i].call(null, data);
},
error: function() {
var data = {ip:ip, reqTime: (new Date()).getTime()};
GeoIp.cache[ip] = data;
var callbacks = GeoIp.pending[ip];
delete GeoIp.pending[ip];
for (var i=0; i<callbacks.length; i++)
callbacks[i].call(null, data);
}
});
}
}
}