getting closer to 1.4
This commit is contained in:
parent
8e0312a750
commit
20208deb66
8 changed files with 870 additions and 786 deletions
802
src/agenda.js
802
src/agenda.js
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,6 @@
|
||||||
|
|
||||||
.fc .fc-axis {
|
.fc .fc-axis {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 1.6em;
|
|
||||||
padding: 0 4px 0 0;
|
padding: 0 4px 0 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -28,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-agenda-head tr.fc-all-day th {
|
.fc-agenda-head tr.fc-all-day th {
|
||||||
height: 2em;
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-agenda tr.fc-first th,
|
.fc-agenda tr.fc-first th,
|
||||||
|
@ -47,6 +46,10 @@
|
||||||
.fc .fc-agenda-body td {
|
.fc .fc-agenda-body td {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc .fc-agenda-body td div {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,15 +60,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.fc .fc-divider th {
|
.fc .fc-divider div {
|
||||||
height: 3px;
|
font-size: 1px; /* for IE6/7 */
|
||||||
border-bottom-width: 1px;
|
height: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.fc .fc-divider .fc-state-default {
|
.fc .fc-divider .fc-state-default {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.fc-agenda-head tr.fc-last th {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.fc-agenda .fc-day-content {
|
||||||
|
padding: 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-head .fc-day-content {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,6 +227,7 @@
|
||||||
_white-space: normal;
|
_white-space: normal;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event-vert span.fc-event-title {
|
.fc-event-vert span.fc-event-title {
|
||||||
|
|
|
@ -224,6 +224,7 @@ table.fc-header {
|
||||||
------------------------------------------------------------------------*/
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.fc-event,
|
.fc-event,
|
||||||
|
.fc-agenda .fc-event-time,
|
||||||
.fc-event a {
|
.fc-event a {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #36c; /* default BORDER color (probably the same as background-color) */
|
border-color: #36c; /* default BORDER color (probably the same as background-color) */
|
||||||
|
|
103
src/grid.js
103
src/grid.js
|
@ -18,7 +18,7 @@ views.month = function(element, options) {
|
||||||
this.title = formatDates(
|
this.title = formatDates(
|
||||||
start,
|
start,
|
||||||
addDays(cloneDate(this.end = addMonths(cloneDate(start), 1)), -1),
|
addDays(cloneDate(this.end = addMonths(cloneDate(start), 1)), -1),
|
||||||
strProp(options.titleFormat, 'month'),
|
this.option('titleFormat'),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
addDays(this.visStart = cloneDate(start), -((start.getDay() - options.firstDay + 7) % 7));
|
addDays(this.visStart = cloneDate(start), -((start.getDay() - options.firstDay + 7) % 7));
|
||||||
|
@ -28,7 +28,7 @@ views.month = function(element, options) {
|
||||||
addDays(this.visEnd, (6 - rowCnt) * 7);
|
addDays(this.visEnd, (6 - rowCnt) * 7);
|
||||||
rowCnt = 6;
|
rowCnt = 6;
|
||||||
}
|
}
|
||||||
this.renderGrid(rowCnt, 7, strProp(options.columnFormat, 'month'), true, fetchEvents);
|
this.renderGrid(rowCnt, 7, this.option('columnFormat'), true, fetchEvents);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,10 @@ views.basicWeek = function(element, options) {
|
||||||
this.title = formatDates(
|
this.title = formatDates(
|
||||||
this.start = this.visStart = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7)),
|
this.start = this.visStart = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7)),
|
||||||
addDays(cloneDate(this.end = this.visEnd = addDays(cloneDate(this.start), 7)), -1),
|
addDays(cloneDate(this.end = this.visEnd = addDays(cloneDate(this.start), 7)), -1),
|
||||||
strProp(options.titleFormat, 'week'),
|
this.option('titleFormat'),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
this.renderGrid(1, 7, strProp(options.columnFormat, 'week'), false, fetchEvents);
|
this.renderGrid(1, 7, this.option('columnFormat'), false, fetchEvents);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -56,10 +56,10 @@ views.basicDay = function(element, options) {
|
||||||
if (delta) {
|
if (delta) {
|
||||||
addDays(date, delta);
|
addDays(date, delta);
|
||||||
}
|
}
|
||||||
this.title = formatDate(date, strProp(options.titleFormat, 'day'), options);
|
this.title = formatDate(date, this.option('titleFormat'), options);
|
||||||
this.start = this.visStart = cloneDate(date, true);
|
this.start = this.visStart = cloneDate(date, true);
|
||||||
this.end = this.visEnd = addDays(cloneDate(this.start), 1);
|
this.end = this.visEnd = addDays(cloneDate(this.start), 1);
|
||||||
this.renderGrid(1, 1, strProp(options.columnFormat, 'day'), false, fetchEvents);
|
this.renderGrid(1, 1, this.option('columnFormat'), false, fetchEvents);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -326,10 +326,11 @@ function Grid(element, options, methods) {
|
||||||
|
|
||||||
|
|
||||||
function compileSegs(events) {
|
function compileSegs(events) {
|
||||||
var d1 = cloneDate(view.visStart);
|
var d1 = cloneDate(view.visStart),
|
||||||
var d2 = addDays(cloneDate(d1), colCnt);
|
d2 = addDays(cloneDate(d1), colCnt),
|
||||||
var rows = [];
|
rows = [],
|
||||||
for (var i=0; i<rowCnt; i++) {
|
i=0;
|
||||||
|
for (; i<rowCnt; i++) {
|
||||||
rows.push(stackSegs(view.sliceSegs(events, d1, d2)));
|
rows.push(stackSegs(view.sliceSegs(events, d1, d2)));
|
||||||
addDays(d1, 7);
|
addDays(d1, 7);
|
||||||
addDays(d2, 7);
|
addDays(d2, 7);
|
||||||
|
@ -350,7 +351,7 @@ function Grid(element, options, methods) {
|
||||||
event,
|
event,
|
||||||
eventClasses,
|
eventClasses,
|
||||||
startElm, endElm,
|
startElm, endElm,
|
||||||
left1, left2,
|
left, right,
|
||||||
eventElement, eventAnchor,
|
eventElement, eventAnchor,
|
||||||
triggerRes;
|
triggerRes;
|
||||||
for (i=0; i<len; i++) {
|
for (i=0; i<len; i++) {
|
||||||
|
@ -387,14 +388,14 @@ function Grid(element, options, methods) {
|
||||||
}
|
}
|
||||||
eventClasses.push('fc-event', 'fc-event-hori');
|
eventClasses.push('fc-event', 'fc-event-hori');
|
||||||
startElm = seg.isStart ?
|
startElm = seg.isStart ?
|
||||||
tr.find('td:eq('+((seg.start.getDay()-firstDay+colCnt)%colCnt)+') div.fc-day-content div') :
|
tr.find('td:eq('+((seg.start.getDay()-firstDay+colCnt)%colCnt)+') div div') :
|
||||||
tbody;
|
tbody;
|
||||||
endElm = seg.isEnd ?
|
endElm = seg.isEnd ?
|
||||||
tr.find('td:eq('+((seg.end.getDay()-firstDay+colCnt-1)%colCnt)+') div.fc-day-content div') :
|
tr.find('td:eq('+((seg.end.getDay()-firstDay+colCnt-1)%colCnt)+') div div') :
|
||||||
tbody;
|
tbody;
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
left1 = endElm.position().left;
|
left = endElm.position().left;
|
||||||
left2 = startElm.position().left + startElm.width();
|
right = startElm.position().left + startElm.width();
|
||||||
if (seg.isStart) {
|
if (seg.isStart) {
|
||||||
eventClasses.push('fc-corner-right');
|
eventClasses.push('fc-corner-right');
|
||||||
}
|
}
|
||||||
|
@ -402,8 +403,8 @@ function Grid(element, options, methods) {
|
||||||
eventClasses.push('fc-corner-left');
|
eventClasses.push('fc-corner-left');
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
left1 = startElm.position().left;
|
left = startElm.position().left;
|
||||||
left2 = endElm.position().left + endElm.width();
|
right = endElm.position().left + endElm.width();
|
||||||
if (seg.isStart) {
|
if (seg.isStart) {
|
||||||
eventClasses.push('fc-corner-left');
|
eventClasses.push('fc-corner-left');
|
||||||
}
|
}
|
||||||
|
@ -415,7 +416,7 @@ function Grid(element, options, methods) {
|
||||||
.append(eventAnchor = $("<a/>")
|
.append(eventAnchor = $("<a/>")
|
||||||
.append(event.allDay || !seg.isStart ? null :
|
.append(event.allDay || !seg.isStart ? null :
|
||||||
$("<span class='fc-event-time'/>")
|
$("<span class='fc-event-time'/>")
|
||||||
.html(formatDates(event.start, event.end, options.timeFormat, options)))
|
.html(formatDates(event.start, event.end, view.option('timeFormat'), options)))
|
||||||
.append($("<span class='fc-event-title'/>")
|
.append($("<span class='fc-event-title'/>")
|
||||||
.text(event.title)));
|
.text(event.title)));
|
||||||
if (event.url) {
|
if (event.url) {
|
||||||
|
@ -430,23 +431,23 @@ function Grid(element, options, methods) {
|
||||||
.css({
|
.css({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: top,
|
top: top,
|
||||||
left: left1 + (rtlLeftDiff||0),
|
left: left + (rtlLeftDiff||0),
|
||||||
zIndex: 2
|
zIndex: 8
|
||||||
})
|
})
|
||||||
.appendTo(element);
|
.appendTo(element);
|
||||||
setOuterWidth(eventElement, left2-left1, true);
|
setOuterWidth(eventElement, right-left, true);
|
||||||
if (rtl && rtlLeftDiff == undefined) {
|
if (rtl && rtlLeftDiff == undefined) {
|
||||||
// bug in IE6 where offsets are miscalculated with direction:rtl
|
// bug in IE6 where offsets are miscalculated with direction:rtl
|
||||||
rtlLeftDiff = left1 - eventElement.position().left;
|
rtlLeftDiff = left - eventElement.position().left;
|
||||||
if (rtlLeftDiff) {
|
if (rtlLeftDiff) {
|
||||||
eventElement.css('left', left1 + rtlLeftDiff);
|
eventElement.css('left', left + rtlLeftDiff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventElementHandlers(event, eventElement);
|
eventElementHandlers(event, eventElement);
|
||||||
if (event.editable || event.editable == undefined && options.editable) {
|
if (event.editable || event.editable == undefined && options.editable) {
|
||||||
draggableEvent(event, eventElement);
|
draggableEvent(event, eventElement);
|
||||||
if (seg.isEnd) {
|
if (seg.isEnd) {
|
||||||
resizableEvent(event, eventElement);
|
view.resizableDayEvent(event, eventElement, colWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.reportEventElement(event, eventElement);
|
view.reportEventElement(event, eventElement);
|
||||||
|
@ -479,7 +480,7 @@ function Grid(element, options, methods) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Draggable
|
/* Event Dragging
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -487,9 +488,9 @@ function Grid(element, options, methods) {
|
||||||
if (!options.disableDragging && eventElement.draggable) {
|
if (!options.disableDragging && eventElement.draggable) {
|
||||||
var matrix;
|
var matrix;
|
||||||
eventElement.draggable({
|
eventElement.draggable({
|
||||||
zIndex: 3,
|
zIndex: 9,
|
||||||
delay: 50,
|
delay: 50,
|
||||||
opacity: options.dragOpacity,
|
opacity: view.option('dragOpacity'),
|
||||||
revertDuration: options.dragRevertDuration,
|
revertDuration: options.dragRevertDuration,
|
||||||
start: function(ev, ui) {
|
start: function(ev, ui) {
|
||||||
matrix = new HoverMatrix(function(cell) {
|
matrix = new HoverMatrix(function(cell) {
|
||||||
|
@ -518,20 +519,17 @@ function Grid(element, options, methods) {
|
||||||
matrix.mouse(ev.pageX, ev.pageY);
|
matrix.mouse(ev.pageX, ev.pageY);
|
||||||
},
|
},
|
||||||
stop: function(ev, ui) {
|
stop: function(ev, ui) {
|
||||||
|
if ($.browser.msie) {
|
||||||
|
eventElement.css('filter', ''); // clear IE opacity side-effects
|
||||||
|
}
|
||||||
view.hideOverlay();
|
view.hideOverlay();
|
||||||
view.trigger('eventDragStop', eventElement, event, ev, ui);
|
view.trigger('eventDragStop', eventElement, event, ev, ui);
|
||||||
var cell = matrix.cell;
|
var cell = matrix.cell;
|
||||||
if (!cell || !cell.rowDelta && !cell.colDelta) {
|
if (!cell || !cell.rowDelta && !cell.colDelta) {
|
||||||
view.showEvents(event, eventElement);
|
view.showEvents(event, eventElement);
|
||||||
}else{
|
}else{
|
||||||
var dayDelta = cell.rowDelta*7 + cell.colDelta*dis;
|
|
||||||
view.moveEvent(event, dayDelta);
|
|
||||||
view.trigger('eventDrop', this, event, dayDelta, 0, function() {
|
|
||||||
view.moveEvent(event, -dayDelta);
|
|
||||||
rerenderEvents();
|
|
||||||
}, ev, ui);
|
|
||||||
eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
|
eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
|
||||||
rerenderEvents();
|
view.eventDrop(this, event, cell.rowDelta*7+cell.colDelta*dis, 0, event.allDay, ev, ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -539,42 +537,7 @@ function Grid(element, options, methods) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// event resizing w/ 'view' methods...
|
||||||
/* Resizable
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
|
|
||||||
function resizableEvent(event, eventElement) {
|
|
||||||
if (!options.disableResizing && eventElement.resizable) {
|
|
||||||
eventElement.resizable({
|
|
||||||
handles: rtl ? 'w' : 'e',
|
|
||||||
grid: colWidth,
|
|
||||||
minWidth: colWidth/2, // need this or else IE throws errors when too small
|
|
||||||
containment: element,
|
|
||||||
start: function(ev, ui) {
|
|
||||||
eventElement.css('z-index', 3);
|
|
||||||
view.hideEvents(event, eventElement);
|
|
||||||
view.trigger('eventResizeStart', this, event, ev, ui);
|
|
||||||
},
|
|
||||||
stop: function(ev, ui) {
|
|
||||||
view.trigger('eventResizeStop', this, event, ev, ui);
|
|
||||||
// ui.size.width wasn't working with grid correctly, use .width()
|
|
||||||
var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth);
|
|
||||||
if (dayDelta) {
|
|
||||||
view.resizeEvent(event, dayDelta);
|
|
||||||
view.trigger('eventResize', this, event, dayDelta, 0, function() {
|
|
||||||
view.resizeEvent(event, -dayDelta);
|
|
||||||
rerenderEvents();
|
|
||||||
}, ev, ui);
|
|
||||||
rerenderEvents();
|
|
||||||
}else{
|
|
||||||
view.showEvents(event, eventElement);
|
|
||||||
}
|
|
||||||
eventElement.css('z-index', 2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
64
src/main.js
64
src/main.js
|
@ -30,7 +30,6 @@ var defaults = {
|
||||||
cacheParam: '_',
|
cacheParam: '_',
|
||||||
|
|
||||||
// time formats
|
// time formats
|
||||||
timeFormat: 'h(:mm)t', // for events
|
|
||||||
titleFormat: {
|
titleFormat: {
|
||||||
month: 'MMMM yyyy',
|
month: 'MMMM yyyy',
|
||||||
week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
|
week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
|
||||||
|
@ -41,6 +40,9 @@ var defaults = {
|
||||||
week: 'ddd M/d',
|
week: 'ddd M/d',
|
||||||
day: 'dddd M/d'
|
day: 'dddd M/d'
|
||||||
},
|
},
|
||||||
|
timeFormat: { // for event elements
|
||||||
|
'': 'h(:mm)t' // default
|
||||||
|
},
|
||||||
|
|
||||||
// locale
|
// locale
|
||||||
isRTL: false,
|
isRTL: false,
|
||||||
|
@ -166,7 +168,7 @@ $.fn.fullCalendar = function(options) {
|
||||||
|
|
||||||
function changeView(v) {
|
function changeView(v) {
|
||||||
if (v != viewName) {
|
if (v != viewName) {
|
||||||
lockContentSize();
|
fixContentSize();
|
||||||
if (view) {
|
if (view) {
|
||||||
if (view.eventsChanged) {
|
if (view.eventsChanged) {
|
||||||
eventsDirtyExcept(view);
|
eventsDirtyExcept(view);
|
||||||
|
@ -188,14 +190,14 @@ $.fn.fullCalendar = function(options) {
|
||||||
}
|
}
|
||||||
view.name = viewName = v;
|
view.name = viewName = v;
|
||||||
render();
|
render();
|
||||||
unlockContentSize();
|
unfixContentSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(inc) {
|
function render(inc) {
|
||||||
if (_element.offsetWidth !== 0) { // visible on the screen
|
if (_element.offsetWidth !== 0) { // visible on the screen
|
||||||
if (inc || !view.date || +view.date != +date) { // !view.date means it hasn't been rendered yet
|
if (inc || !view.date || +view.date != +date) { // !view.date means it hasn't been rendered yet
|
||||||
ignoreWindowResizes = true;
|
fixContentSize();
|
||||||
view.render(date, inc || 0, function(callback) {
|
view.render(date, inc || 0, function(callback) {
|
||||||
// dont refetch if new view contains the same events (or a subset)
|
// dont refetch if new view contains the same events (or a subset)
|
||||||
if (!eventStart || view.visStart < eventStart || view.visEnd > eventEnd) {
|
if (!eventStart || view.visStart < eventStart || view.visEnd > eventEnd) {
|
||||||
|
@ -204,7 +206,7 @@ $.fn.fullCalendar = function(options) {
|
||||||
callback(events); // no refetching
|
callback(events); // no refetching
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ignoreWindowResizes = false;
|
unfixContentSize();
|
||||||
view.date = cloneDate(date);
|
view.date = cloneDate(date);
|
||||||
if (header) {
|
if (header) {
|
||||||
// enable/disable 'today' button
|
// enable/disable 'today' button
|
||||||
|
@ -620,35 +622,47 @@ $.fn.fullCalendar = function(options) {
|
||||||
/* Resizing
|
/* Resizing
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
function lockContentSize() {
|
|
||||||
content.css({
|
|
||||||
overflow: 'hidden',
|
|
||||||
height: Math.round(content.width() / options.aspectRatio)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function unlockContentSize() {
|
|
||||||
content.css({
|
|
||||||
overflow: '',
|
|
||||||
height: ($.browser.msie && $.browser.version == '6.0') ? 1 : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var elementWidth,
|
var elementWidth,
|
||||||
ignoreWindowResizes = false,
|
contentSizeFixed = false,
|
||||||
resizeCnt = 0;
|
resizeCnt = 0;
|
||||||
|
|
||||||
|
function fixContentSize() {
|
||||||
|
if (!contentSizeFixed) {
|
||||||
|
contentSizeFixed = true;
|
||||||
|
content.css({
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: Math.round(content.width() / options.aspectRatio)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unfixContentSize() {
|
||||||
|
if (contentSizeFixed) {
|
||||||
|
content.css({
|
||||||
|
overflow: 'visible',
|
||||||
|
height: ''
|
||||||
|
});
|
||||||
|
if ($.browser.msie && ($.browser.version=='6.0' || $.browser.version=='7.0')) {
|
||||||
|
// in IE6/7 the inside of the content div was invisible
|
||||||
|
// bizarre hack to get this work... need both lines
|
||||||
|
content[0].clientHeight;
|
||||||
|
content.hide().show();
|
||||||
|
}
|
||||||
|
contentSizeFixed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(window).resize(function() {
|
$(window).resize(function() {
|
||||||
if (!ignoreWindowResizes && view.date) { // view.date means the view has been rendered
|
if (!contentSizeFixed && view.date) { // view.date means the view has been rendered
|
||||||
var rcnt = ++resizeCnt; // add a delay
|
var rcnt = ++resizeCnt; // add a delay
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (rcnt == resizeCnt && !ignoreWindowResizes) {
|
if (rcnt == resizeCnt && !contentSizeFixed) {
|
||||||
var newWidth = element.width();
|
var newWidth = element.width();
|
||||||
if (newWidth != elementWidth) {
|
if (newWidth != elementWidth) {
|
||||||
elementWidth = newWidth;
|
elementWidth = newWidth;
|
||||||
lockContentSize();
|
fixContentSize();
|
||||||
view.updateSize();
|
view.updateSize();
|
||||||
unlockContentSize();
|
unfixContentSize();
|
||||||
view.rerenderEvents(true);
|
view.rerenderEvents(true);
|
||||||
sizesDirtyExcept(view);
|
sizesDirtyExcept(view);
|
||||||
view.trigger('windowResize', _element);
|
view.trigger('windowResize', _element);
|
||||||
|
@ -686,7 +700,7 @@ function normalizeEvent(event, options) {
|
||||||
}
|
}
|
||||||
event._start = cloneDate(event.start = parseDate(event.start));
|
event._start = cloneDate(event.start = parseDate(event.start));
|
||||||
event.end = parseDate(event.end);
|
event.end = parseDate(event.end);
|
||||||
if (event.end && event.end < event.start) {
|
if (event.end && event.end <= event.start) {
|
||||||
event.end = null;
|
event.end = null;
|
||||||
}
|
}
|
||||||
event._end = event.end ? cloneDate(event.end) : null;
|
event._end = event.end ? cloneDate(event.end) : null;
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
var DAY_MS = 86400000,
|
var DAY_MS = 86400000,
|
||||||
HOUR_MS = 3600000;
|
HOUR_MS = 3600000,
|
||||||
|
MINUTE_MS = 60000;
|
||||||
|
|
||||||
function addYears(d, n, keepTime) {
|
function addYears(d, n, keepTime) {
|
||||||
d.setFullYear(d.getFullYear() + n);
|
d.setFullYear(d.getFullYear() + n);
|
||||||
|
@ -340,7 +341,3 @@ function zeroPad(n) {
|
||||||
return (n < 10 ? '0' : '') + n;
|
return (n < 10 ? '0' : '') + n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function strProp(s, prop) {
|
|
||||||
return typeof s == 'string' ? s : s[prop];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
610
src/view.js
610
src/view.js
|
@ -1,264 +1,346 @@
|
||||||
|
|
||||||
/* Methods & Utilities for All Views
|
/* Methods & Utilities for All Views
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
var viewMethods = {
|
var viewMethods = {
|
||||||
|
|
||||||
/*
|
// TODO: maybe change the 'vis' variables to 'excl'
|
||||||
* Objects inheriting these methods must implement the following properties/methods:
|
|
||||||
* - title
|
/*
|
||||||
* - start
|
* Objects inheriting these methods must implement the following properties/methods:
|
||||||
* - end
|
* - title
|
||||||
* - visStart
|
* - start
|
||||||
* - visEnd
|
* - end
|
||||||
* - defaultEventEnd(event)
|
* - visStart
|
||||||
* - visEventEnd(event)
|
* - visEnd
|
||||||
* - render(events)
|
* - defaultEventEnd(event)
|
||||||
* - rerenderEvents()
|
* - visEventEnd(event)
|
||||||
*
|
* - render(events)
|
||||||
*
|
* - rerenderEvents()
|
||||||
* z-index reservations:
|
*
|
||||||
* 1. day-overlay
|
*
|
||||||
* 2. events
|
* z-index reservations:
|
||||||
* 3. dragging/resizing events
|
* 3 - day-overlay
|
||||||
*
|
* 8 - events
|
||||||
*/
|
* 9 - dragging/resizing events
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
init: function(element, options) {
|
|
||||||
this.element = element;
|
|
||||||
this.options = options;
|
init: function(element, options) {
|
||||||
this.cachedEvents = [];
|
this.element = element;
|
||||||
this.eventsByID = {};
|
this.options = options;
|
||||||
this.eventElements = [];
|
this.cachedEvents = [];
|
||||||
this.eventElementsByID = {};
|
this.eventsByID = {};
|
||||||
},
|
this.eventElements = [];
|
||||||
|
this.eventElementsByID = {};
|
||||||
|
},
|
||||||
|
|
||||||
// triggers an event handler, always append view as last arg
|
|
||||||
|
|
||||||
trigger: function(name, thisObj) {
|
// triggers an event handler, always append view as last arg
|
||||||
if (this.options[name]) {
|
|
||||||
return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this]));
|
trigger: function(name, thisObj) {
|
||||||
}
|
if (this.options[name]) {
|
||||||
},
|
return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// returns a Date object for an event's end
|
|
||||||
|
|
||||||
eventEnd: function(event) {
|
// returns a Date object for an event's end
|
||||||
return event.end || this.defaultEventEnd(event);
|
|
||||||
},
|
eventEnd: function(event) {
|
||||||
|
return event.end ? cloneDate(event.end) : this.defaultEventEnd(event); // TODO: make sure always using copies
|
||||||
|
},
|
||||||
|
|
||||||
// report when view receives new events
|
|
||||||
|
|
||||||
reportEvents: function(events) { // events are already normalized at this point
|
// report when view receives new events
|
||||||
var i, len=events.length, event,
|
|
||||||
eventsByID = this.eventsByID = {},
|
reportEvents: function(events) { // events are already normalized at this point
|
||||||
cachedEvents = this.cachedEvents = [];
|
var i, len=events.length, event,
|
||||||
for (i=0; i<len; i++) {
|
eventsByID = this.eventsByID = {},
|
||||||
event = events[i];
|
cachedEvents = this.cachedEvents = [];
|
||||||
if (eventsByID[event._id]) {
|
for (i=0; i<len; i++) {
|
||||||
eventsByID[event._id].push(event);
|
event = events[i];
|
||||||
}else{
|
if (eventsByID[event._id]) {
|
||||||
eventsByID[event._id] = [event];
|
eventsByID[event._id].push(event);
|
||||||
}
|
}else{
|
||||||
cachedEvents.push(event);
|
eventsByID[event._id] = [event];
|
||||||
}
|
}
|
||||||
},
|
cachedEvents.push(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// report when view creates an element for an event
|
|
||||||
|
|
||||||
reportEventElement: function(event, element) {
|
// report when view creates an element for an event
|
||||||
this.eventElements.push(element);
|
|
||||||
var eventElementsByID = this.eventElementsByID;
|
reportEventElement: function(event, element) {
|
||||||
if (eventElementsByID[event._id]) {
|
this.eventElements.push(element);
|
||||||
eventElementsByID[event._id].push(element);
|
var eventElementsByID = this.eventElementsByID;
|
||||||
}else{
|
if (eventElementsByID[event._id]) {
|
||||||
eventElementsByID[event._id] = [element];
|
eventElementsByID[event._id].push(element);
|
||||||
}
|
}else{
|
||||||
},
|
eventElementsByID[event._id] = [element];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// event element manipulation
|
|
||||||
|
|
||||||
clearEvents: function() { // only remove ELEMENTS
|
// event element manipulation
|
||||||
$.each(this.eventElements, function() {
|
|
||||||
this.remove();
|
clearEvents: function() { // only remove ELEMENTS
|
||||||
});
|
$.each(this.eventElements, function() {
|
||||||
this.eventElements = [];
|
this.remove();
|
||||||
this.eventElementsByID = {};
|
});
|
||||||
},
|
this.eventElements = [];
|
||||||
|
this.eventElementsByID = {};
|
||||||
showEvents: function(event, exceptElement) {
|
},
|
||||||
this._eee(event, exceptElement, 'show');
|
|
||||||
},
|
showEvents: function(event, exceptElement) {
|
||||||
|
this._eee(event, exceptElement, 'show');
|
||||||
hideEvents: function(event, exceptElement) {
|
},
|
||||||
this._eee(event, exceptElement, 'hide');
|
|
||||||
},
|
hideEvents: function(event, exceptElement) {
|
||||||
|
this._eee(event, exceptElement, 'hide');
|
||||||
_eee: function(event, exceptElement, funcName) { // event-element-each
|
},
|
||||||
var elements = this.eventElementsByID[event._id],
|
|
||||||
i, len = elements.length;
|
_eee: function(event, exceptElement, funcName) { // event-element-each
|
||||||
for (i=0; i<len; i++) {
|
var elements = this.eventElementsByID[event._id],
|
||||||
if (elements[i] != exceptElement) {
|
i, len = elements.length;
|
||||||
elements[i][funcName]();
|
for (i=0; i<len; i++) {
|
||||||
}
|
if (elements[i] != exceptElement) {
|
||||||
}
|
elements[i][funcName]();
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// event modification reporting
|
|
||||||
|
|
||||||
moveEvent: function(event, days, minutes) { // actually DO the date changes
|
// event modification reporting
|
||||||
minutes = minutes || 0;
|
|
||||||
var events = this.eventsByID[event._id],
|
eventDrop: function(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
|
||||||
i, len=events.length, e;
|
var view = this,
|
||||||
for (i=0; i<len; i++) {
|
oldAllDay = event.allDay;
|
||||||
e = events[i];
|
view.moveEvents(view.eventsByID[event._id], dayDelta, minuteDelta, allDay);
|
||||||
e.allDay = event.allDay;
|
view.trigger('eventDrop', e, event, dayDelta, minuteDelta, allDay, function() { // TODO: change docs
|
||||||
addMinutes(addDays(e.start, days, true), minutes);
|
// TODO: investigate cases where this inverse technique might not work
|
||||||
if (e.end) {
|
view.moveEvents(view.eventsByID[event._id], -dayDelta, -minuteDelta, oldAllDay);
|
||||||
e.end = addMinutes(addDays(e.end, days, true), minutes);
|
view.rerenderEvents();
|
||||||
}
|
}, ev, ui);
|
||||||
normalizeEvent(e, this.options);
|
view.eventsChanged = true;
|
||||||
}
|
view.rerenderEvents();
|
||||||
this.eventsChanged = true;
|
},
|
||||||
},
|
|
||||||
|
eventResize: function(e, event, dayDelta, minuteDelta, ev, ui) {
|
||||||
resizeEvent: function(event, days, minutes) { // actually DO the date changes
|
var view = this;
|
||||||
minutes = minutes || 0;
|
view.elongateEvents(view.eventsByID[event._id], dayDelta, minuteDelta);
|
||||||
var events = this.eventsByID[event._id],
|
view.trigger('eventResize', e, event, dayDelta, minuteDelta, function() {
|
||||||
i, len=events.length, e;
|
// TODO: investigate cases where this inverse technique might not work
|
||||||
for (i=0; i<len; i++) {
|
view.elongateEvents(view.eventsByID[event._id], -dayDelta, -minuteDelta);
|
||||||
e = events[i];
|
view.rerenderEvents();
|
||||||
e.end = addMinutes(addDays(this.eventEnd(e), days, true), minutes);
|
}, ev, ui);
|
||||||
normalizeEvent(e, this.options);
|
view.eventsChanged = true;
|
||||||
}
|
view.rerenderEvents();
|
||||||
this.eventsChanged = true;
|
},
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
// event modification
|
||||||
// semi-transparent overlay (while dragging)
|
|
||||||
|
moveEvents: function(events, dayDelta, minuteDelta, allDay) {
|
||||||
showOverlay: function(props) {
|
minuteDelta = minuteDelta || 0;
|
||||||
if (!this.dayOverlay) {
|
for (var e, len=events.length, i=0; i<len; i++) {
|
||||||
this.dayOverlay = $("<div class='fc-cell-overlay' style='position:absolute;z-index:1;display:none'/>")
|
e = events[i];
|
||||||
.appendTo(this.element);
|
if (allDay != undefined) {
|
||||||
}
|
e.allDay = allDay;
|
||||||
var o = this.element.offset();
|
}
|
||||||
this.dayOverlay
|
addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
|
||||||
.css({
|
if (e.end) {
|
||||||
top: props.top - o.top,
|
e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
|
||||||
left: props.left - o.left,
|
}
|
||||||
width: props.width,
|
normalizeEvent(e, this.options);
|
||||||
height: props.height
|
}
|
||||||
})
|
},
|
||||||
.show();
|
|
||||||
},
|
elongateEvents: function(events, dayDelta, minuteDelta) {
|
||||||
|
minuteDelta = minuteDelta || 0;
|
||||||
hideOverlay: function() {
|
for (var e, len=events.length, i=0; i<len; i++) {
|
||||||
if (this.dayOverlay) {
|
e = events[i];
|
||||||
this.dayOverlay.hide();
|
e.end = addMinutes(addDays(this.eventEnd(e), dayDelta, true), minuteDelta);
|
||||||
}
|
normalizeEvent(e, this.options);
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
// event rendering utilities
|
|
||||||
|
// semi-transparent overlay (while dragging)
|
||||||
sliceSegs: function(events, start, end) {
|
|
||||||
var segs = [],
|
showOverlay: function(props) {
|
||||||
i, len=events.length, event,
|
if (!this.dayOverlay) {
|
||||||
eventStart, eventEnd,
|
this.dayOverlay = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3;display:none'/>")
|
||||||
segStart, segEnd,
|
.appendTo(this.element);
|
||||||
isStart, isEnd;
|
}
|
||||||
for (i=0; i<len; i++) {
|
var o = this.element.offset();
|
||||||
event = events[i];
|
this.dayOverlay
|
||||||
eventStart = event.start;
|
.css({
|
||||||
eventEnd = this.visEventEnd(event);
|
top: props.top - o.top,
|
||||||
if (eventEnd > start && eventStart < end) {
|
left: props.left - o.left,
|
||||||
if (eventStart < start) {
|
width: props.width,
|
||||||
segStart = cloneDate(start);
|
height: props.height
|
||||||
isStart = false;
|
})
|
||||||
}else{
|
.show();
|
||||||
segStart = eventStart;
|
},
|
||||||
isStart = true;
|
|
||||||
}
|
hideOverlay: function() {
|
||||||
if (eventEnd > end) {
|
if (this.dayOverlay) {
|
||||||
segEnd = cloneDate(end);
|
this.dayOverlay.hide();
|
||||||
isEnd = false;
|
}
|
||||||
}else{
|
},
|
||||||
segEnd = eventEnd;
|
|
||||||
isEnd = true;
|
|
||||||
}
|
|
||||||
segs.push({
|
// common horizontal event resizing
|
||||||
event: event,
|
|
||||||
start: segStart,
|
resizableDayEvent: function(event, eventElement, colWidth) {
|
||||||
end: segEnd,
|
var view = this;
|
||||||
isStart: isStart,
|
if (!view.options.disableResizing && eventElement.resizable) {
|
||||||
isEnd: isEnd,
|
eventElement.resizable({
|
||||||
msLength: segEnd - segStart
|
handles: view.options.isRTL ? 'w' : 'e',
|
||||||
});
|
grid: colWidth,
|
||||||
}
|
minWidth: colWidth/2, // need this or else IE throws errors when too small
|
||||||
}
|
containment: view.element,
|
||||||
return segs.sort(segCmp);
|
start: function(ev, ui) {
|
||||||
}
|
eventElement.css('z-index', 9);
|
||||||
|
view.hideEvents(event, eventElement);
|
||||||
};
|
view.trigger('eventResizeStart', this, event, ev, ui);
|
||||||
|
},
|
||||||
|
stop: function(ev, ui) {
|
||||||
// more event rendering utilities
|
view.trigger('eventResizeStop', this, event, ev, ui);
|
||||||
|
// ui.size.width wasn't working with grid correctly, use .width()
|
||||||
function stackSegs(segs) {
|
var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth);
|
||||||
var levels = [],
|
if (dayDelta) {
|
||||||
i, len = segs.length, seg,
|
view.eventResize(this, event, dayDelta, 0, ev, ui);
|
||||||
j, collide, k;
|
}else{
|
||||||
for (i=0; i<len; i++) {
|
eventElement.css('z-index', 8);
|
||||||
seg = segs[i];
|
view.showEvents(event, eventElement);
|
||||||
j = 0; // the level index where seg should belong
|
}
|
||||||
while (true) {
|
}
|
||||||
collide = false;
|
});
|
||||||
if (levels[j]) {
|
}
|
||||||
for (k=0; k<levels[j].length; k++) {
|
},
|
||||||
if (segsCollide(levels[j][k], seg)) {
|
|
||||||
collide = true;
|
|
||||||
break;
|
|
||||||
}
|
// get a property from the 'options' object, using smart view naming
|
||||||
}
|
|
||||||
}
|
option: function(name) {
|
||||||
if (collide) {
|
var v = this.options[name];
|
||||||
j++;
|
if (typeof v == 'object') {
|
||||||
}else{
|
var parts = this.name.split(/(?=[A-Z])/),
|
||||||
break;
|
i=parts.length-1, res;
|
||||||
}
|
for (; i>=0; i--) {
|
||||||
}
|
res = v[parts[i].toLowerCase()];
|
||||||
if (levels[j]) {
|
if (res != undefined) {
|
||||||
levels[j].push(seg);
|
return res;
|
||||||
}else{
|
}
|
||||||
levels[j] = [seg];
|
}
|
||||||
}
|
return v[''];
|
||||||
//seg.after = 0;
|
}
|
||||||
}
|
return v;
|
||||||
return levels;
|
},
|
||||||
}
|
|
||||||
|
|
||||||
function segCmp(a, b) {
|
|
||||||
return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
|
// event rendering utilities
|
||||||
}
|
|
||||||
|
sliceSegs: function(events, start, end) {
|
||||||
function segsCollide(seg1, seg2) {
|
var segs = [],
|
||||||
return seg1.end > seg2.start && seg1.start < seg2.end;
|
i, len=events.length, event,
|
||||||
}
|
eventStart, eventEnd,
|
||||||
|
segStart, segEnd,
|
||||||
|
isStart, isEnd;
|
||||||
|
for (i=0; i<len; i++) {
|
||||||
|
event = events[i];
|
||||||
|
eventStart = event.start;
|
||||||
|
eventEnd = this.visEventEnd(event);
|
||||||
|
if (eventEnd > start && eventStart < end) {
|
||||||
|
if (eventStart < start) {
|
||||||
|
segStart = cloneDate(start);
|
||||||
|
isStart = false;
|
||||||
|
}else{
|
||||||
|
segStart = eventStart;
|
||||||
|
isStart = true;
|
||||||
|
}
|
||||||
|
if (eventEnd > end) {
|
||||||
|
segEnd = cloneDate(end);
|
||||||
|
isEnd = false;
|
||||||
|
}else{
|
||||||
|
segEnd = eventEnd;
|
||||||
|
isEnd = true;
|
||||||
|
}
|
||||||
|
segs.push({
|
||||||
|
event: event,
|
||||||
|
start: segStart,
|
||||||
|
end: segEnd,
|
||||||
|
isStart: isStart,
|
||||||
|
isEnd: isEnd,
|
||||||
|
msLength: segEnd - segStart
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return segs.sort(segCmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// event rendering calculation utilities
|
||||||
|
|
||||||
|
function stackSegs(segs) {
|
||||||
|
var levels = [],
|
||||||
|
i, len = segs.length, seg,
|
||||||
|
j, collide, k;
|
||||||
|
for (i=0; i<len; i++) {
|
||||||
|
seg = segs[i];
|
||||||
|
j = 0; // the level index where seg should belong
|
||||||
|
while (true) {
|
||||||
|
collide = false;
|
||||||
|
if (levels[j]) {
|
||||||
|
for (k=0; k<levels[j].length; k++) {
|
||||||
|
if (segsCollide(levels[j][k], seg)) {
|
||||||
|
collide = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (collide) {
|
||||||
|
j++;
|
||||||
|
}else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (levels[j]) {
|
||||||
|
levels[j].push(seg);
|
||||||
|
}else{
|
||||||
|
levels[j] = [seg];
|
||||||
|
}
|
||||||
|
//seg.after = 0;
|
||||||
|
}
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
function segCmp(a, b) {
|
||||||
|
return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
function segsCollide(seg1, seg2) {
|
||||||
|
return seg1.end > seg2.start && seg1.start < seg2.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,14 @@
|
||||||
var m = d.getMonth();
|
var m = d.getMonth();
|
||||||
|
|
||||||
$('#calendar').fullCalendar({
|
$('#calendar').fullCalendar({
|
||||||
|
slotMinutes: 30,
|
||||||
|
//allDayHeader: false,
|
||||||
//weekMode: 'variable',
|
//weekMode: 'variable',
|
||||||
theme: true,
|
//theme: true,
|
||||||
|
//firstDay: 1,
|
||||||
//isRTL: true,
|
//isRTL: true,
|
||||||
editable: true,
|
editable: true,
|
||||||
|
//dragOpacity: .5,
|
||||||
defaultView: 'agendaWeek',
|
defaultView: 'agendaWeek',
|
||||||
header: {
|
header: {
|
||||||
left: 'prev,next today',
|
left: 'prev,next today',
|
||||||
|
@ -27,20 +31,26 @@
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Long Event",
|
title: "Long Event",
|
||||||
start: new Date(y, m, 6, 14, 0),
|
start: new Date(y, m, 6, 14, 0),
|
||||||
end: new Date(y, m, 11),
|
end: new Date(y, m, 11)
|
||||||
|
//allDay: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Repeating Event111",
|
||||||
|
start: new Date(y, m, 8),
|
||||||
|
allDay: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Repeating Event222",
|
||||||
|
start: new Date(y, m, 9, 5, 0),
|
||||||
allDay: false
|
allDay: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 345,
|
||||||
title: "Repeating Event",
|
title: "Hey Hey",
|
||||||
start: new Date(y, m, 2),
|
start: new Date(y, m, 9, 4, 0),
|
||||||
allDay: true
|
allDay: false
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "Repeating Event",
|
|
||||||
start: new Date(y, m, 9),
|
|
||||||
allDay: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
|
|
Loading…
Reference in a new issue