mirror of https://github.com/zulip/zulip.git
js: Remove /* eslint indent: "off" */ comments.
The time has come to dedent these files. Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
parent
8c065d1fcd
commit
5f590d3500
|
@ -1,98 +1,96 @@
|
|||
/* eslint indent: "off" */
|
||||
|
||||
var bot_data = (function () {
|
||||
var exports = {};
|
||||
var exports = {};
|
||||
|
||||
var bots = {};
|
||||
var bot_fields = ['api_key', 'avatar_url', 'default_all_public_streams',
|
||||
'default_events_register_stream', 'default_sending_stream',
|
||||
'email', 'full_name', 'is_active', 'owner', 'bot_type', 'user_id'];
|
||||
var services = {};
|
||||
var services_fields = ['base_url', 'interface',
|
||||
'config_data', 'service_name', 'token'];
|
||||
var bots = {};
|
||||
var bot_fields = ['api_key', 'avatar_url', 'default_all_public_streams',
|
||||
'default_events_register_stream', 'default_sending_stream',
|
||||
'email', 'full_name', 'is_active', 'owner', 'bot_type', 'user_id'];
|
||||
var services = {};
|
||||
var services_fields = ['base_url', 'interface',
|
||||
'config_data', 'service_name', 'token'];
|
||||
|
||||
var send_change_event = _.debounce(function () {
|
||||
settings_bots.render_bots();
|
||||
}, 50);
|
||||
var send_change_event = _.debounce(function () {
|
||||
settings_bots.render_bots();
|
||||
}, 50);
|
||||
|
||||
var set_can_admin = function bot_data__set_can_admin(bot) {
|
||||
if (page_params.is_admin) {
|
||||
bot.can_admin = true;
|
||||
} else if (bot.owner !== undefined && people.is_current_user(bot.owner)) {
|
||||
bot.can_admin = true;
|
||||
} else {
|
||||
bot.can_admin = false;
|
||||
}
|
||||
};
|
||||
var set_can_admin = function bot_data__set_can_admin(bot) {
|
||||
if (page_params.is_admin) {
|
||||
bot.can_admin = true;
|
||||
} else if (bot.owner !== undefined && people.is_current_user(bot.owner)) {
|
||||
bot.can_admin = true;
|
||||
} else {
|
||||
bot.can_admin = false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.add = function bot_data__add(bot) {
|
||||
var clean_bot = _.pick(bot, bot_fields);
|
||||
bots[bot.user_id] = clean_bot;
|
||||
set_can_admin(clean_bot);
|
||||
var clean_services = _.map(bot.services, function (service) {
|
||||
return _.pick(service, services_fields);
|
||||
});
|
||||
services[bot.user_id] = clean_services;
|
||||
exports.add = function bot_data__add(bot) {
|
||||
var clean_bot = _.pick(bot, bot_fields);
|
||||
bots[bot.user_id] = clean_bot;
|
||||
set_can_admin(clean_bot);
|
||||
var clean_services = _.map(bot.services, function (service) {
|
||||
return _.pick(service, services_fields);
|
||||
});
|
||||
services[bot.user_id] = clean_services;
|
||||
|
||||
send_change_event();
|
||||
};
|
||||
send_change_event();
|
||||
};
|
||||
|
||||
exports.deactivate = function bot_data__deactivate(bot_id) {
|
||||
bots[bot_id].is_active = false;
|
||||
send_change_event();
|
||||
};
|
||||
exports.deactivate = function bot_data__deactivate(bot_id) {
|
||||
bots[bot_id].is_active = false;
|
||||
send_change_event();
|
||||
};
|
||||
|
||||
exports.del = function bot_data__del(bot_id) {
|
||||
delete bots[bot_id];
|
||||
delete services[bot_id];
|
||||
send_change_event();
|
||||
};
|
||||
exports.del = function bot_data__del(bot_id) {
|
||||
delete bots[bot_id];
|
||||
delete services[bot_id];
|
||||
send_change_event();
|
||||
};
|
||||
|
||||
exports.update = function bot_data__update(bot_id, bot_update) {
|
||||
var bot = bots[bot_id];
|
||||
_.extend(bot, _.pick(bot_update, bot_fields));
|
||||
set_can_admin(bot);
|
||||
exports.update = function bot_data__update(bot_id, bot_update) {
|
||||
var bot = bots[bot_id];
|
||||
_.extend(bot, _.pick(bot_update, bot_fields));
|
||||
set_can_admin(bot);
|
||||
|
||||
// We currently only support one service per bot.
|
||||
var service = services[bot_id][0];
|
||||
if (typeof bot_update.services !== 'undefined' && bot_update.services.length > 0) {
|
||||
_.extend(service, _.pick(bot_update.services[0], services_fields));
|
||||
}
|
||||
send_change_event();
|
||||
};
|
||||
// We currently only support one service per bot.
|
||||
var service = services[bot_id][0];
|
||||
if (typeof bot_update.services !== 'undefined' && bot_update.services.length > 0) {
|
||||
_.extend(service, _.pick(bot_update.services[0], services_fields));
|
||||
}
|
||||
send_change_event();
|
||||
};
|
||||
|
||||
exports.get_all_bots_for_current_user = function bots_data__get_editable() {
|
||||
return _.filter(bots, function (bot) {
|
||||
return people.is_current_user(bot.owner);
|
||||
});
|
||||
};
|
||||
exports.get_all_bots_for_current_user = function bots_data__get_editable() {
|
||||
return _.filter(bots, function (bot) {
|
||||
return people.is_current_user(bot.owner);
|
||||
});
|
||||
};
|
||||
|
||||
exports.get_editable = function bots_data__get_editable() {
|
||||
return _.filter(bots, function (bot) {
|
||||
return bot.is_active && people.is_current_user(bot.owner);
|
||||
});
|
||||
};
|
||||
exports.get_editable = function bots_data__get_editable() {
|
||||
return _.filter(bots, function (bot) {
|
||||
return bot.is_active && people.is_current_user(bot.owner);
|
||||
});
|
||||
};
|
||||
|
||||
exports.get = function bot_data__get(bot_id) {
|
||||
return bots[bot_id];
|
||||
};
|
||||
exports.get = function bot_data__get(bot_id) {
|
||||
return bots[bot_id];
|
||||
};
|
||||
|
||||
exports.get_bot_owner_email = function (bot_id) {
|
||||
return bots[bot_id].owner;
|
||||
};
|
||||
exports.get_bot_owner_email = function (bot_id) {
|
||||
return bots[bot_id].owner;
|
||||
};
|
||||
|
||||
exports.get_services = function bot_data__get_services(bot_id) {
|
||||
return services[bot_id];
|
||||
};
|
||||
exports.get_services = function bot_data__get_services(bot_id) {
|
||||
return services[bot_id];
|
||||
};
|
||||
|
||||
exports.initialize = function () {
|
||||
_.each(page_params.realm_bots, function (bot) {
|
||||
exports.add(bot);
|
||||
});
|
||||
delete page_params.realm_bots;
|
||||
};
|
||||
exports.initialize = function () {
|
||||
_.each(page_params.realm_bots, function (bot) {
|
||||
exports.add(bot);
|
||||
});
|
||||
delete page_params.realm_bots;
|
||||
};
|
||||
|
||||
return exports;
|
||||
return exports;
|
||||
}());
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = bot_data;
|
||||
|
|
|
@ -1,335 +1,333 @@
|
|||
/* eslint indent: "off" */
|
||||
|
||||
var LightboxCanvas = (function () {
|
||||
var events = {
|
||||
documentMouseup: [],
|
||||
windowResize: [],
|
||||
};
|
||||
|
||||
window.onload = function () {
|
||||
document.body.addEventListener("mouseup", function (e) {
|
||||
events.documentMouseup = events.documentMouseup.filter(function (event) {
|
||||
// go through automatic cleanup when running events.
|
||||
if (!document.body.contains(event.canvas)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.callback.call(this, e);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("resize", function (e) {
|
||||
events.windowResize = events.windowResize.filter(function (event) {
|
||||
if (!document.body.contains(event.canvas)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.callback.call(this, e);
|
||||
|
||||
return true;
|
||||
}.bind(this));
|
||||
});
|
||||
};
|
||||
|
||||
var funcs = {
|
||||
setZoom: function (meta, zoom) {
|
||||
// condition to handle zooming event by zoom hotkeys
|
||||
if (zoom === '+') {
|
||||
zoom = meta.zoom * 1.2;
|
||||
} else if (zoom === '-') {
|
||||
zoom = meta.zoom / 1.2;
|
||||
}
|
||||
// make sure the zoom is above 1 and below the maxZoom.
|
||||
meta.zoom = Math.min(Math.max(zoom, 1), meta.maxZoom);
|
||||
},
|
||||
|
||||
// this is a function given a canvas that attaches all of the events
|
||||
// required to pan and zoom.
|
||||
attachEvents: function (canvas, context, meta) {
|
||||
var mousedown = false;
|
||||
|
||||
// wheelEvent.deltaMode is a value that describes what the unit is
|
||||
// for the `deltaX`, `deltaY`, and `deltaZ` properties.
|
||||
var DELTA_MODE = {
|
||||
PIXEL: 0,
|
||||
LINE: 1,
|
||||
PAGE: 2,
|
||||
};
|
||||
|
||||
// give object structure in `mousedown`, because its props are only
|
||||
// ever set once `mousedown` + `mousemove` is triggered.
|
||||
var lastPosition = {};
|
||||
|
||||
// in browsers such as Safari, the `e.movementX` and `e.movementY`
|
||||
// props don't exist, so we need to create them as a difference of
|
||||
// where the last `layerX` and `layerY` movements since the last
|
||||
// `mousemove` event in this `mousedown` event were registered.
|
||||
var polyfillMouseMovement = function (e) {
|
||||
e.movementX = e.layerX - lastPosition.x || 0;
|
||||
e.movementY = e.layerY - lastPosition.y || 0;
|
||||
|
||||
lastPosition = {
|
||||
x: e.layerX,
|
||||
y: e.layerY,
|
||||
};
|
||||
};
|
||||
|
||||
// use the wheel event rather than scroll because this isn't
|
||||
// actually an element that can scroll. The wheel event will
|
||||
// detect the *gesture* of scrolling over an element, without actually
|
||||
// worrying about scrollable content.
|
||||
canvas.addEventListener("wheel", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// this is to reverese scrolling directions for the image.
|
||||
var delta = meta.direction * e.deltaY;
|
||||
|
||||
if (e.deltaMode === DELTA_MODE.LINE) {
|
||||
// the vertical height in pixels of an approximate line.
|
||||
delta *= 15;
|
||||
}
|
||||
|
||||
if (e.deltaMode === DELTA_MODE.PAGE) {
|
||||
// the vertical height in pixels of an approximate page.
|
||||
delta *= 300;
|
||||
}
|
||||
|
||||
// this is calculated as the user defined speed times the normalizer
|
||||
// (which just is what it takes to take the raw delta and transform
|
||||
// it to a normal speed), multiply it against the current zoom.
|
||||
// Example:
|
||||
// delta = 8
|
||||
// normalizedDelta = delta * (1 / 20) * 1 = 0.4
|
||||
// zoom = zoom * (0.4 / 100) + 1
|
||||
var zoom = meta.zoom * (
|
||||
meta.speed * meta.internalSpeedMultiplier * delta / 100 + 1
|
||||
);
|
||||
|
||||
funcs.setZoom(meta, zoom);
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
var events = {
|
||||
documentMouseup: [],
|
||||
windowResize: [],
|
||||
};
|
||||
|
||||
window.onload = function () {
|
||||
document.body.addEventListener("mouseup", function (e) {
|
||||
events.documentMouseup = events.documentMouseup.filter(function (event) {
|
||||
// go through automatic cleanup when running events.
|
||||
if (!document.body.contains(event.canvas)) {
|
||||
return false;
|
||||
});
|
||||
|
||||
// the only valid mousedown events should originate inside of the
|
||||
// canvas.
|
||||
canvas.addEventListener("mousedown", function () {
|
||||
mousedown = true;
|
||||
});
|
||||
|
||||
// on mousemove, actually run the pan events.
|
||||
canvas.addEventListener("mousemove", function (e) {
|
||||
// to pan, there must be mousedown and mousemove, check if valid.
|
||||
if (mousedown === true) {
|
||||
polyfillMouseMovement(e);
|
||||
// find the percent of movement relative to the canvas width
|
||||
// since e.movementX, e.movementY are in px.
|
||||
var percentMovement = {
|
||||
x: e.movementX / canvas.width,
|
||||
y: e.movementY / canvas.height,
|
||||
};
|
||||
|
||||
// add the percentMovement to the meta coordinates but divide
|
||||
// out by the zoom ratio because when zoomed in 10x for example
|
||||
// moving the photo by 1% will appear like 10% on the <canvas>.
|
||||
meta.coords.x += percentMovement.x * 2 / meta.zoom;
|
||||
meta.coords.y += percentMovement.y * 2 / meta.zoom;
|
||||
|
||||
// redraw the image.
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
}
|
||||
});
|
||||
|
||||
// event listener to handle zoom in and out from using keyboard keys z/Z and +/-
|
||||
// in the canvas
|
||||
// these hotkeys are not implemented in static/js/hotkey.js as the code in
|
||||
// static/js/lightbox_canvas.js and static/js/lightbox.js isn't written a way
|
||||
// that the LightboxCanvas instance created in lightbox.js can be
|
||||
// accessed from hotkey.js. Major code refactoring is required in lightbox.js
|
||||
// to implement these keyboard shortcuts in hotkey.js
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!overlays.lightbox_open()) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "Z" || e.key === '+') {
|
||||
funcs.setZoom(meta, '+');
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
} else if (e.key === "z" || e.key === '-') {
|
||||
funcs.setZoom(meta, '-');
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
} else if (e.key === 'v') {
|
||||
overlays.close_overlay('lightbox');
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
|
||||
// make sure that when the mousedown is lifted on <canvas>to prevent
|
||||
// panning events.
|
||||
canvas.addEventListener("mouseup", function () {
|
||||
mousedown = false;
|
||||
// reset this to be empty so that the values will `NaN` on first
|
||||
// mousemove and default to a change of (0, 0).
|
||||
lastPosition = {};
|
||||
});
|
||||
|
||||
|
||||
// do so on the document.body as well, though depending on the infra,
|
||||
// these are less reliable as preventDefault may prevent these events
|
||||
// from propagating all the way to the <body>.
|
||||
events.documentMouseup.push({
|
||||
canvas: canvas,
|
||||
meta: meta,
|
||||
callback: function () {
|
||||
mousedown = false;
|
||||
},
|
||||
});
|
||||
|
||||
events.windowResize.push({
|
||||
canvas: canvas,
|
||||
meta: meta,
|
||||
callback: function () {
|
||||
funcs.sizeCanvas(canvas, meta);
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
imageRatio: function (image) {
|
||||
return image.naturalWidth / image.naturalHeight;
|
||||
},
|
||||
|
||||
displayImage: function (canvas, context, meta) {
|
||||
meta.coords.x = Math.max(1 / (meta.zoom * 2), meta.coords.x);
|
||||
meta.coords.x = Math.min(1 - 1 / (meta.zoom * 2), meta.coords.x);
|
||||
|
||||
meta.coords.y = Math.max(1 / (meta.zoom * 2), meta.coords.y);
|
||||
meta.coords.y = Math.min(1 - 1 / (meta.zoom * 2), meta.coords.y);
|
||||
|
||||
var c = {
|
||||
x: meta.coords.x - 1,
|
||||
y: meta.coords.y - 1,
|
||||
};
|
||||
|
||||
var x = meta.zoom * c.x * canvas.width + canvas.width / 2;
|
||||
var y = meta.zoom * c.y * canvas.height + canvas.height / 2;
|
||||
var w = canvas.width * meta.zoom;
|
||||
var h = canvas.height * meta.zoom;
|
||||
|
||||
canvas.width = canvas.width;
|
||||
context.imageSmoothingEnabled = false;
|
||||
|
||||
context.drawImage(meta.image, x, y, w, h);
|
||||
},
|
||||
|
||||
// the `sizeCanvas` method figures out the appropriate bounding box for
|
||||
// the canvas given a parent that has constraints.
|
||||
// for example, if a photo has a ration of 1.5:1 (w:h), and the parent
|
||||
// box is 1:1 respectively, we want to stretch the photo to be as large
|
||||
// as we can, which means that we check if having the photo width = 100%
|
||||
// means that the height is less than 100% of the parent height. If so,
|
||||
// then we size the photo as w = 100%, h = 100% / 1.5.
|
||||
sizeCanvas: function (canvas, meta) {
|
||||
if (typeof meta.onresize === "function") {
|
||||
meta.onresize(canvas);
|
||||
}
|
||||
|
||||
var parent = {
|
||||
width: canvas.parentNode.clientWidth,
|
||||
height: canvas.parentNode.clientHeight,
|
||||
};
|
||||
event.callback.call(this, e);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
if (parent.height * meta.ratio > parent.width) {
|
||||
canvas.width = parent.width * 2;
|
||||
canvas.style.width = parent.width + "px";
|
||||
|
||||
|
||||
canvas.height = parent.width / meta.ratio * 2;
|
||||
canvas.style.height = parent.width / meta.ratio + "px";
|
||||
} else {
|
||||
canvas.height = parent.height * 2;
|
||||
canvas.style.height = parent.height + "px";
|
||||
|
||||
canvas.width = parent.height * meta.ratio * 2;
|
||||
canvas.style.width = parent.height * meta.ratio + "px";
|
||||
window.addEventListener("resize", function (e) {
|
||||
events.windowResize = events.windowResize.filter(function (event) {
|
||||
if (!document.body.contains(event.canvas)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
blueslip.warn("Please specify a 'data-width' or 'data-height' argument for canvas.");
|
||||
},
|
||||
};
|
||||
event.callback.call(this, e);
|
||||
|
||||
// a class w/ prototype to create a new `LightboxCanvas` instance.
|
||||
var __LightboxCanvas = function (el) {
|
||||
var self = this;
|
||||
return true;
|
||||
}.bind(this));
|
||||
});
|
||||
};
|
||||
|
||||
this.meta = {
|
||||
direction: -1,
|
||||
zoom: 1,
|
||||
image: null,
|
||||
coords: {
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
},
|
||||
speed: 1,
|
||||
// this is to normalize the speed to what I would consider to be
|
||||
// "standard" zoom speed.
|
||||
internalSpeedMultiplier: 0.05,
|
||||
maxZoom: 10,
|
||||
var funcs = {
|
||||
setZoom: function (meta, zoom) {
|
||||
// condition to handle zooming event by zoom hotkeys
|
||||
if (zoom === '+') {
|
||||
zoom = meta.zoom * 1.2;
|
||||
} else if (zoom === '-') {
|
||||
zoom = meta.zoom / 1.2;
|
||||
}
|
||||
// make sure the zoom is above 1 and below the maxZoom.
|
||||
meta.zoom = Math.min(Math.max(zoom, 1), meta.maxZoom);
|
||||
},
|
||||
|
||||
// this is a function given a canvas that attaches all of the events
|
||||
// required to pan and zoom.
|
||||
attachEvents: function (canvas, context, meta) {
|
||||
var mousedown = false;
|
||||
|
||||
// wheelEvent.deltaMode is a value that describes what the unit is
|
||||
// for the `deltaX`, `deltaY`, and `deltaZ` properties.
|
||||
var DELTA_MODE = {
|
||||
PIXEL: 0,
|
||||
LINE: 1,
|
||||
PAGE: 2,
|
||||
};
|
||||
|
||||
if (el instanceof Node) {
|
||||
this.canvas = el;
|
||||
} else if (typeof el === "string") {
|
||||
this.canvas = document.querySelector(el);
|
||||
} else {
|
||||
blueslip.warn("Error. 'LightboxCanvas' accepts either string selector or node.");
|
||||
return;
|
||||
// give object structure in `mousedown`, because its props are only
|
||||
// ever set once `mousedown` + `mousemove` is triggered.
|
||||
var lastPosition = {};
|
||||
|
||||
// in browsers such as Safari, the `e.movementX` and `e.movementY`
|
||||
// props don't exist, so we need to create them as a difference of
|
||||
// where the last `layerX` and `layerY` movements since the last
|
||||
// `mousemove` event in this `mousedown` event were registered.
|
||||
var polyfillMouseMovement = function (e) {
|
||||
e.movementX = e.layerX - lastPosition.x || 0;
|
||||
e.movementY = e.layerY - lastPosition.y || 0;
|
||||
|
||||
lastPosition = {
|
||||
x: e.layerX,
|
||||
y: e.layerY,
|
||||
};
|
||||
};
|
||||
|
||||
// use the wheel event rather than scroll because this isn't
|
||||
// actually an element that can scroll. The wheel event will
|
||||
// detect the *gesture* of scrolling over an element, without actually
|
||||
// worrying about scrollable content.
|
||||
canvas.addEventListener("wheel", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// this is to reverese scrolling directions for the image.
|
||||
var delta = meta.direction * e.deltaY;
|
||||
|
||||
if (e.deltaMode === DELTA_MODE.LINE) {
|
||||
// the vertical height in pixels of an approximate line.
|
||||
delta *= 15;
|
||||
}
|
||||
|
||||
if (e.deltaMode === DELTA_MODE.PAGE) {
|
||||
// the vertical height in pixels of an approximate page.
|
||||
delta *= 300;
|
||||
}
|
||||
|
||||
// this is calculated as the user defined speed times the normalizer
|
||||
// (which just is what it takes to take the raw delta and transform
|
||||
// it to a normal speed), multiply it against the current zoom.
|
||||
// Example:
|
||||
// delta = 8
|
||||
// normalizedDelta = delta * (1 / 20) * 1 = 0.4
|
||||
// zoom = zoom * (0.4 / 100) + 1
|
||||
var zoom = meta.zoom * (
|
||||
meta.speed * meta.internalSpeedMultiplier * delta / 100 + 1
|
||||
);
|
||||
|
||||
funcs.setZoom(meta, zoom);
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// the only valid mousedown events should originate inside of the
|
||||
// canvas.
|
||||
canvas.addEventListener("mousedown", function () {
|
||||
mousedown = true;
|
||||
});
|
||||
|
||||
// on mousemove, actually run the pan events.
|
||||
canvas.addEventListener("mousemove", function (e) {
|
||||
// to pan, there must be mousedown and mousemove, check if valid.
|
||||
if (mousedown === true) {
|
||||
polyfillMouseMovement(e);
|
||||
// find the percent of movement relative to the canvas width
|
||||
// since e.movementX, e.movementY are in px.
|
||||
var percentMovement = {
|
||||
x: e.movementX / canvas.width,
|
||||
y: e.movementY / canvas.height,
|
||||
};
|
||||
|
||||
// add the percentMovement to the meta coordinates but divide
|
||||
// out by the zoom ratio because when zoomed in 10x for example
|
||||
// moving the photo by 1% will appear like 10% on the <canvas>.
|
||||
meta.coords.x += percentMovement.x * 2 / meta.zoom;
|
||||
meta.coords.y += percentMovement.y * 2 / meta.zoom;
|
||||
|
||||
// redraw the image.
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
}
|
||||
});
|
||||
|
||||
// event listener to handle zoom in and out from using keyboard keys z/Z and +/-
|
||||
// in the canvas
|
||||
// these hotkeys are not implemented in static/js/hotkey.js as the code in
|
||||
// static/js/lightbox_canvas.js and static/js/lightbox.js isn't written a way
|
||||
// that the LightboxCanvas instance created in lightbox.js can be
|
||||
// accessed from hotkey.js. Major code refactoring is required in lightbox.js
|
||||
// to implement these keyboard shortcuts in hotkey.js
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!overlays.lightbox_open()) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "Z" || e.key === '+') {
|
||||
funcs.setZoom(meta, '+');
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
} else if (e.key === "z" || e.key === '-') {
|
||||
funcs.setZoom(meta, '-');
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
} else if (e.key === 'v') {
|
||||
overlays.close_overlay('lightbox');
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
|
||||
// make sure that when the mousedown is lifted on <canvas>to prevent
|
||||
// panning events.
|
||||
canvas.addEventListener("mouseup", function () {
|
||||
mousedown = false;
|
||||
// reset this to be empty so that the values will `NaN` on first
|
||||
// mousemove and default to a change of (0, 0).
|
||||
lastPosition = {};
|
||||
});
|
||||
|
||||
|
||||
// do so on the document.body as well, though depending on the infra,
|
||||
// these are less reliable as preventDefault may prevent these events
|
||||
// from propagating all the way to the <body>.
|
||||
events.documentMouseup.push({
|
||||
canvas: canvas,
|
||||
meta: meta,
|
||||
callback: function () {
|
||||
mousedown = false;
|
||||
},
|
||||
});
|
||||
|
||||
events.windowResize.push({
|
||||
canvas: canvas,
|
||||
meta: meta,
|
||||
callback: function () {
|
||||
funcs.sizeCanvas(canvas, meta);
|
||||
funcs.displayImage(canvas, context, meta);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
imageRatio: function (image) {
|
||||
return image.naturalWidth / image.naturalHeight;
|
||||
},
|
||||
|
||||
displayImage: function (canvas, context, meta) {
|
||||
meta.coords.x = Math.max(1 / (meta.zoom * 2), meta.coords.x);
|
||||
meta.coords.x = Math.min(1 - 1 / (meta.zoom * 2), meta.coords.x);
|
||||
|
||||
meta.coords.y = Math.max(1 / (meta.zoom * 2), meta.coords.y);
|
||||
meta.coords.y = Math.min(1 - 1 / (meta.zoom * 2), meta.coords.y);
|
||||
|
||||
var c = {
|
||||
x: meta.coords.x - 1,
|
||||
y: meta.coords.y - 1,
|
||||
};
|
||||
|
||||
var x = meta.zoom * c.x * canvas.width + canvas.width / 2;
|
||||
var y = meta.zoom * c.y * canvas.height + canvas.height / 2;
|
||||
var w = canvas.width * meta.zoom;
|
||||
var h = canvas.height * meta.zoom;
|
||||
|
||||
canvas.width = canvas.width;
|
||||
context.imageSmoothingEnabled = false;
|
||||
|
||||
context.drawImage(meta.image, x, y, w, h);
|
||||
},
|
||||
|
||||
// the `sizeCanvas` method figures out the appropriate bounding box for
|
||||
// the canvas given a parent that has constraints.
|
||||
// for example, if a photo has a ration of 1.5:1 (w:h), and the parent
|
||||
// box is 1:1 respectively, we want to stretch the photo to be as large
|
||||
// as we can, which means that we check if having the photo width = 100%
|
||||
// means that the height is less than 100% of the parent height. If so,
|
||||
// then we size the photo as w = 100%, h = 100% / 1.5.
|
||||
sizeCanvas: function (canvas, meta) {
|
||||
if (typeof meta.onresize === "function") {
|
||||
meta.onresize(canvas);
|
||||
}
|
||||
|
||||
this.context = this.canvas.getContext("2d");
|
||||
|
||||
this.meta.image = new Image();
|
||||
this.meta.image.src = this.canvas.getAttribute("data-src");
|
||||
this.meta.image.onload = function () {
|
||||
self.meta.ratio = funcs.imageRatio(this);
|
||||
|
||||
funcs.sizeCanvas(self.canvas, self.meta);
|
||||
funcs.displayImage(self.canvas, self.context, self.meta);
|
||||
var parent = {
|
||||
width: canvas.parentNode.clientWidth,
|
||||
height: canvas.parentNode.clientHeight,
|
||||
};
|
||||
|
||||
this.canvas.image = this.meta.image;
|
||||
if (parent.height * meta.ratio > parent.width) {
|
||||
canvas.width = parent.width * 2;
|
||||
canvas.style.width = parent.width + "px";
|
||||
|
||||
funcs.attachEvents(this.canvas, this.context, self.meta);
|
||||
|
||||
canvas.height = parent.width / meta.ratio * 2;
|
||||
canvas.style.height = parent.width / meta.ratio + "px";
|
||||
} else {
|
||||
canvas.height = parent.height * 2;
|
||||
canvas.style.height = parent.height + "px";
|
||||
|
||||
canvas.width = parent.height * meta.ratio * 2;
|
||||
canvas.style.width = parent.height * meta.ratio + "px";
|
||||
}
|
||||
|
||||
blueslip.warn("Please specify a 'data-width' or 'data-height' argument for canvas.");
|
||||
},
|
||||
};
|
||||
|
||||
// a class w/ prototype to create a new `LightboxCanvas` instance.
|
||||
var __LightboxCanvas = function (el) {
|
||||
var self = this;
|
||||
|
||||
this.meta = {
|
||||
direction: -1,
|
||||
zoom: 1,
|
||||
image: null,
|
||||
coords: {
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
},
|
||||
speed: 1,
|
||||
// this is to normalize the speed to what I would consider to be
|
||||
// "standard" zoom speed.
|
||||
internalSpeedMultiplier: 0.05,
|
||||
maxZoom: 10,
|
||||
};
|
||||
|
||||
__LightboxCanvas.prototype = {
|
||||
// set the speed at which scrolling zooms in on a photo.
|
||||
speed: function (speed) {
|
||||
this.meta.speed = speed;
|
||||
},
|
||||
if (el instanceof Node) {
|
||||
this.canvas = el;
|
||||
} else if (typeof el === "string") {
|
||||
this.canvas = document.querySelector(el);
|
||||
} else {
|
||||
blueslip.warn("Error. 'LightboxCanvas' accepts either string selector or node.");
|
||||
return;
|
||||
}
|
||||
|
||||
// set the max zoom of the `LightboxCanvas` canvas as a mult of the total width.
|
||||
maxZoom: function (maxZoom) {
|
||||
this.meta.maxZoom = maxZoom;
|
||||
},
|
||||
this.context = this.canvas.getContext("2d");
|
||||
|
||||
reverseScrollDirection: function () {
|
||||
this.meta.direction = 1;
|
||||
},
|
||||
this.meta.image = new Image();
|
||||
this.meta.image.src = this.canvas.getAttribute("data-src");
|
||||
this.meta.image.onload = function () {
|
||||
self.meta.ratio = funcs.imageRatio(this);
|
||||
|
||||
setZoom: function (zoom) {
|
||||
funcs.setZoom(this.meta, zoom);
|
||||
funcs.displayImage(this.canvas, this.context, this.meta);
|
||||
},
|
||||
|
||||
resize: function (callback) {
|
||||
this.meta.onresize = callback;
|
||||
},
|
||||
funcs.sizeCanvas(self.canvas, self.meta);
|
||||
funcs.displayImage(self.canvas, self.context, self.meta);
|
||||
};
|
||||
|
||||
return __LightboxCanvas;
|
||||
this.canvas.image = this.meta.image;
|
||||
|
||||
funcs.attachEvents(this.canvas, this.context, self.meta);
|
||||
};
|
||||
|
||||
__LightboxCanvas.prototype = {
|
||||
// set the speed at which scrolling zooms in on a photo.
|
||||
speed: function (speed) {
|
||||
this.meta.speed = speed;
|
||||
},
|
||||
|
||||
// set the max zoom of the `LightboxCanvas` canvas as a mult of the total width.
|
||||
maxZoom: function (maxZoom) {
|
||||
this.meta.maxZoom = maxZoom;
|
||||
},
|
||||
|
||||
reverseScrollDirection: function () {
|
||||
this.meta.direction = 1;
|
||||
},
|
||||
|
||||
setZoom: function (zoom) {
|
||||
funcs.setZoom(this.meta, zoom);
|
||||
funcs.displayImage(this.canvas, this.context, this.meta);
|
||||
},
|
||||
|
||||
resize: function (callback) {
|
||||
this.meta.onresize = callback;
|
||||
},
|
||||
};
|
||||
|
||||
return __LightboxCanvas;
|
||||
}());
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
|
|
|
@ -1,220 +1,281 @@
|
|||
/* eslint indent: "off" */
|
||||
|
||||
var list_render = (function () {
|
||||
|
||||
var exports = {};
|
||||
var exports = {};
|
||||
|
||||
var DEFAULTS = {
|
||||
INITIAL_RENDER_COUNT: 80,
|
||||
LOAD_COUNT: 20,
|
||||
instances: {},
|
||||
var DEFAULTS = {
|
||||
INITIAL_RENDER_COUNT: 80,
|
||||
LOAD_COUNT: 20,
|
||||
instances: {},
|
||||
};
|
||||
|
||||
// @params
|
||||
// container: jQuery object to append to.
|
||||
// list: The list of items to progressively append.
|
||||
// opts: An object of random preferences.
|
||||
exports.create = function ($container, list, opts) {
|
||||
// this memoizes the results and will return a previously invoked
|
||||
// instance's prototype.
|
||||
if (opts.name && DEFAULTS.instances[opts.name]) {
|
||||
// the false flag here means "don't run `init`". This is because a
|
||||
// user is likely reinitializing and will have put .init() afterwards.
|
||||
// This happens when the same codepath is hit multiple times.
|
||||
return DEFAULTS.instances[opts.name]
|
||||
// sets the container to the new container in this prototype's args.
|
||||
.set_container($container)
|
||||
// sets the input to the new input in the args.
|
||||
.set_opts(opts)
|
||||
.__set_events()
|
||||
.data(list)
|
||||
.init();
|
||||
}
|
||||
|
||||
var meta = {
|
||||
sorting_function: null,
|
||||
prop: null,
|
||||
sorting_functions: {},
|
||||
generic_sorting_functions: {},
|
||||
offset: 0,
|
||||
listRenders: {},
|
||||
list: list,
|
||||
filtered_list: list,
|
||||
|
||||
filter_list: function (value, callback) {
|
||||
this.filtered_list = this.list.filter(function (item) {
|
||||
if (typeof callback === "function") {
|
||||
return callback(item, value);
|
||||
}
|
||||
|
||||
return !!(item.toLocaleLowerCase().indexOf(value) >= 0);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// @params
|
||||
// container: jQuery object to append to.
|
||||
// list: The list of items to progressively append.
|
||||
// opts: An object of random preferences.
|
||||
exports.create = function ($container, list, opts) {
|
||||
// this memoizes the results and will return a previously invoked
|
||||
// instance's prototype.
|
||||
if (opts.name && DEFAULTS.instances[opts.name]) {
|
||||
// the false flag here means "don't run `init`". This is because a
|
||||
// user is likely reinitializing and will have put .init() afterwards.
|
||||
// This happens when the same codepath is hit multiple times.
|
||||
return DEFAULTS.instances[opts.name]
|
||||
// sets the container to the new container in this prototype's args.
|
||||
.set_container($container)
|
||||
// sets the input to the new input in the args.
|
||||
.set_opts(opts)
|
||||
.__set_events()
|
||||
.data(list)
|
||||
.init();
|
||||
}
|
||||
if (!opts) {
|
||||
return;
|
||||
}
|
||||
|
||||
var meta = {
|
||||
sorting_function: null,
|
||||
prop: null,
|
||||
sorting_functions: {},
|
||||
generic_sorting_functions: {},
|
||||
offset: 0,
|
||||
listRenders: {},
|
||||
list: list,
|
||||
filtered_list: list,
|
||||
// we want to assume below that `opts.filter` exists, but may not necessarily
|
||||
// have any defined specs.
|
||||
if (!opts.filter) {
|
||||
opts.filter = {};
|
||||
}
|
||||
|
||||
filter_list: function (value, callback) {
|
||||
this.filtered_list = this.list.filter(function (item) {
|
||||
if (typeof callback === "function") {
|
||||
return callback(item, value);
|
||||
}
|
||||
var prototype = {
|
||||
// Reads the provided list (in the scope directly above)
|
||||
// and renders the next block of messages automatically
|
||||
// into the specified contianer.
|
||||
render: function (load_count) {
|
||||
load_count = load_count || opts.load_count || DEFAULTS.LOAD_COUNT;
|
||||
|
||||
return !!(item.toLocaleLowerCase().indexOf(value) >= 0);
|
||||
});
|
||||
},
|
||||
};
|
||||
// Stop once the offset reaches the length of the original list.
|
||||
if (meta.offset >= meta.filtered_list.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
return;
|
||||
}
|
||||
var slice = meta.filtered_list.slice(meta.offset, meta.offset + load_count);
|
||||
|
||||
// we want to assume below that `opts.filter` exists, but may not necessarily
|
||||
// have any defined specs.
|
||||
if (!opts.filter) {
|
||||
opts.filter = {};
|
||||
}
|
||||
var html = _.reduce(slice, function (acc, item) {
|
||||
var _item = opts.modifier(item);
|
||||
|
||||
var prototype = {
|
||||
// Reads the provided list (in the scope directly above)
|
||||
// and renders the next block of messages automatically
|
||||
// into the specified contianer.
|
||||
render: function (load_count) {
|
||||
load_count = load_count || opts.load_count || DEFAULTS.LOAD_COUNT;
|
||||
// if valid jQuery selection, attempt to grab all elements within
|
||||
// and string them together into a giant outerHTML fragment.
|
||||
if (_item.constructor === jQuery) {
|
||||
_item = (function ($nodes) {
|
||||
var html = "";
|
||||
$nodes.each(function () {
|
||||
if (this.nodeType === 1) {
|
||||
html += this.outerHTML;
|
||||
}
|
||||
});
|
||||
|
||||
// Stop once the offset reaches the length of the original list.
|
||||
if (meta.offset >= meta.filtered_list.length) {
|
||||
return;
|
||||
return html;
|
||||
}(_item));
|
||||
}
|
||||
|
||||
var slice = meta.filtered_list.slice(meta.offset, meta.offset + load_count);
|
||||
// if is a valid element, get the outerHTML.
|
||||
if (_item instanceof Element) {
|
||||
_item = _item.outerHTML;
|
||||
}
|
||||
|
||||
var html = _.reduce(slice, function (acc, item) {
|
||||
var _item = opts.modifier(item);
|
||||
// return the modified HTML or nothing if corrupt (null, undef, etc.).
|
||||
return acc + (_item || "");
|
||||
}, "");
|
||||
|
||||
// if valid jQuery selection, attempt to grab all elements within
|
||||
// and string them together into a giant outerHTML fragment.
|
||||
if (_item.constructor === jQuery) {
|
||||
_item = (function ($nodes) {
|
||||
var html = "";
|
||||
$nodes.each(function () {
|
||||
if (this.nodeType === 1) {
|
||||
html += this.outerHTML;
|
||||
}
|
||||
});
|
||||
$container.append($(html));
|
||||
meta.offset += load_count;
|
||||
|
||||
return html;
|
||||
}(_item));
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// if is a valid element, get the outerHTML.
|
||||
if (_item instanceof Element) {
|
||||
_item = _item.outerHTML;
|
||||
}
|
||||
// Fills the container with an initial batch of items.
|
||||
// Needs to be enough to exceed the max height, so that a
|
||||
// scrollable area is created.
|
||||
init: function () {
|
||||
this.clear();
|
||||
this.render(DEFAULTS.INITIAL_RENDER_COUNT);
|
||||
return this;
|
||||
},
|
||||
|
||||
// return the modified HTML or nothing if corrupt (null, undef, etc.).
|
||||
return acc + (_item || "");
|
||||
}, "");
|
||||
filter: function (map_function) {
|
||||
meta.filtered_list = meta.list(map_function);
|
||||
},
|
||||
|
||||
$container.append($(html));
|
||||
meta.offset += load_count;
|
||||
// reset the data associated with a list. This is so that instead of
|
||||
// initializing a new progressive list render instance, you can just
|
||||
// update the data of an existing one.
|
||||
data: function (data) {
|
||||
// if no args are provided then just return the existing data.
|
||||
// this interface is similar to how many jQuery functions operate,
|
||||
// where a call to the method without data returns the existing data.
|
||||
if (typeof data === "undefined" && arguments.length === 0) {
|
||||
return meta.list;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
meta.list = data;
|
||||
|
||||
if (opts.filter && opts.filter.element) {
|
||||
var value = $(opts.filter.element).val().toLocaleLowerCase();
|
||||
meta.filter_list(value, opts.filter.callback);
|
||||
}
|
||||
|
||||
prototype.clear();
|
||||
|
||||
return this;
|
||||
},
|
||||
}
|
||||
|
||||
// Fills the container with an initial batch of items.
|
||||
// Needs to be enough to exceed the max height, so that a
|
||||
// scrollable area is created.
|
||||
init: function () {
|
||||
this.clear();
|
||||
this.render(DEFAULTS.INITIAL_RENDER_COUNT);
|
||||
return this;
|
||||
},
|
||||
|
||||
filter: function (map_function) {
|
||||
meta.filtered_list = meta.list(map_function);
|
||||
},
|
||||
|
||||
// reset the data associated with a list. This is so that instead of
|
||||
// initializing a new progressive list render instance, you can just
|
||||
// update the data of an existing one.
|
||||
data: function (data) {
|
||||
// if no args are provided then just return the existing data.
|
||||
// this interface is similar to how many jQuery functions operate,
|
||||
// where a call to the method without data returns the existing data.
|
||||
if (typeof data === "undefined" && arguments.length === 0) {
|
||||
return meta.list;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
meta.list = data;
|
||||
|
||||
if (opts.filter && opts.filter.element) {
|
||||
var value = $(opts.filter.element).val().toLocaleLowerCase();
|
||||
meta.filter_list(value, opts.filter.callback);
|
||||
}
|
||||
|
||||
prototype.clear();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
blueslip.warn("The data object provided to the progressive" +
|
||||
blueslip.warn("The data object provided to the progressive" +
|
||||
" list render is invalid");
|
||||
return this;
|
||||
},
|
||||
return this;
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
$container.html("");
|
||||
meta.offset = 0;
|
||||
return this;
|
||||
},
|
||||
clear: function () {
|
||||
$container.html("");
|
||||
meta.offset = 0;
|
||||
return this;
|
||||
},
|
||||
|
||||
// Let's imagine the following:
|
||||
// list_render is initialized and becomes prototope A with scope A.
|
||||
// list_render is re-initialized and becomes prototype A with scope A again.
|
||||
// The issue is that when re-initializing, new variables could have been thrown
|
||||
// in and old variables could be useless (eg. dead nodes), so we need to
|
||||
// replace these with new copies if necessary.
|
||||
set_container: function ($new_container) {
|
||||
if ($new_container) {
|
||||
$container = $new_container;
|
||||
// Let's imagine the following:
|
||||
// list_render is initialized and becomes prototope A with scope A.
|
||||
// list_render is re-initialized and becomes prototype A with scope A again.
|
||||
// The issue is that when re-initializing, new variables could have been thrown
|
||||
// in and old variables could be useless (eg. dead nodes), so we need to
|
||||
// replace these with new copies if necessary.
|
||||
set_container: function ($new_container) {
|
||||
if ($new_container) {
|
||||
$container = $new_container;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
set_opts: function (new_opts) {
|
||||
if (opts) {
|
||||
opts = new_opts;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
reverse: function () {
|
||||
meta.filtered_list.reverse();
|
||||
prototype.init();
|
||||
return this;
|
||||
},
|
||||
|
||||
// the sorting function is either the function or string that calls the
|
||||
// function to sort the list by. The prop is used for generic functions
|
||||
// that can be called to sort with a particular prop.
|
||||
|
||||
// the `map` will normalize the values with a function you provide to make
|
||||
// it easier to sort with.
|
||||
|
||||
// `do_not_display` will signal to not update the DOM, likely because in
|
||||
// the next function it will be updated in the DOM.
|
||||
sort: function (sorting_function, prop, do_not_display) {
|
||||
meta.prop = prop;
|
||||
|
||||
if (typeof sorting_function === "function") {
|
||||
meta.sorting_function = sorting_function;
|
||||
} else if (typeof sorting_function === "string") {
|
||||
if (typeof prop === "string") {
|
||||
/* eslint-disable max-len */
|
||||
meta.sorting_function = meta.generic_sorting_functions[sorting_function](prop);
|
||||
} else {
|
||||
meta.sorting_function = meta.sorting_functions[sorting_function];
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
// we do not want to sort if we are just looking to reverse
|
||||
// by calling with no sorting_function
|
||||
if (meta.sorting_function) {
|
||||
meta.filtered_list = meta.filtered_list.sort(meta.sorting_function);
|
||||
}
|
||||
|
||||
set_opts: function (new_opts) {
|
||||
if (opts) {
|
||||
opts = new_opts;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
reverse: function () {
|
||||
meta.filtered_list.reverse();
|
||||
if (!do_not_display) {
|
||||
// clear and re-initialize the list with the newly filtered subset
|
||||
// of items.
|
||||
prototype.init();
|
||||
return this;
|
||||
},
|
||||
|
||||
// the sorting function is either the function or string that calls the
|
||||
// function to sort the list by. The prop is used for generic functions
|
||||
// that can be called to sort with a particular prop.
|
||||
if (opts.filter.onupdate) {
|
||||
opts.filter.onupdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// the `map` will normalize the values with a function you provide to make
|
||||
// it easier to sort with.
|
||||
add_sort_function: function (name, sorting_function) {
|
||||
meta.sorting_functions[name] = sorting_function;
|
||||
},
|
||||
|
||||
// `do_not_display` will signal to not update the DOM, likely because in
|
||||
// the next function it will be updated in the DOM.
|
||||
sort: function (sorting_function, prop, do_not_display) {
|
||||
meta.prop = prop;
|
||||
// generic sorting functions are ones that will use a specified prop
|
||||
// and perform a sort on it with the given sorting function.
|
||||
add_generic_sort_function: function (name, sorting_function) {
|
||||
meta.generic_sorting_functions[name] = sorting_function;
|
||||
},
|
||||
|
||||
if (typeof sorting_function === "function") {
|
||||
meta.sorting_function = sorting_function;
|
||||
} else if (typeof sorting_function === "string") {
|
||||
if (typeof prop === "string") {
|
||||
/* eslint-disable max-len */
|
||||
meta.sorting_function = meta.generic_sorting_functions[sorting_function](prop);
|
||||
} else {
|
||||
meta.sorting_function = meta.sorting_functions[sorting_function];
|
||||
}
|
||||
remove_sort: function () {
|
||||
meta.sorting_function = false;
|
||||
},
|
||||
|
||||
// this sets the events given the particular arguments assigned in
|
||||
// the container and opts.
|
||||
__set_events: function () {
|
||||
var $nearestScrollingContainer = $container;
|
||||
while ($nearestScrollingContainer.length) {
|
||||
if ($nearestScrollingContainer.is("body, html")) {
|
||||
blueslip.warn("Please wrap progressive scrolling lists in an element with 'max-height' attribute. Error found in:\n" + blueslip.preview_node($container));
|
||||
break;
|
||||
}
|
||||
|
||||
// we do not want to sort if we are just looking to reverse
|
||||
// by calling with no sorting_function
|
||||
if (meta.sorting_function) {
|
||||
meta.filtered_list = meta.filtered_list.sort(meta.sorting_function);
|
||||
if ($nearestScrollingContainer.css("max-height") !== "none") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!do_not_display) {
|
||||
$nearestScrollingContainer = $nearestScrollingContainer.parent();
|
||||
}
|
||||
|
||||
// on scroll of the nearest scrolling container, if it hits the bottom
|
||||
// of the container then fetch a new block of items and render them.
|
||||
$nearestScrollingContainer.scroll(function () {
|
||||
if (this.scrollHeight - (this.scrollTop + this.clientHeight) < 10) {
|
||||
prototype.render();
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.filter.element) {
|
||||
opts.filter.element.on(opts.filter.event || "input", function () {
|
||||
var self = this;
|
||||
var value = self.value.toLocaleLowerCase();
|
||||
|
||||
// run the sort algorithm that was used last, which is done
|
||||
// by passing `undefined` -- which will make it use the params
|
||||
// from the last sort.
|
||||
// it will then also not run an update in the DOM (because we
|
||||
// pass `true`), because it will update regardless below at
|
||||
// `prototype.init()`.
|
||||
prototype.sort(undefined, meta.prop, true);
|
||||
meta.filter_list(value, opts.filter.callback);
|
||||
|
||||
// clear and re-initialize the list with the newly filtered subset
|
||||
// of items.
|
||||
prototype.init();
|
||||
|
@ -222,126 +283,63 @@ var list_render = (function () {
|
|||
if (opts.filter.onupdate) {
|
||||
opts.filter.onupdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
add_sort_function: function (name, sorting_function) {
|
||||
meta.sorting_functions[name] = sorting_function;
|
||||
},
|
||||
|
||||
// generic sorting functions are ones that will use a specified prop
|
||||
// and perform a sort on it with the given sorting function.
|
||||
add_generic_sort_function: function (name, sorting_function) {
|
||||
meta.generic_sorting_functions[name] = sorting_function;
|
||||
},
|
||||
|
||||
remove_sort: function () {
|
||||
meta.sorting_function = false;
|
||||
},
|
||||
|
||||
// this sets the events given the particular arguments assigned in
|
||||
// the container and opts.
|
||||
__set_events: function () {
|
||||
var $nearestScrollingContainer = $container;
|
||||
while ($nearestScrollingContainer.length) {
|
||||
if ($nearestScrollingContainer.is("body, html")) {
|
||||
blueslip.warn("Please wrap progressive scrolling lists in an element with 'max-height' attribute. Error found in:\n" + blueslip.preview_node($container));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($nearestScrollingContainer.css("max-height") !== "none") {
|
||||
break;
|
||||
}
|
||||
|
||||
$nearestScrollingContainer = $nearestScrollingContainer.parent();
|
||||
}
|
||||
|
||||
// on scroll of the nearest scrolling container, if it hits the bottom
|
||||
// of the container then fetch a new block of items and render them.
|
||||
$nearestScrollingContainer.scroll(function () {
|
||||
if (this.scrollHeight - (this.scrollTop + this.clientHeight) < 10) {
|
||||
prototype.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.filter.element) {
|
||||
opts.filter.element.on(opts.filter.event || "input", function () {
|
||||
var self = this;
|
||||
var value = self.value.toLocaleLowerCase();
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
// run the sort algorithm that was used last, which is done
|
||||
// by passing `undefined` -- which will make it use the params
|
||||
// from the last sort.
|
||||
// it will then also not run an update in the DOM (because we
|
||||
// pass `true`), because it will update regardless below at
|
||||
// `prototype.init()`.
|
||||
prototype.sort(undefined, meta.prop, true);
|
||||
meta.filter_list(value, opts.filter.callback);
|
||||
prototype.__set_events();
|
||||
|
||||
// clear and re-initialize the list with the newly filtered subset
|
||||
// of items.
|
||||
prototype.init();
|
||||
// add built-in generic sort functions.
|
||||
prototype.add_generic_sort_function("alphabetic", function (prop) {
|
||||
return function (a, b) {
|
||||
// The conversion to uppercase helps make the sorting case insensitive.
|
||||
var str1 = a[prop].toUpperCase();
|
||||
var str2 = b[prop].toUpperCase();
|
||||
|
||||
if (opts.filter.onupdate) {
|
||||
opts.filter.onupdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (str1 === str2) {
|
||||
return 0;
|
||||
} else if (str1 > str2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
return -1;
|
||||
};
|
||||
});
|
||||
|
||||
prototype.__set_events();
|
||||
prototype.add_generic_sort_function("numeric", function (prop) {
|
||||
return function (a, b) {
|
||||
if (parseFloat(a[prop]) > parseFloat(b[prop])) {
|
||||
return 1;
|
||||
} else if (parseFloat(a[prop]) === parseFloat(b[prop])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// add built-in generic sort functions.
|
||||
prototype.add_generic_sort_function("alphabetic", function (prop) {
|
||||
return function (a, b) {
|
||||
// The conversion to uppercase helps make the sorting case insensitive.
|
||||
var str1 = a[prop].toUpperCase();
|
||||
var str2 = b[prop].toUpperCase();
|
||||
return -1;
|
||||
};
|
||||
});
|
||||
|
||||
if (str1 === str2) {
|
||||
return 0;
|
||||
} else if (str1 > str2) {
|
||||
return 1;
|
||||
}
|
||||
// Save the instance for potential future retrieval if a name is provided.
|
||||
if (opts.name) {
|
||||
DEFAULTS.instances[opts.name] = prototype;
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
});
|
||||
// Attach click handler to column heads for sorting rows accordingly
|
||||
if (opts.parent_container) {
|
||||
opts.parent_container.on("click", "[data-sort]", exports.handle_sort);
|
||||
}
|
||||
|
||||
prototype.add_generic_sort_function("numeric", function (prop) {
|
||||
return function (a, b) {
|
||||
if (parseFloat(a[prop]) > parseFloat(b[prop])) {
|
||||
return 1;
|
||||
} else if (parseFloat(a[prop]) === parseFloat(b[prop])) {
|
||||
return 0;
|
||||
}
|
||||
return prototype;
|
||||
};
|
||||
|
||||
return -1;
|
||||
};
|
||||
});
|
||||
exports.get = function (name) {
|
||||
return DEFAULTS.instances[name] || false;
|
||||
};
|
||||
|
||||
// Save the instance for potential future retrieval if a name is provided.
|
||||
if (opts.name) {
|
||||
DEFAULTS.instances[opts.name] = prototype;
|
||||
}
|
||||
|
||||
// Attach click handler to column heads for sorting rows accordingly
|
||||
if (opts.parent_container) {
|
||||
opts.parent_container.on("click", "[data-sort]", exports.handle_sort);
|
||||
}
|
||||
|
||||
return prototype;
|
||||
};
|
||||
|
||||
exports.get = function (name) {
|
||||
return DEFAULTS.instances[name] || false;
|
||||
};
|
||||
|
||||
exports.handle_sort = function () {
|
||||
/*
|
||||
exports.handle_sort = function () {
|
||||
/*
|
||||
one would specify sort parameters like this:
|
||||
- name => sort alphabetic.
|
||||
- age => sort numeric.
|
||||
|
@ -358,39 +356,39 @@ var list_render = (function () {
|
|||
</table>
|
||||
</div>
|
||||
*/
|
||||
var $this = $(this);
|
||||
var sort_type = $this.data("sort");
|
||||
var prop_name = $this.data("sort-prop");
|
||||
var list_name = $this.closest(".progressive-table-wrapper").data("list-render");
|
||||
var $this = $(this);
|
||||
var sort_type = $this.data("sort");
|
||||
var prop_name = $this.data("sort-prop");
|
||||
var list_name = $this.closest(".progressive-table-wrapper").data("list-render");
|
||||
|
||||
var list = list_render.get(list_name);
|
||||
var list = list_render.get(list_name);
|
||||
|
||||
if (!list) {
|
||||
blueslip.error("Error. This `.progressive-table-wrapper` has no `data-list-render` attribute.");
|
||||
return;
|
||||
if (!list) {
|
||||
blueslip.error("Error. This `.progressive-table-wrapper` has no `data-list-render` attribute.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this.hasClass("active")) {
|
||||
if (!$this.hasClass("descend")) {
|
||||
$this.addClass("descend");
|
||||
} else {
|
||||
$this.removeClass("descend");
|
||||
}
|
||||
|
||||
if ($this.hasClass("active")) {
|
||||
if (!$this.hasClass("descend")) {
|
||||
$this.addClass("descend");
|
||||
} else {
|
||||
$this.removeClass("descend");
|
||||
}
|
||||
list.reverse();
|
||||
// Table has already been sorted by this property; do not re-sort.
|
||||
return;
|
||||
}
|
||||
|
||||
list.reverse();
|
||||
// Table has already been sorted by this property; do not re-sort.
|
||||
return;
|
||||
}
|
||||
// if `prop_name` is defined, it will trigger the generic codepath,
|
||||
// and not if it is undefined.
|
||||
list.sort(sort_type, prop_name);
|
||||
|
||||
// if `prop_name` is defined, it will trigger the generic codepath,
|
||||
// and not if it is undefined.
|
||||
list.sort(sort_type, prop_name);
|
||||
$this.siblings(".active").removeClass("active");
|
||||
$this.addClass("active");
|
||||
};
|
||||
|
||||
$this.siblings(".active").removeClass("active");
|
||||
$this.addClass("active");
|
||||
};
|
||||
|
||||
return exports;
|
||||
return exports;
|
||||
}());
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint indent: "off" */
|
||||
import SimpleBar from 'simplebar';
|
||||
import {activate_correct_tab} from './tabbed-instructions.js';
|
||||
|
||||
|
@ -65,107 +64,107 @@ function scrollToHash(simplebar) {
|
|||
}
|
||||
|
||||
(function () {
|
||||
var html_map = {};
|
||||
var loading = {
|
||||
name: null,
|
||||
};
|
||||
var html_map = {};
|
||||
var loading = {
|
||||
name: null,
|
||||
};
|
||||
|
||||
var markdownSB = new SimpleBar($(".markdown")[0]);
|
||||
var markdownSB = new SimpleBar($(".markdown")[0]);
|
||||
|
||||
var fetch_page = function (path, callback) {
|
||||
$.get(path, function (res) {
|
||||
var $html = $(res).find(".markdown .content");
|
||||
var fetch_page = function (path, callback) {
|
||||
$.get(path, function (res) {
|
||||
var $html = $(res).find(".markdown .content");
|
||||
|
||||
callback($html.html().trim());
|
||||
render_code_sections();
|
||||
});
|
||||
};
|
||||
|
||||
var update_page = function (html_map, path) {
|
||||
if (html_map[path]) {
|
||||
$(".markdown .content").html(html_map[path]);
|
||||
render_code_sections();
|
||||
scrollToHash(markdownSB);
|
||||
} else {
|
||||
loading.name = path;
|
||||
fetch_page(path, function (res) {
|
||||
html_map[path] = res;
|
||||
$(".markdown .content").html(html_map[path]);
|
||||
loading.name = null;
|
||||
scrollToHash(markdownSB);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
new SimpleBar($(".sidebar")[0]);
|
||||
|
||||
$(".sidebar.slide h2").click(function (e) {
|
||||
var $next = $(e.target).next();
|
||||
|
||||
if ($next.is("ul")) {
|
||||
// Close other article's headings first
|
||||
$('.sidebar ul').not($next).hide();
|
||||
// Toggle the heading
|
||||
$next.slideToggle("fast", "swing");
|
||||
}
|
||||
callback($html.html().trim());
|
||||
render_code_sections();
|
||||
});
|
||||
};
|
||||
|
||||
$(".sidebar a").click(function (e) {
|
||||
var path = $(this).attr("href");
|
||||
var path_dir = path.split('/')[1];
|
||||
var current_dir = window.location.pathname.split('/')[1];
|
||||
|
||||
// Do not block redirecting to external URLs
|
||||
if (path_dir !== current_dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loading.name === path) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.pushState({}, "", path);
|
||||
|
||||
update_page(html_map, path);
|
||||
|
||||
$(".sidebar").removeClass("show");
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
if (window.location.pathname === '/help/') {
|
||||
// Expand the Guides user docs section in sidebar in the /help/ homepage.
|
||||
$('.help .sidebar h2#guides + ul').show();
|
||||
}
|
||||
// Remove ID attributes from sidebar links so they don't conflict with index page anchor links
|
||||
$('.help .sidebar h1, .help .sidebar h2, .help .sidebar h3').removeAttr('id');
|
||||
|
||||
// Scroll to anchor link when clicked
|
||||
$(document).on('click', '.markdown .content h1, .markdown .content h2, .markdown .content h3', function () {
|
||||
window.location.hash = $(this).attr("id");
|
||||
var update_page = function (html_map, path) {
|
||||
if (html_map[path]) {
|
||||
$(".markdown .content").html(html_map[path]);
|
||||
render_code_sections();
|
||||
scrollToHash(markdownSB);
|
||||
});
|
||||
} else {
|
||||
loading.name = path;
|
||||
fetch_page(path, function (res) {
|
||||
html_map[path] = res;
|
||||
$(".markdown .content").html(html_map[path]);
|
||||
loading.name = null;
|
||||
scrollToHash(markdownSB);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(".hamburger").click(function () {
|
||||
$(".sidebar").toggleClass("show");
|
||||
});
|
||||
new SimpleBar($(".sidebar")[0]);
|
||||
|
||||
$(".markdown").click(function () {
|
||||
if ($(".sidebar.show").length) {
|
||||
$(".sidebar.show").toggleClass("show");
|
||||
}
|
||||
});
|
||||
$(".sidebar.slide h2").click(function (e) {
|
||||
var $next = $(e.target).next();
|
||||
|
||||
render_code_sections();
|
||||
if ($next.is("ul")) {
|
||||
// Close other article's headings first
|
||||
$('.sidebar ul').not($next).hide();
|
||||
// Toggle the heading
|
||||
$next.slideToggle("fast", "swing");
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, make sure if we loaded a window with a hash, we scroll
|
||||
// to the right place.
|
||||
$(".sidebar a").click(function (e) {
|
||||
var path = $(this).attr("href");
|
||||
var path_dir = path.split('/')[1];
|
||||
var current_dir = window.location.pathname.split('/')[1];
|
||||
|
||||
// Do not block redirecting to external URLs
|
||||
if (path_dir !== current_dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loading.name === path) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.pushState({}, "", path);
|
||||
|
||||
update_page(html_map, path);
|
||||
|
||||
$(".sidebar").removeClass("show");
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
if (window.location.pathname === '/help/') {
|
||||
// Expand the Guides user docs section in sidebar in the /help/ homepage.
|
||||
$('.help .sidebar h2#guides + ul').show();
|
||||
}
|
||||
// Remove ID attributes from sidebar links so they don't conflict with index page anchor links
|
||||
$('.help .sidebar h1, .help .sidebar h2, .help .sidebar h3').removeAttr('id');
|
||||
|
||||
// Scroll to anchor link when clicked
|
||||
$(document).on('click', '.markdown .content h1, .markdown .content h2, .markdown .content h3', function () {
|
||||
window.location.hash = $(this).attr("id");
|
||||
scrollToHash(markdownSB);
|
||||
});
|
||||
|
||||
window.addEventListener("popstate", function () {
|
||||
var path = window.location.pathname;
|
||||
update_page(html_map, path);
|
||||
});
|
||||
$(".hamburger").click(function () {
|
||||
$(".sidebar").toggleClass("show");
|
||||
});
|
||||
|
||||
$('body').addClass('noscroll');
|
||||
$(".markdown").click(function () {
|
||||
if ($(".sidebar.show").length) {
|
||||
$(".sidebar.show").toggleClass("show");
|
||||
}
|
||||
});
|
||||
|
||||
render_code_sections();
|
||||
|
||||
// Finally, make sure if we loaded a window with a hash, we scroll
|
||||
// to the right place.
|
||||
scrollToHash(markdownSB);
|
||||
|
||||
window.addEventListener("popstate", function () {
|
||||
var path = window.location.pathname;
|
||||
update_page(html_map, path);
|
||||
});
|
||||
|
||||
$('body').addClass('noscroll');
|
||||
}());
|
||||
|
|
|
@ -1,49 +1,48 @@
|
|||
/* eslint indent: "off" */
|
||||
var realm_icon = (function () {
|
||||
|
||||
var exports = {};
|
||||
var exports = {};
|
||||
|
||||
exports.build_realm_icon_widget = function (upload_function) {
|
||||
var get_file_input = function () {
|
||||
return $('#realm_icon_file_input').expectOne();
|
||||
};
|
||||
exports.build_realm_icon_widget = function (upload_function) {
|
||||
var get_file_input = function () {
|
||||
return $('#realm_icon_file_input').expectOne();
|
||||
};
|
||||
|
||||
if (page_params.realm_icon_source === 'G') {
|
||||
$("#realm_icon_delete_button").hide();
|
||||
} else {
|
||||
$("#realm_icon_delete_button").show();
|
||||
}
|
||||
$("#realm_icon_delete_button").on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
channel.del({
|
||||
url: '/json/realm/icon',
|
||||
});
|
||||
if (page_params.realm_icon_source === 'G') {
|
||||
$("#realm_icon_delete_button").hide();
|
||||
} else {
|
||||
$("#realm_icon_delete_button").show();
|
||||
}
|
||||
$("#realm_icon_delete_button").on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
channel.del({
|
||||
url: '/json/realm/icon',
|
||||
});
|
||||
});
|
||||
|
||||
return upload_widget.build_direct_upload_widget(
|
||||
get_file_input,
|
||||
$("#realm_icon_file_input_error").expectOne(),
|
||||
$("#realm_icon_upload_button").expectOne(),
|
||||
upload_function,
|
||||
page_params.max_icon_file_size
|
||||
);
|
||||
};
|
||||
return upload_widget.build_direct_upload_widget(
|
||||
get_file_input,
|
||||
$("#realm_icon_file_input_error").expectOne(),
|
||||
$("#realm_icon_upload_button").expectOne(),
|
||||
upload_function,
|
||||
page_params.max_icon_file_size
|
||||
);
|
||||
};
|
||||
|
||||
exports.rerender = function () {
|
||||
$("#realm-settings-icon").attr("src", page_params.realm_icon_url);
|
||||
if (page_params.realm_icon_source === 'U') {
|
||||
$("#realm_icon_delete_button").show();
|
||||
} else {
|
||||
$("#realm_icon_delete_button").hide();
|
||||
// Need to clear input because of a small edge case
|
||||
// where you try to upload the same image you just deleted.
|
||||
var file_input = $("#realm_icon_file_input");
|
||||
file_input.val('');
|
||||
}
|
||||
};
|
||||
exports.rerender = function () {
|
||||
$("#realm-settings-icon").attr("src", page_params.realm_icon_url);
|
||||
if (page_params.realm_icon_source === 'U') {
|
||||
$("#realm_icon_delete_button").show();
|
||||
} else {
|
||||
$("#realm_icon_delete_button").hide();
|
||||
// Need to clear input because of a small edge case
|
||||
// where you try to upload the same image you just deleted.
|
||||
var file_input = $("#realm_icon_file_input");
|
||||
file_input.val('');
|
||||
}
|
||||
};
|
||||
|
||||
return exports;
|
||||
return exports;
|
||||
}());
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
|
|
|
@ -1,90 +1,89 @@
|
|||
/* eslint indent: "off" */
|
||||
var realm_logo = (function () {
|
||||
var exports = {};
|
||||
var exports = {};
|
||||
|
||||
exports.build_realm_logo_widget = function (upload_function, is_night) {
|
||||
var logo_section_id = '#day-logo-section';
|
||||
if (is_night) {
|
||||
logo_section_id = '#night-logo-section';
|
||||
}
|
||||
|
||||
var delete_button_elem = $(logo_section_id + " .realm-logo-delete-button");
|
||||
var file_input_elem = $(logo_section_id + " .realm-logo-file-input");
|
||||
var file_input_error_elem = $(logo_section_id + " .realm-logo-file-input-error");
|
||||
var upload_button_elem = $(logo_section_id + " .realm-logo-upload-button");
|
||||
|
||||
var get_file_input = function () {
|
||||
return file_input_elem.expectOne();
|
||||
};
|
||||
|
||||
if (page_params.realm_logo_source === 'D') {
|
||||
delete_button_elem.hide();
|
||||
} else {
|
||||
delete_button_elem.show();
|
||||
}
|
||||
|
||||
var data = {night: JSON.stringify(is_night)};
|
||||
delete_button_elem.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
channel.del({
|
||||
url: '/json/realm/logo',
|
||||
data: data,
|
||||
});
|
||||
});
|
||||
|
||||
return upload_widget.build_direct_upload_widget(
|
||||
get_file_input,
|
||||
file_input_error_elem.expectOne(),
|
||||
upload_button_elem.expectOne(),
|
||||
upload_function,
|
||||
page_params.max_logo_file_size
|
||||
);
|
||||
};
|
||||
|
||||
function change_logo_delete_button(logo_source, logo_delete_button, file_input) {
|
||||
if (logo_source === 'U') {
|
||||
logo_delete_button.show();
|
||||
} else {
|
||||
logo_delete_button.hide();
|
||||
// Need to clear input because of a small edge case
|
||||
// where you try to upload the same image you just deleted.
|
||||
file_input.val('');
|
||||
}
|
||||
exports.build_realm_logo_widget = function (upload_function, is_night) {
|
||||
var logo_section_id = '#day-logo-section';
|
||||
if (is_night) {
|
||||
logo_section_id = '#night-logo-section';
|
||||
}
|
||||
|
||||
exports.rerender = function () {
|
||||
var file_input = $("#day-logo-section .realm-logo-file-input");
|
||||
var night_file_input = $("#night-logo-section .realm-logo-file-input");
|
||||
$("#day-logo-section .realm-logo-img").attr("src", page_params.realm_logo_url);
|
||||
var delete_button_elem = $(logo_section_id + " .realm-logo-delete-button");
|
||||
var file_input_elem = $(logo_section_id + " .realm-logo-file-input");
|
||||
var file_input_error_elem = $(logo_section_id + " .realm-logo-file-input-error");
|
||||
var upload_button_elem = $(logo_section_id + " .realm-logo-upload-button");
|
||||
|
||||
if (page_params.realm_night_logo_source === 'D' &&
|
||||
page_params.realm_logo_source !== 'D') {
|
||||
// If no night mode logo is uploaded but a day mode one
|
||||
// is, use the day mode one; this handles the common case
|
||||
// of transparent background logos that look good on both
|
||||
// night and day themes. See also similar code in admin.js.
|
||||
|
||||
$("#night-logo-section .realm-logo-img").attr("src", page_params.realm_logo_url);
|
||||
} else {
|
||||
$("#night-logo-section .realm-logo-img").attr("src", page_params.realm_night_logo_url);
|
||||
}
|
||||
|
||||
if (page_params.night_mode && page_params.realm_night_logo_source !== 'D') {
|
||||
$("#realm-logo").attr("src", page_params.realm_night_logo_url);
|
||||
} else {
|
||||
$("#realm-logo").attr("src", page_params.realm_logo_url);
|
||||
}
|
||||
|
||||
change_logo_delete_button(page_params.realm_logo_source,
|
||||
$("#day-logo-section .realm-logo-delete-button"),
|
||||
file_input);
|
||||
change_logo_delete_button(page_params.realm_night_logo_source,
|
||||
$("#night-logo-section .realm-logo-delete-button"),
|
||||
night_file_input);
|
||||
var get_file_input = function () {
|
||||
return file_input_elem.expectOne();
|
||||
};
|
||||
|
||||
return exports;
|
||||
if (page_params.realm_logo_source === 'D') {
|
||||
delete_button_elem.hide();
|
||||
} else {
|
||||
delete_button_elem.show();
|
||||
}
|
||||
|
||||
var data = {night: JSON.stringify(is_night)};
|
||||
delete_button_elem.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
channel.del({
|
||||
url: '/json/realm/logo',
|
||||
data: data,
|
||||
});
|
||||
});
|
||||
|
||||
return upload_widget.build_direct_upload_widget(
|
||||
get_file_input,
|
||||
file_input_error_elem.expectOne(),
|
||||
upload_button_elem.expectOne(),
|
||||
upload_function,
|
||||
page_params.max_logo_file_size
|
||||
);
|
||||
};
|
||||
|
||||
function change_logo_delete_button(logo_source, logo_delete_button, file_input) {
|
||||
if (logo_source === 'U') {
|
||||
logo_delete_button.show();
|
||||
} else {
|
||||
logo_delete_button.hide();
|
||||
// Need to clear input because of a small edge case
|
||||
// where you try to upload the same image you just deleted.
|
||||
file_input.val('');
|
||||
}
|
||||
}
|
||||
|
||||
exports.rerender = function () {
|
||||
var file_input = $("#day-logo-section .realm-logo-file-input");
|
||||
var night_file_input = $("#night-logo-section .realm-logo-file-input");
|
||||
$("#day-logo-section .realm-logo-img").attr("src", page_params.realm_logo_url);
|
||||
|
||||
if (page_params.realm_night_logo_source === 'D' &&
|
||||
page_params.realm_logo_source !== 'D') {
|
||||
// If no night mode logo is uploaded but a day mode one
|
||||
// is, use the day mode one; this handles the common case
|
||||
// of transparent background logos that look good on both
|
||||
// night and day themes. See also similar code in admin.js.
|
||||
|
||||
$("#night-logo-section .realm-logo-img").attr("src", page_params.realm_logo_url);
|
||||
} else {
|
||||
$("#night-logo-section .realm-logo-img").attr("src", page_params.realm_night_logo_url);
|
||||
}
|
||||
|
||||
if (page_params.night_mode && page_params.realm_night_logo_source !== 'D') {
|
||||
$("#realm-logo").attr("src", page_params.realm_night_logo_url);
|
||||
} else {
|
||||
$("#realm-logo").attr("src", page_params.realm_logo_url);
|
||||
}
|
||||
|
||||
change_logo_delete_button(page_params.realm_logo_source,
|
||||
$("#day-logo-section .realm-logo-delete-button"),
|
||||
file_input);
|
||||
change_logo_delete_button(page_params.realm_night_logo_source,
|
||||
$("#night-logo-section .realm-logo-delete-button"),
|
||||
night_file_input);
|
||||
};
|
||||
|
||||
return exports;
|
||||
}());
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
|
|
|
@ -1,179 +1,178 @@
|
|||
/* eslint indent: "off" */
|
||||
var upload_widget = (function () {
|
||||
|
||||
var exports = {};
|
||||
var exports = {};
|
||||
|
||||
var default_max_file_size = 5;
|
||||
var default_max_file_size = 5;
|
||||
|
||||
var supported_types = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/tiff',
|
||||
];
|
||||
var supported_types = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/tiff',
|
||||
];
|
||||
|
||||
function is_image_format(file) {
|
||||
var type = file.type;
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
return _.indexOf(supported_types, type) >= 0;
|
||||
function is_image_format(file) {
|
||||
var type = file.type;
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
return _.indexOf(supported_types, type) >= 0;
|
||||
}
|
||||
|
||||
exports.build_widget = function (
|
||||
get_file_input, // function returns a jQuery file input object
|
||||
file_name_field, // jQuery object to show file name
|
||||
input_error, // jQuery object for error text
|
||||
clear_button, // jQuery button to clear last upload choice
|
||||
upload_button, // jQuery button to open file dialog
|
||||
max_file_upload_size
|
||||
) {
|
||||
// default value of max upladed file size
|
||||
max_file_upload_size = max_file_upload_size || default_max_file_size;
|
||||
|
||||
function accept(file) {
|
||||
file_name_field.text(file.name);
|
||||
input_error.hide();
|
||||
clear_button.show();
|
||||
upload_button.hide();
|
||||
}
|
||||
|
||||
exports.build_widget = function (
|
||||
get_file_input, // function returns a jQuery file input object
|
||||
file_name_field, // jQuery object to show file name
|
||||
input_error, // jQuery object for error text
|
||||
clear_button, // jQuery button to clear last upload choice
|
||||
upload_button, // jQuery button to open file dialog
|
||||
max_file_upload_size
|
||||
) {
|
||||
// default value of max upladed file size
|
||||
max_file_upload_size = max_file_upload_size || default_max_file_size;
|
||||
function clear() {
|
||||
var control = get_file_input();
|
||||
control.val('');
|
||||
file_name_field.text('');
|
||||
clear_button.hide();
|
||||
upload_button.show();
|
||||
}
|
||||
|
||||
function accept(file) {
|
||||
file_name_field.text(file.name);
|
||||
input_error.hide();
|
||||
clear_button.show();
|
||||
upload_button.hide();
|
||||
}
|
||||
clear_button.on('click', function (e) {
|
||||
clear();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
function clear() {
|
||||
var control = get_file_input();
|
||||
control.val('');
|
||||
file_name_field.text('');
|
||||
clear_button.hide();
|
||||
upload_button.show();
|
||||
}
|
||||
|
||||
clear_button.on('click', function (e) {
|
||||
clear();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
upload_button.on('drop', function (e) {
|
||||
var files = e.dataTransfer.files;
|
||||
if (files === null || files === undefined || files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
get_file_input().get(0).files = files;
|
||||
e.preventDefault();
|
||||
upload_button.on('drop', function (e) {
|
||||
var files = e.dataTransfer.files;
|
||||
if (files === null || files === undefined || files.length === 0) {
|
||||
return false;
|
||||
});
|
||||
|
||||
get_file_input().attr('accept', supported_types.toString());
|
||||
get_file_input().on('change', function (e) {
|
||||
if (e.target.files.length === 0) {
|
||||
input_error.hide();
|
||||
} else if (e.target.files.length === 1) {
|
||||
var file = e.target.files[0];
|
||||
if (file.size > max_file_upload_size * 1024 * 1024) {
|
||||
input_error.text(i18n.t('File size must be < __max_file_size__Mb.', {
|
||||
max_file_size: max_file_upload_size,
|
||||
}));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else if (!is_image_format(file)) {
|
||||
input_error.text(i18n.t('File type is not supported.'));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else {
|
||||
accept(file);
|
||||
}
|
||||
} else {
|
||||
input_error.text(i18n.t('Please just upload one file.'));
|
||||
}
|
||||
});
|
||||
|
||||
upload_button.on('click', function (e) {
|
||||
get_file_input().trigger('click');
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
function close() {
|
||||
clear();
|
||||
clear_button.off('click');
|
||||
upload_button.off('drop');
|
||||
get_file_input().off('change');
|
||||
upload_button.off('click');
|
||||
}
|
||||
get_file_input().get(0).files = files;
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
return {
|
||||
// Call back to clear() in situations like adding bots, when
|
||||
// we want to use the same widget over and over again.
|
||||
clear: clear,
|
||||
// Call back to close() when you are truly done with the widget,
|
||||
// so you can release handlers.
|
||||
close: close,
|
||||
};
|
||||
};
|
||||
exports.build_direct_upload_widget = function (
|
||||
get_file_input, // function returns a jQuery file input object
|
||||
input_error, // jQuery object for error text
|
||||
upload_button, // jQuery button to open file dialog
|
||||
upload_function,
|
||||
max_file_upload_size
|
||||
) {
|
||||
// default value of max upladed file size
|
||||
max_file_upload_size = max_file_upload_size || default_max_file_size;
|
||||
function accept() {
|
||||
get_file_input().attr('accept', supported_types.toString());
|
||||
get_file_input().on('change', function (e) {
|
||||
if (e.target.files.length === 0) {
|
||||
input_error.hide();
|
||||
if (upload_button.hasClass("night-settings")) {
|
||||
upload_function(get_file_input(), true);
|
||||
} else if (upload_button.hasClass("day-settings")) {
|
||||
upload_function(get_file_input(), false);
|
||||
} else if (e.target.files.length === 1) {
|
||||
var file = e.target.files[0];
|
||||
if (file.size > max_file_upload_size * 1024 * 1024) {
|
||||
input_error.text(i18n.t('File size must be < __max_file_size__Mb.', {
|
||||
max_file_size: max_file_upload_size,
|
||||
}));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else if (!is_image_format(file)) {
|
||||
input_error.text(i18n.t('File type is not supported.'));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else {
|
||||
upload_function(get_file_input());
|
||||
accept(file);
|
||||
}
|
||||
} else {
|
||||
input_error.text(i18n.t('Please just upload one file.'));
|
||||
}
|
||||
});
|
||||
|
||||
function clear() {
|
||||
var control = get_file_input();
|
||||
var new_control = control.clone(true);
|
||||
control.replaceWith(new_control);
|
||||
}
|
||||
upload_button.on('click', function (e) {
|
||||
get_file_input().trigger('click');
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
upload_button.on('drop', function (e) {
|
||||
var files = e.dataTransfer.files;
|
||||
if (files === null || files === undefined || files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
get_file_input().get(0).files = files;
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
function close() {
|
||||
clear();
|
||||
clear_button.off('click');
|
||||
upload_button.off('drop');
|
||||
get_file_input().off('change');
|
||||
upload_button.off('click');
|
||||
}
|
||||
|
||||
get_file_input().attr('accept', supported_types.toString());
|
||||
get_file_input().on('change', function (e) {
|
||||
if (e.target.files.length === 0) {
|
||||
input_error.hide();
|
||||
} else if (e.target.files.length === 1) {
|
||||
var file = e.target.files[0];
|
||||
if (file.size > max_file_upload_size * 1024 * 1024) {
|
||||
input_error.text(i18n.t('File size must be < __max_file_size__Mb.', {
|
||||
max_file_size: max_file_upload_size,
|
||||
}));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else if (!is_image_format(file)) {
|
||||
input_error.text(i18n.t('File type is not supported.'));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else {
|
||||
accept();
|
||||
}
|
||||
} else {
|
||||
input_error.text(i18n.t('Please just upload one file.'));
|
||||
}
|
||||
});
|
||||
|
||||
upload_button.on('click', function (e) {
|
||||
get_file_input().trigger('click');
|
||||
e.preventDefault();
|
||||
});
|
||||
return {
|
||||
// Call back to clear() in situations like adding bots, when
|
||||
// we want to use the same widget over and over again.
|
||||
clear: clear,
|
||||
// Call back to close() when you are truly done with the widget,
|
||||
// so you can release handlers.
|
||||
close: close,
|
||||
};
|
||||
};
|
||||
exports.build_direct_upload_widget = function (
|
||||
get_file_input, // function returns a jQuery file input object
|
||||
input_error, // jQuery object for error text
|
||||
upload_button, // jQuery button to open file dialog
|
||||
upload_function,
|
||||
max_file_upload_size
|
||||
) {
|
||||
// default value of max upladed file size
|
||||
max_file_upload_size = max_file_upload_size || default_max_file_size;
|
||||
function accept() {
|
||||
input_error.hide();
|
||||
if (upload_button.hasClass("night-settings")) {
|
||||
upload_function(get_file_input(), true);
|
||||
} else if (upload_button.hasClass("day-settings")) {
|
||||
upload_function(get_file_input(), false);
|
||||
} else {
|
||||
upload_function(get_file_input());
|
||||
}
|
||||
}
|
||||
|
||||
return exports;
|
||||
function clear() {
|
||||
var control = get_file_input();
|
||||
var new_control = control.clone(true);
|
||||
control.replaceWith(new_control);
|
||||
}
|
||||
|
||||
upload_button.on('drop', function (e) {
|
||||
var files = e.dataTransfer.files;
|
||||
if (files === null || files === undefined || files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
get_file_input().get(0).files = files;
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
get_file_input().attr('accept', supported_types.toString());
|
||||
get_file_input().on('change', function (e) {
|
||||
if (e.target.files.length === 0) {
|
||||
input_error.hide();
|
||||
} else if (e.target.files.length === 1) {
|
||||
var file = e.target.files[0];
|
||||
if (file.size > max_file_upload_size * 1024 * 1024) {
|
||||
input_error.text(i18n.t('File size must be < __max_file_size__Mb.', {
|
||||
max_file_size: max_file_upload_size,
|
||||
}));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else if (!is_image_format(file)) {
|
||||
input_error.text(i18n.t('File type is not supported.'));
|
||||
input_error.show();
|
||||
clear();
|
||||
} else {
|
||||
accept();
|
||||
}
|
||||
} else {
|
||||
input_error.text(i18n.t('Please just upload one file.'));
|
||||
}
|
||||
});
|
||||
|
||||
upload_button.on('click', function (e) {
|
||||
get_file_input().trigger('click');
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
return exports;
|
||||
|
||||
}());
|
||||
|
||||
|
|
Loading…
Reference in New Issue