[d34684]: / ForgeActivity / forgeactivity / nf / activity / js / activity.js  Maximize  Restore  History

Download this file

343 lines (316 with data), 13.4 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
ASOptions = {
maxPages: 3,
maintainScrollHistory: true,
usePjax: true,
useHash: false, // not worth the jitter
forceAdvancedScroll: false,
useShowMore: false,
useInfiniteScroll: true
}
$(function() {
if (!$('.timeline li').length) {
return; // no timeline, no paging
}
$.expr[':']['timeline-page'] = $.expr.createPseudo(function(page) {
// Select timeline elements by their page. NB: only works on activity LIs.
return function(elem) {
return $(elem).data('page') == page;
}
});
function detectFeatures() {
var hasAPI = window.history && window.history.pushState && window.history.replaceState;
var iOS4 = navigator.userAgent.match(/iP(od|one|ad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork/);
if (!hasAPI || iOS4) {
ASOptions.usePjax = false;
}
if (!ASOptions.usePjax) {
if (!ASOptions.useHash) {
ASOptions.maintainScrollHistory = false;
}
if (!ASOptions.forceAdvancedScroll) {
ASOptions.useShowMore = false;
ASOptions.useInfiniteScroll = false;
}
}
}
var firstVisibleId = null;
var oldScrollTop = null;
var oldTop = null;
function saveScrollPosition() {
// Save the relative position of the first visible element of
// interest for later restore to keep the important visible content
// at the same viewport position before / after DOM changes.
// TODO: This could be made more generic by making the "interesting
// elements" selector configurable.
var $firstVisible = $('.timeline li:in-viewport:first');
firstVisibleId = $firstVisible.attr('id');
oldScrollTop = $(window).scrollTop();
oldTop = $firstVisible.offset().top;
}
function restoreScrollPosition() {
// Restore the relative position of "interesting" content previously
// saved by saveScrollPosition.
var $window = $(window);
var $firstVisible = $('#'+firstVisibleId);
if (!$firstVisible.length) {
return;
}
var newTop = $firstVisible.offset().top;
var scrollTop = $window.scrollTop();
var elemAdjustment = newTop - oldTop;
var viewportAdjustment = scrollTop - oldScrollTop;
$window.scrollTop(scrollTop + elemAdjustment - viewportAdjustment);
$(window).trigger('scroll');
}
function maintainScrollHistory_pjax() {
// Use the HTML5 history API to record page and scroll position.
// TODO: Page changes should pushState while just scroll changes
// should replaceState.
var $firstVisibleActivity = $('.timeline li:in-viewport:first');
var page = $firstVisibleActivity.data('page');
var limit = $('.timeline').data('limit');
var hash = $firstVisibleActivity.attr('id');
if (page != null && limit != null && hash != null) {
history.replaceState(null, null, '?page='+page+'&limit='+limit+'#'+hash);
}
}
function maintainScrollHistory_hash() {
// Use the location.hash to record the scroll position.
// TODO/FIXME: This doesn't record the page for forceAdvancedPaging, and since
// the hash history is additive (confirm?), it can require clicking Back
// through all of your scrolling.
var $firstVisibleActivity = $('.timeline li:in-viewport:first');
saveScrollPosition();
window.location.hash = $firstVisibleActivity.attr('id'); // causes jump...
restoreScrollPosition();
}
var scrollHandlerDelayed = null;
function scrollHandler(event) {
clearTimeout(scrollHandlerDelayed);
var method = ASOptions.usePjax
? maintainScrollHistory_pjax
: maintainScrollHistory_hash;
var delay = ASOptions.usePjax
? 100 // scrolls replace history and don't affect scrolling, so more is ok
: 750; // scrolls add history and affect scrolling, so make sure they're done
scrollHandlerDelayed = setTimeout(method, delay);
}
function enableScrollHistory() {
// Attempt to record the scroll position in the browser history
// using either the HTML5 history API (aka PJAX) or via the location
// hash. Otherwise, when the user clicks a link and then comes back,
// they will lose their scroll position and, in the case of advanced
// paging, which page they were on. See: http://xkcd.com/1309/
if (!ASOptions.maintainScrollHistory) {
return;
}
$(window).scroll(scrollHandler);
}
function pageOut(newer) {
// Remove newest or oldest page to keep memory usage in check.
var $timeline = $('.timeline li');
var firstPage = $timeline.first().data('page');
var lastPage = $timeline.last().data('page');
var numPages = lastPage - firstPage + 1;
if (numPages <= ASOptions.maxPages) {
return;
}
var pageToRemove = newer ? firstPage : lastPage;
$('.timeline li:timeline-page('+pageToRemove+')').remove();
$('.no-more.'+(newer ? 'newer' : 'older')).remove();
}
var pageInQueue = [];
function pageIn(newer, url) {
// Load a single page of either newer or older content from the URL.
// Then calls pageOut to ensure that not too many are loaded at once,
// to keep memory usage in check. Also uses save/restoreScrollPosition
// to try to keep the same content in view at the same place.
if ($('.no-more.'+(newer ? 'newer' : 'older')).length) {
return;
}
pageInQueue.push({newer: newer, url: url});
if (pageInQueue.length > 1) {
return;
}
var newerText = newer ? 'newer' : 'older';
$.get(url, function(html) {
var $timeline = $('.timeline');
var empty = html.match(/^\s*$/);
var newestPage = newer && $('.timeline li:first').data('page') <= 1;
var limit = $('.timeline').data('limit');
var fullPage = true;
saveScrollPosition();
if (!empty) {
$timeline[newer ? 'prepend' : 'append'](html);
var newPage = $timeline.find('li:' + (newer ? 'first' : 'last')).data('page');
fullPage = $timeline.find('li:timeline-page('+newPage+')').length == limit;
pageOut(!newer);
}
if (empty || !fullPage || newestPage) {
makeNoMore(newer);
}
if (ASOptions.useShowMore) {
// this has to be here instead of showMoreLink handler to
// ensure that scroll changes between added / removed content
// and Show More links combine properly and don't cause a jump
// due to hitting the edge of the page
updateShowMore();
}
restoreScrollPosition();
pageInQueue.shift();
if (pageInQueue.length) {
var next = pageInQueue.shift();
pageIn(next.newer, next.url);
}
}).fail(function() {
flash('Error loading activities', 'error');
});
}
function makeNoMore(newer) {
var $timeline = $('.timeline');
var method = newer ? 'before' : 'after';
var cls = newer ? 'newer' : 'older';
$timeline[method]('<div class="no-more '+cls+'">No more activities</div>');
}
function makePageUrl(targetPage) {
var limit = $('.timeline').data('limit');
return 'pjax?page='+targetPage+'&limit='+limit;
}
function makeShowMoreLink(newer, targetPage) {
var cls = newer ? 'newer' : 'older';
var url = makePageUrl(targetPage);
var link = '<a class="show-more '+cls+'" href="'+url+'">Show More</a>';
$('.timeline')[newer ? 'before' : 'after'](link);
$('.show-more.'+cls).click(function(event) {
event.preventDefault();
pageIn(newer, this.href);
});
}
function updateShowMore() {
// Update the state of the Show More links when using "Show More"-style
// advanced paging.
var firstPage = $('.timeline li:first').data('page');
var lastPage = $('.timeline li:last').data('page');
var noMoreNewer = $('.no-more.newer').length;
var noMoreOlder = $('.no-more.older').length;
$('.show-more').remove(); // TODO: could update HREFs instead of always re-creating links
if (!noMoreNewer) {
makeShowMoreLink(true, firstPage-1);
}
if (!noMoreOlder) {
makeShowMoreLink(false, lastPage+1);
}
}
function enableShowMore() {
$('.page_list').remove();
if ($('.timeline li:first').data('page') == 0) {
makeNoMore(true);
}
updateShowMore();
}
var currentPage = null;
function handleInfiniteScroll(event) {
var newPage = $('.timeline li:in-viewport:first').data('page');
if (newPage == currentPage) {
return;
}
var firstPage = $('.timeline li:first').data('page');
var lastPage = $('.timeline li:last').data('page');
var noMoreNewer = $('.no-more.newer').length;
var noMoreOlder = $('.no-more.older').length;
if (newPage < currentPage && !noMoreNewer) {
pageIn(true, makePageUrl(firstPage-1));
} else if (newPage > currentPage && !noMoreOlder) {
pageIn(false, makePageUrl(lastPage+1));
}
currentPage = newPage;
}
function enableInfiniteScroll() {
$('.page_list').remove();
currentPage = $('.timeline li:first').data('page');
if (currentPage == 0) {
makeNoMore(true);
} else {
pageIn(true, makePageUrl(currentPage-1));
}
pageIn(false, makePageUrl(currentPage+1));
$(window).scroll(handleInfiniteScroll);
}
function enableAdvancedPaging() {
if (ASOptions.useInfiniteScroll) {
enableInfiniteScroll();
} else if (ASOptions.useShowMore) {
enableShowMore();
}
}
function enableDeleteButtons() {
var confirmed = false;
$('.timeline').on('mouseenter', 'li[data-can-delete]', function() {
$(this).prepend('<input type=button value=Delete name=delete title="Permanently deletes this item from all users/projects activity records.<br>Only neighborhood admins can do this.">');
$('input[name=delete]', this).tooltipster({contentAsHTML: true});
});
$('.timeline').on('mouseleave', 'li[data-can-delete]', function() {
$('input[name=delete]', this).remove();
});
$('.timeline').on('click', 'li[data-can-delete] input[name=delete]', function() {
if (!confirmed) {
confirmed = confirm('Are you sure you want to delete this? You cannot undo!');
if (!confirmed) {
return;
}
}
$(this).prop('disabled', true);
$(this).val('Deleting...')
var $row = $(this).closest('[data-can-delete]');
$row.css('background', 'lightgray');
$.post('delete_item', {
activity_id: $row.attr('id'),
_session_id: $.cookie('_session_id')
}).done(function() {
$('input[name=delete]', $row).remove();
$row.css('text-decoration', 'line-through').removeAttr('data-can-delete');
}).fail(function() {
flash('Deleting failed.', 'error');
$row.css('background', 'orange');
});
return false;
});
}
detectFeatures();
enableScrollHistory();
enableAdvancedPaging();
enableDeleteButtons();
});
function markTop() {
var $marker = $('#offset-marker');
if (!$marker.length) {
$marker = $('<div id="offset-marker">&nbsp;</div>');
$marker.css({
'position': 'absolute',
'top': 0,
'width': '100%',
'border-top': '1px solid green'
});
$marker.appendTo($('body'));
}
$marker.css({'top': $(window).scrollTop()});
}
function markFirst() {
$('.timeline li:in-viewport:first').css({'background-color': '#f0fff0'});
}