First commit waiting for Budget Alert
This commit is contained in:
769
sophal/js/view/view.calendar.js
Normal file
769
sophal/js/view/view.calendar.js
Normal file
@ -0,0 +1,769 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
/* global d3: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
ui.controller('CalendarViewCtrl', CalendarViewCtrl);
|
||||
|
||||
CalendarViewCtrl.$inject = ['$scope', '$element'];
|
||||
function CalendarViewCtrl($scope, $element) {
|
||||
|
||||
ui.DSViewCtrl('calendar', $scope, $element);
|
||||
|
||||
var ds = $scope._dataSource;
|
||||
|
||||
var view = {};
|
||||
var colors = {};
|
||||
|
||||
var initialized = false;
|
||||
|
||||
$scope.onShow = function(viewPromise) {
|
||||
|
||||
if (initialized) {
|
||||
return $scope.refresh();
|
||||
}
|
||||
|
||||
viewPromise.then(function(){
|
||||
|
||||
var schema = $scope.schema;
|
||||
|
||||
initialized = true;
|
||||
|
||||
view = {
|
||||
start: schema.eventStart,
|
||||
stop: schema.eventStop,
|
||||
length: parseInt(schema.eventLength) || 0,
|
||||
color: schema.colorBy,
|
||||
title: schema.items[0].name
|
||||
};
|
||||
|
||||
$scope._viewResolver.resolve(schema, $element);
|
||||
$scope.updateRoute();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isAgenda = function() {
|
||||
var field = this.fields[view.start];
|
||||
return field && field.type === "datetime";
|
||||
};
|
||||
|
||||
var d3_colors = d3.scale.category10().range().concat(
|
||||
d3.scale.category20().range());
|
||||
|
||||
function nextColor(n) {
|
||||
if (n === undefined || n < 0 || n >= d3_colors.length) {
|
||||
n = _.random(0, d3_colors.length);
|
||||
}
|
||||
var c = d3.rgb(d3_colors[n]);
|
||||
return {
|
||||
bg: "" + c,
|
||||
fg: "" + c.brighter(99),
|
||||
bc: "" + c.darker(0.9)
|
||||
};
|
||||
}
|
||||
|
||||
$scope.fetchItems = function(start, end, callback) {
|
||||
var fields = _.pluck(this.fields, 'name');
|
||||
var criteria = {
|
||||
operator: "and",
|
||||
criteria: [{
|
||||
fieldName: view.start,
|
||||
operator: ">=",
|
||||
value: start
|
||||
}, {
|
||||
fieldName: view.start,
|
||||
operator: "<=",
|
||||
value: end
|
||||
}]
|
||||
};
|
||||
|
||||
// make sure to include items whose end date falls in current range
|
||||
if (view.stop) {
|
||||
criteria = {
|
||||
operator: "or",
|
||||
criteria: [criteria, {
|
||||
operator: "and",
|
||||
criteria: [{
|
||||
fieldName: view.stop,
|
||||
operator: ">=",
|
||||
value: start
|
||||
}, {
|
||||
fieldName: view.stop,
|
||||
operator: "<=",
|
||||
value: end
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// consider stored filter
|
||||
if (ds._filter) {
|
||||
if (ds._filter.criteria) {
|
||||
criteria = {
|
||||
operator: "and",
|
||||
criteria: [criteria].concat(ds._filter.criteria)
|
||||
};
|
||||
}
|
||||
if (_.size(ds._filter._domains) > 0) {
|
||||
criteria._domains = ds._filter._domains;
|
||||
}
|
||||
}
|
||||
|
||||
var opts = {
|
||||
fields: fields,
|
||||
filter: criteria,
|
||||
domain: this._domain,
|
||||
context: this._context,
|
||||
store: false,
|
||||
limit: -1
|
||||
};
|
||||
|
||||
ds.search(opts).success(function(records) {
|
||||
var items = _.clone(records);
|
||||
items.sort(function (x, y) { return x.id - y.id; });
|
||||
updateColors(items, true);
|
||||
callback(records);
|
||||
});
|
||||
};
|
||||
|
||||
function updateColors(records, reset) {
|
||||
var colorBy = view.color;
|
||||
var colorField = $scope.fields[colorBy];
|
||||
|
||||
if (!colorField) {
|
||||
return colors;
|
||||
}
|
||||
if (reset) {
|
||||
colors = {};
|
||||
}
|
||||
|
||||
_.each(records, function(record) {
|
||||
var item = record[colorBy];
|
||||
if (item === null || item === undefined) {
|
||||
return;
|
||||
}
|
||||
var key = $scope.getColorKey(record, item);
|
||||
var title = colorField.targetName ? item[colorField.targetName] : item;
|
||||
if (colorField.selectionList) {
|
||||
var select = _.find(colorField.selectionList, function (select) {
|
||||
return ("" + select.value) === ("" + title);
|
||||
});
|
||||
if (select) {
|
||||
title = select.title;
|
||||
}
|
||||
}
|
||||
if (!colors[key]) {
|
||||
colors[key] = {
|
||||
item: item,
|
||||
title: title || _t('Unknown'),
|
||||
color: nextColor(_.size(colors))
|
||||
};
|
||||
}
|
||||
record.$colorKey = key;
|
||||
});
|
||||
return colors;
|
||||
}
|
||||
|
||||
$scope.getColors = function() {
|
||||
return colors;
|
||||
};
|
||||
|
||||
$scope.getColor = function(record) {
|
||||
var key = this.getColorKey(record);
|
||||
if (key && !colors[key]) {
|
||||
updateColors([record], false);
|
||||
}
|
||||
if (colors[key]) {
|
||||
return colors[key].color;
|
||||
}
|
||||
return nextColor(0);
|
||||
};
|
||||
|
||||
$scope.getColorKey = function(record, key) {
|
||||
if (key) {
|
||||
return "" + (key.id || key);
|
||||
}
|
||||
if (record) {
|
||||
return this.getColorKey(null, record[view.color]);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
$scope.getEventInfo = function(record) {
|
||||
var info = {},
|
||||
value;
|
||||
|
||||
value = record[view.start];
|
||||
info.start = value ? moment(value) : moment();
|
||||
|
||||
value = record[view.stop];
|
||||
info.end = value ? moment(value) : moment(info.start).add(view.length || 1, "hours");
|
||||
|
||||
var title = this.fields[view.title];
|
||||
var titleText = null;
|
||||
|
||||
if (title) {
|
||||
value = record[title.name];
|
||||
if (title.targetName) {
|
||||
value = value[title.targetName];
|
||||
} else if (title.selectionList) {
|
||||
var select = _.find(title.selectionList, function (select) {
|
||||
return ("" + select.value) === ("" + value);
|
||||
});
|
||||
if (select) {
|
||||
titleText = select.title;
|
||||
}
|
||||
}
|
||||
titleText = titleText || value || _t('Unknown');
|
||||
}
|
||||
|
||||
info.title = ("" + titleText);
|
||||
info.allDay = isAllDay(info);
|
||||
info.className = info.allDay ? "calendar-event-allDay" : "calendar-event-day";
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
function isAllDay(event) {
|
||||
if($scope.fields[view.start] && $scope.fields[view.start].type === 'date') {
|
||||
return true;
|
||||
}
|
||||
var start = moment(event.start);
|
||||
var end = moment(event.end);
|
||||
if (start.format("HH:mm") !== "00:00") {
|
||||
return false;
|
||||
}
|
||||
return !event.end || end.format("HH:mm") === "00:00";
|
||||
}
|
||||
|
||||
$scope.onEventChange = function(event, delta) {
|
||||
|
||||
var record = _.clone(event.record);
|
||||
|
||||
var start = event.start;
|
||||
var end = event.end;
|
||||
|
||||
if (isAllDay(event)) {
|
||||
start = start.clone().startOf("day").local();
|
||||
end = (end || start).clone().startOf("day").local();
|
||||
}
|
||||
|
||||
record[view.start] = start;
|
||||
record[view.stop] = end;
|
||||
|
||||
$scope.record = record;
|
||||
|
||||
function reset() {
|
||||
$scope.record = null;
|
||||
}
|
||||
|
||||
function doSave() {
|
||||
return ds.save(record).success(function(res){
|
||||
return $scope.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
var handler = $scope.onChangeHandler;
|
||||
if (handler) {
|
||||
var promise = handler.onChange().then(function () {
|
||||
return doSave();
|
||||
});
|
||||
promise.success = function(fn) {
|
||||
promise.then(fn);
|
||||
return promise;
|
||||
};
|
||||
promise.error = function (fn) {
|
||||
promise.then(null, fn);
|
||||
return promise;
|
||||
};
|
||||
return promise;
|
||||
}
|
||||
|
||||
return doSave();
|
||||
};
|
||||
|
||||
$scope.removeEvent = function(event, callback) {
|
||||
ds.remove(event.record).success(callback);
|
||||
};
|
||||
|
||||
$scope.select = function() {
|
||||
|
||||
};
|
||||
|
||||
$scope.refresh = function() {
|
||||
|
||||
};
|
||||
|
||||
$scope.getRouteOptions = function() {
|
||||
var args = [],
|
||||
query = {};
|
||||
|
||||
return {
|
||||
mode: 'calendar',
|
||||
args: args,
|
||||
query: query
|
||||
};
|
||||
};
|
||||
|
||||
$scope.setRouteOptions = function(options) {
|
||||
var opts = options || {};
|
||||
|
||||
if (opts.mode === "calendar") {
|
||||
return $scope.updateRoute();
|
||||
}
|
||||
var params = $scope._viewParams;
|
||||
if (params.viewType !== "calendar") {
|
||||
return $scope.show();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.canNext = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.canPrev = function() {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('axelor.ui').directive('uiViewCalendar', ['ViewService', 'ActionService', function(ViewService, ActionService) {
|
||||
|
||||
function link(scope, element, attrs, controller) {
|
||||
|
||||
var main = element.children('.calendar-main');
|
||||
var mini = element.find('.calendar-mini');
|
||||
var legend = element.find('.calendar-legend');
|
||||
|
||||
var ctx = (scope._viewParams.context||{});
|
||||
var params = (scope._viewParams.params||{});
|
||||
|
||||
var schema = scope.schema;
|
||||
var mode = ctx.calendarMode || params.calendarMode || schema.mode || "month";
|
||||
var date = ctx.calendarDate || params.calendarDate;
|
||||
|
||||
var editable = schema.editable === undefined ? true : schema.editable;
|
||||
var calRange = {};
|
||||
|
||||
var RecordManager = (function () {
|
||||
|
||||
var records = [],
|
||||
current = [];
|
||||
|
||||
function add(record) {
|
||||
if (!record || _.isArray(record)) {
|
||||
records = record || [];
|
||||
return filter();
|
||||
}
|
||||
var found = _.findWhere(records, {
|
||||
id: record.id
|
||||
});
|
||||
if (!found) {
|
||||
found = record;
|
||||
records.push(record);
|
||||
}
|
||||
if (found !== record) {
|
||||
_.extend(found, record);
|
||||
}
|
||||
return filter();
|
||||
}
|
||||
|
||||
function remove(record) {
|
||||
records = _.filter(records, function (item) {
|
||||
return record.id !== item.id;
|
||||
});
|
||||
return filter();
|
||||
}
|
||||
|
||||
function filter() {
|
||||
|
||||
var selected = [];
|
||||
|
||||
_.each(scope.getColors(), function (color) {
|
||||
if (color.checked) {
|
||||
selected.push(scope.getColorKey(null, color.item));
|
||||
}
|
||||
});
|
||||
|
||||
main.fullCalendar('removeEventSource', current);
|
||||
current = records;
|
||||
|
||||
if (selected.length) {
|
||||
current = _.filter(records, function(record) {
|
||||
return _.contains(selected, record.$colorKey);
|
||||
});
|
||||
}
|
||||
|
||||
main.fullCalendar('addEventSource', current);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
function events(start, end, timezone, callback) {
|
||||
calRange.start = start;
|
||||
calRange.end = end;
|
||||
scope._viewPromise.then(function(){
|
||||
scope.fetchItems(start, end, function(items) {
|
||||
callback([]);
|
||||
add(items);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
add: add,
|
||||
remove: remove,
|
||||
filter: filter,
|
||||
events: events
|
||||
};
|
||||
}());
|
||||
|
||||
if (date) {
|
||||
date = moment(date).toDate();
|
||||
}
|
||||
|
||||
mini.datepicker({
|
||||
showOtherMonths: true,
|
||||
selectOtherMonths: true,
|
||||
onSelect: function(dateStr) {
|
||||
main.fullCalendar('gotoDate', mini.datepicker('getDate'));
|
||||
}
|
||||
});
|
||||
|
||||
if (date) {
|
||||
mini.datepicker('setDate', date);
|
||||
}
|
||||
|
||||
var lang = axelor.config["user.lang"] || 'en';
|
||||
|
||||
var options = {
|
||||
|
||||
header: false,
|
||||
|
||||
timeFormat: 'h(:mm)t',
|
||||
|
||||
axisFormat: 'h(:mm)t',
|
||||
|
||||
timezone: 'local',
|
||||
|
||||
lang: lang,
|
||||
|
||||
editable: editable,
|
||||
|
||||
selectable: editable,
|
||||
|
||||
selectHelper: editable,
|
||||
|
||||
select: function(start, end) {
|
||||
var event = {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
|
||||
// all day
|
||||
if (!start.hasTime() && !end.hasTime()) {
|
||||
event.start = start.clone().startOf("day").local();
|
||||
event.end = end.clone().startOf("day").local();
|
||||
}
|
||||
|
||||
scope.$applyAsync(function(){
|
||||
scope.showEditor(event);
|
||||
});
|
||||
main.fullCalendar('unselect');
|
||||
},
|
||||
|
||||
defaultDate: date,
|
||||
|
||||
events: RecordManager,
|
||||
|
||||
eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
|
||||
hideBubble();
|
||||
scope.onEventChange(event, delta).error(function(){
|
||||
revertFunc();
|
||||
});
|
||||
},
|
||||
|
||||
eventResize: function( event, delta, revertFunc, jsEvent, ui, view ) {
|
||||
scope.onEventChange(event, delta).error(function(){
|
||||
revertFunc();
|
||||
});
|
||||
},
|
||||
|
||||
eventClick: function(event, jsEvent, view) {
|
||||
showBubble(event, jsEvent.target);
|
||||
},
|
||||
|
||||
eventDataTransform: function(record) {
|
||||
return updateEvent(null, record);
|
||||
},
|
||||
|
||||
viewDisplay: function(view) {
|
||||
hideBubble();
|
||||
mini.datepicker('setDate', main.fullCalendar('getDate'));
|
||||
},
|
||||
|
||||
allDayText: _t('All Day')
|
||||
};
|
||||
|
||||
if (lang.indexOf('fr') === 0) {
|
||||
_.extend(options, {
|
||||
timeFormat: 'H:mm',
|
||||
axisFormat: 'H:mm',
|
||||
firstDay: 1,
|
||||
|
||||
views: {
|
||||
week: {
|
||||
titleFormat: 'D MMM YYYY',
|
||||
columnFormat: 'ddd DD/MM'
|
||||
},
|
||||
|
||||
day: {
|
||||
titleFormat: 'D MMM YYYY'
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main.fullCalendar(options);
|
||||
|
||||
var editor = null;
|
||||
var bubble = null;
|
||||
|
||||
function hideBubble() {
|
||||
if (bubble) {
|
||||
bubble.popover('destroy');
|
||||
bubble = null;
|
||||
}
|
||||
}
|
||||
|
||||
function showBubble(event, elem) {
|
||||
hideBubble();
|
||||
bubble = $(elem).popover({
|
||||
html: true,
|
||||
title: "<b>" + event.title + "</b>",
|
||||
placement: "top",
|
||||
container: 'body',
|
||||
content: function() {
|
||||
var html = $("<div></div>").addClass("calendar-bubble-content");
|
||||
var start = event.start;
|
||||
var end = event.end && event.allDay ? moment(event.end).add(-1, 'second') : event.end;
|
||||
var singleDay = (event.allDay || !scope.isAgenda()) && (!end || moment(start).isSame(end, 'day'));
|
||||
var dateFormat = !scope.isAgenda() || event.allDay ? "ddd D MMM" : "ddd D MMM HH:mm";
|
||||
|
||||
$("<span>").text(moment(start).format(dateFormat)).appendTo(html);
|
||||
|
||||
if (schema.eventStop && end && !singleDay) {
|
||||
$("<span> - </span>").appendTo(html);
|
||||
$("<span>").text(moment(end).format(dateFormat)).appendTo(html);
|
||||
}
|
||||
|
||||
$("<hr>").appendTo(html);
|
||||
|
||||
if (scope.isEditable()) {
|
||||
$('<a href="javascript: void(0)" style="margin-right: 5px;"></a>').text(_t("Delete"))
|
||||
.appendTo(html)
|
||||
.click(function(e){
|
||||
hideBubble();
|
||||
scope.$applyAsync(function(){
|
||||
scope.removeEvent(event, function() {
|
||||
RecordManager.remove(event.record);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('<a class="pull-right" href="javascript: void(0)"></a>')
|
||||
.append(_t("Edit event")).append("<strong> »</strong>")
|
||||
.appendTo(html)
|
||||
.click(function(e){
|
||||
hideBubble();
|
||||
scope.$applyAsync(function(){
|
||||
scope.showEditor(event);
|
||||
});
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
});
|
||||
|
||||
bubble.popover('show');
|
||||
}
|
||||
|
||||
$("body").on("mousedown", function(e){
|
||||
var elem = $(e.target || e.srcElement);
|
||||
if (!bubble || bubble.is(elem) || bubble.has(elem).length) {
|
||||
return;
|
||||
}
|
||||
if (!elem.parents().is(".popover")) {
|
||||
hideBubble();
|
||||
}
|
||||
});
|
||||
|
||||
function updateEvent(event, record) {
|
||||
if (!event || !event.id) {
|
||||
var color = scope.getColor(record);
|
||||
event = {
|
||||
id: record.id,
|
||||
record: record,
|
||||
backgroundColor: color.bg,
|
||||
borderColor: color.bc,
|
||||
textColor: color.fg
|
||||
};
|
||||
} else {
|
||||
_.extend(event.record, record);
|
||||
}
|
||||
|
||||
event = _.extend(event, scope.getEventInfo(record));
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
scope.editorCanSave = true;
|
||||
|
||||
scope.showEditor = function(event) {
|
||||
|
||||
var view = this.schema;
|
||||
var record = _.extend({}, event.record);
|
||||
|
||||
record[view.eventStart] = event.start;
|
||||
record[view.eventStop] = event.end;
|
||||
|
||||
if (!editor) {
|
||||
editor = ViewService.compile('<div ui-editor-popup></div>')(scope.$new());
|
||||
editor.data('$target', element);
|
||||
}
|
||||
|
||||
var popup = editor.isolateScope();
|
||||
|
||||
popup.show(record, function(result) {
|
||||
RecordManager.add(result);
|
||||
});
|
||||
popup.waitForActions(function() {
|
||||
if (!record || !record.id) {
|
||||
popup.$broadcast("on:new");
|
||||
} else {
|
||||
popup.setEditable(scope.isEditable());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scope.isEditable = function() {
|
||||
return editable;
|
||||
};
|
||||
|
||||
scope.refresh = function(record) {
|
||||
if (calRange.start && calRange.end) {
|
||||
return RecordManager.events(calRange.start, calRange.end, options.timezone, function () {});
|
||||
}
|
||||
return main.fullCalendar("refetchEvents");
|
||||
};
|
||||
|
||||
scope.filterEvents = function() {
|
||||
RecordManager.filter();
|
||||
};
|
||||
|
||||
scope.pagerText = function() {
|
||||
return main.fullCalendar("getView").title;
|
||||
};
|
||||
|
||||
scope.isMode = function(name) {
|
||||
return mode === name;
|
||||
};
|
||||
|
||||
scope.onMode = function(name) {
|
||||
mode = name;
|
||||
if (name === "week") {
|
||||
name = scope.isAgenda() ? "agendaWeek" : "basicWeek";
|
||||
}
|
||||
if (name === "day") {
|
||||
name = scope.isAgenda() ? "agendaDay" : "basicDay";
|
||||
}
|
||||
main.fullCalendar("changeView", name);
|
||||
};
|
||||
|
||||
scope.onRefresh = function () {
|
||||
scope.refresh();
|
||||
};
|
||||
|
||||
scope.onNext = function() {
|
||||
main.fullCalendar('next');
|
||||
};
|
||||
|
||||
scope.onPrev = function() {
|
||||
main.fullCalendar('prev');
|
||||
};
|
||||
|
||||
scope.onToday = function() {
|
||||
main.fullCalendar('today');
|
||||
};
|
||||
|
||||
scope.onChangeHandler = null;
|
||||
if (schema.onChange) {
|
||||
scope.onChangeHandler = ActionService.handler(scope, element, {
|
||||
action: schema.onChange
|
||||
});
|
||||
}
|
||||
|
||||
function adjustSize() {
|
||||
if (main.is(':hidden')) {
|
||||
return;
|
||||
}
|
||||
hideBubble();
|
||||
main.fullCalendar('render');
|
||||
main.fullCalendar('option', 'height', element.height());
|
||||
legend.css("max-height", legend.parent().height() - mini.height()
|
||||
- (parseInt(legend.css('marginTop')) || 0)
|
||||
- (parseInt(legend.css('marginBottom')) || 0));
|
||||
}
|
||||
|
||||
scope.$onAdjust(adjustSize, 100);
|
||||
|
||||
scope.$callWhen(function () {
|
||||
return main.is(':visible');
|
||||
}, function() {
|
||||
element.parents('.view-container:first').css('overflow', 'inherit');
|
||||
scope.onMode(mode);
|
||||
adjustSize();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return {
|
||||
link: function(scope, element, attrs, controller) {
|
||||
scope._viewPromise.then(function(){
|
||||
link(scope, element, attrs, controller);
|
||||
});
|
||||
},
|
||||
replace: true,
|
||||
template:
|
||||
'<div>'+
|
||||
'<div class="calendar-main" ui-attach-scroll=".fc-scroller"></div>'+
|
||||
'<div class="calendar-side">'+
|
||||
'<div class="calendar-mini"></div>'+
|
||||
'<form class="form calendar-legend">'+
|
||||
'<label class="checkbox" ng-repeat="color in getColors()" style="color: {{color.color.bc}}">'+
|
||||
'<input type="checkbox" ng-click="filterEvents()" ng-model="color.checked"> {{color.title}}</label>'+
|
||||
'</div>'+
|
||||
'</div>'+
|
||||
'</div>'
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user