").addClass("ZebraDialog_ButtonsOuter"+(plugin.settings.center_buttons?" ZebraDialog_Buttons_Centered":"")))}if(plugin.dialog.appendTo("body"),plugin.settings.show_close_button){var l=$('
×').bind("click",function(a){a.preventDefault(),plugin.close()}).appendTo(a?a:plugin.message);a&&l.css({right:parseInt(a.css("paddingRight"),10),top:(parseInt(a.css("height"),10)+parseInt(a.css("paddingTop"),10)+parseInt(a.css("paddingBottom"),10)-l.height())/2})}return $(window).bind("resize.Zebra_Dialog",function(){clearTimeout(timeout),timeout=setTimeout(function(){draw()},100)}),plugin.settings.keyboard&&$(document).bind("keyup.Zebra_Dialog",function(a){return 27==a.which&&plugin.close(),!0}),plugin.isIE6&&$(window).bind("scroll.Zebra_Dialog",function(){emulate_fixed_position()}),plugin.settings.auto_close!==!1&&(plugin.dialog.bind("click",function(){clearTimeout(plugin.timeout),plugin.close()}),plugin.timeout=setTimeout(plugin.close,plugin.settings.auto_close)),draw(!1),plugin},plugin.close=function(a){$(document).unbind(".Zebra_Dialog"),$(window).unbind(".Zebra_Dialog"),plugin.overlay&&plugin.overlay.animate({opacity:0},plugin.settings.animation_speed_hide,function(){plugin.overlay.remove()}),plugin.dialog.animate({top:0,opacity:0},plugin.settings.animation_speed_hide,function(){plugin.dialog.remove(),plugin.settings.onClose&&"function"==typeof plugin.settings.onClose&&plugin.settings.onClose(void 0!==a?a:"")})};var draw=function(){var viewport_width=$(window).width(),viewport_height=$(window).height(),dialog_width=plugin.dialog.width(),dialog_height=plugin.dialog.height(),values={left:0,top:0,right:viewport_width-dialog_width,bottom:viewport_height-dialog_height,center:(viewport_width-dialog_width)/2,middle:(viewport_height-dialog_height)/2};if(plugin.dialog_left=void 0,plugin.dialog_top=void 0,$.isArray(plugin.settings.position)&&2==plugin.settings.position.length&&"string"==typeof plugin.settings.position[0]&&plugin.settings.position[0].match(/^(left|right|center)[\s0-9\+\-]*$/)&&"string"==typeof plugin.settings.position[1]&&plugin.settings.position[1].match(/^(top|bottom|middle)[\s0-9\+\-]*$/)&&(plugin.settings.position[0]=plugin.settings.position[0].toLowerCase(),plugin.settings.position[1]=plugin.settings.position[1].toLowerCase(),$.each(values,function(index,value){for(var i=0;2>i;i++){var tmp=plugin.settings.position[i].replace(index,value);tmp!=plugin.settings.position[i]&&(0===i?plugin.dialog_left=eval(tmp):plugin.dialog_top=eval(tmp))}})),(void 0===plugin.dialog_left||void 0===plugin.dialog_top)&&(plugin.dialog_left=values.center,plugin.dialog_top=values.middle),plugin.settings.vcenter_short_message){var message=plugin.message.find("div:first"),message_height=message.height(),container_height=plugin.message.height();container_height>message_height&&message.css({"padding-top":(container_height-message_height)/2})}"boolean"==typeof arguments[0]&&arguments[0]===!1||0===plugin.settings.reposition_speed?plugin.dialog.css({left:plugin.dialog_left,top:plugin.dialog_top,visibility:"visible",opacity:0}).animate({opacity:1},plugin.settings.animation_speed_show):(plugin.dialog.stop(!0),plugin.dialog.css("visibility","visible").animate({left:plugin.dialog_left,top:plugin.dialog_top},plugin.settings.reposition_speed)),plugin.dialog.find("a[class^=ZebraDialog_Button]:first").focus(),plugin.isIE6&&setTimeout(emulate_fixed_position,500)},emulate_fixed_position=function(){var a=$(window).scrollTop(),b=$(window).scrollLeft();plugin.settings.modal&&plugin.overlay.css({top:a,left:b}),plugin.dialog.css({left:plugin.dialog_left+b,top:plugin.dialog_top+a})},get_buttons=function(){if(plugin.settings.buttons!==!0&&!$.isArray(plugin.settings.buttons))return!1;if(plugin.settings.buttons===!0)switch(plugin.settings.type){case"question":plugin.settings.buttons=["Yes","No"];break;default:plugin.settings.buttons=["Ok"]}return plugin.settings.buttons},get_type=function(){switch(plugin.settings.type){case"confirmation":case"error":case"information":case"question":case"warning":return plugin.settings.type.charAt(0).toUpperCase()+plugin.settings.type.slice(1).toLowerCase();default:return!1}},browser={init:function(){this.name=this.searchString(this.dataBrowser)||"",this.version=this.searchVersion(navigator.userAgent)||this.searchVersion(navigator.appVersion)||""},searchString:function(a){for(var b=0;b
now - time < errorTracker.windowSize);
30 | errorTracker.errors504 = errorTracker.errors504.filter(time => now - time < errorTracker.windowSize);
31 | }
32 |
33 | // Calculate retry delay with exponential backoff
34 | function calculateRetryDelay(retryCount, isRateLimit = false) {
35 | if (isRateLimit) {
36 | // For rate limits, use longer delays
37 | const baseDelay = 5000; // 5 seconds for rate limits
38 | return Math.min(baseDelay * Math.pow(2, retryCount), 60000); // Max 1 minute
39 | }
40 | return Math.min(retryConfig.baseDelay * Math.pow(retryConfig.backoffMultiplier, retryCount), retryConfig.maxDelay);
41 | }
42 |
43 | // Retry a failed request
44 | function retryRequest(ajaxSettings, retryCount = 0, originalError = null) {
45 | const requestKey = JSON.stringify({
46 | url: ajaxSettings.url,
47 | data: ajaxSettings.data
48 | });
49 |
50 | // Prevent duplicate retries
51 | if (errorTracker.activeRetries.has(requestKey)) {
52 | console.log('[TrelloExport] Request already being retried, skipping duplicate');
53 | return;
54 | }
55 |
56 | errorTracker.activeRetries.add(requestKey);
57 |
58 | const isRateLimit = originalError && originalError.status === 429;
59 | const delay = calculateRetryDelay(retryCount, isRateLimit);
60 |
61 | console.log(`[TrelloExport] Retrying request in ${delay}ms (attempt ${retryCount + 1}/${retryConfig.maxRetries})`);
62 |
63 | // Show retry notification
64 | const retryNotification = $.growl.notice({
65 | title: "Retrying Request",
66 | message: `Waiting ${Math.round(delay / 1000)} seconds before retry (attempt ${retryCount + 1}/${retryConfig.maxRetries})...`,
67 | duration: delay + 1000
68 | });
69 |
70 | setTimeout(function() {
71 | // Clone the original settings to avoid modifying them
72 | const retrySettings = $.extend(true, {}, ajaxSettings);
73 |
74 | // Add retry info to the request
75 | retrySettings.beforeSend = function(xhr) {
76 | xhr.setRequestHeader('X-Retry-Attempt', retryCount + 1);
77 | if (ajaxSettings.beforeSend) {
78 | ajaxSettings.beforeSend(xhr);
79 | }
80 | };
81 |
82 | // Wrap success handler
83 | const originalSuccess = retrySettings.success;
84 | retrySettings.success = function(data, textStatus, jqXHR) {
85 | errorTracker.activeRetries.delete(requestKey);
86 | console.log('[TrelloExport] Retry successful');
87 | $.growl.notice({
88 | title: "Success",
89 | message: "Request completed successfully after retry",
90 | duration: 3000
91 | });
92 | if (originalSuccess) {
93 | originalSuccess(data, textStatus, jqXHR);
94 | }
95 | };
96 |
97 | // Wrap error handler
98 | const originalError = retrySettings.error;
99 | retrySettings.error = function(jqXHR, textStatus, errorThrown) {
100 | errorTracker.activeRetries.delete(requestKey);
101 |
102 | if ((jqXHR.status === 429 || jqXHR.status === 504) && retryCount < retryConfig.maxRetries - 1) {
103 | // Retry again
104 | retryRequest(ajaxSettings, retryCount + 1, jqXHR);
105 | } else {
106 | // Max retries reached or different error
107 | console.error('[TrelloExport] Max retries reached or non-retryable error');
108 | if (originalError) {
109 | originalError(jqXHR, textStatus, errorThrown);
110 | }
111 | }
112 | };
113 |
114 | // Make the retry request
115 | $.ajax(retrySettings);
116 |
117 | }, delay);
118 | }
119 |
120 | // Monitor ajax errors
121 | $(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
122 | if (!ajaxSettings.url || !ajaxSettings.url.includes('trello.com/1/')) {
123 | return;
124 | }
125 |
126 | cleanOldErrors();
127 |
128 | // Check if this request should be retried
129 | const shouldRetry = (jqXHR.status === 429 || jqXHR.status === 504) &&
130 | !ajaxSettings.isRetry && // Prevent infinite retry loops
131 | !ajaxSettings.url.includes('batch'); // Don't retry batch requests
132 |
133 | if (jqXHR.status === 429) {
134 | // Rate limit error
135 | errorTracker.errors429.push(Date.now());
136 | console.error('[TrelloExport] Rate limit error (429) detected');
137 |
138 | if (shouldRetry) {
139 | // Automatically retry the request
140 | $.growl.warning({
141 | title: "Rate Limit - Retrying",
142 | message: "Too many requests to Trello. Automatically retrying with delay...",
143 | duration: 5000
144 | });
145 |
146 | // Mark this as a retry to prevent loops
147 | ajaxSettings.isRetry = true;
148 | retryRequest(ajaxSettings, 0, jqXHR);
149 | } else {
150 | // Show error without retry
151 | $.growl.error({
152 | title: "Rate Limit Reached",
153 | message: "Too many requests to Trello. Please wait a moment and try again.",
154 | duration: 10000,
155 | fixed: true
156 | });
157 | }
158 |
159 | // If multiple 429 errors, suggest solutions
160 | if (errorTracker.errors429.length >= 3) {
161 | $.growl.warning({
162 | title: "Tip",
163 | message: "Try exporting fewer lists or cards at once, or wait a few minutes before trying again.",
164 | duration: 15000
165 | });
166 | }
167 | } else if (jqXHR.status === 504 || (jqXHR.statusText === 'timeout' && ajaxSettings.timeout)) {
168 | // Timeout error
169 | errorTracker.errors504.push(Date.now());
170 | console.error('[TrelloExport] Timeout error (504) detected');
171 |
172 | if (shouldRetry) {
173 | // Automatically retry the request
174 | $.growl.warning({
175 | title: "Timeout - Retrying",
176 | message: "Request timed out. Automatically retrying...",
177 | duration: 5000
178 | });
179 |
180 | // Mark this as a retry to prevent loops
181 | ajaxSettings.isRetry = true;
182 | retryRequest(ajaxSettings, 0, jqXHR);
183 | } else {
184 | $.growl.error({
185 | title: "Request Timeout",
186 | message: "The request took too long. This usually happens with large boards.",
187 | duration: 10000,
188 | fixed: true
189 | });
190 | }
191 |
192 | // If multiple timeouts, suggest solutions
193 | if (errorTracker.errors504.length >= 2) {
194 | $.growl.warning({
195 | title: "Tip",
196 | message: "For large boards, try: 1) Export one list at a time, 2) Reduce the number of cards, or 3) Disable comments/attachments export.",
197 | duration: 20000
198 | });
199 | }
200 | }
201 | });
202 |
203 | // Add adaptive delays between requests to prevent rate limiting
204 | let lastRequestTime = 0;
205 | let requestCount = 0;
206 | let requestWindowStart = Date.now();
207 | const requestWindow = 10000; // 10 seconds
208 | const maxRequestsPerWindow = 50; // Trello's typical rate limit
209 |
210 | $(document).ajaxSend(function(event, jqXHR, ajaxSettings) {
211 | if (!ajaxSettings.url || !ajaxSettings.url.includes('trello.com/1/')) {
212 | return;
213 | }
214 |
215 | const now = Date.now();
216 |
217 | // Reset counter if window expired
218 | if (now - requestWindowStart > requestWindow) {
219 | requestCount = 0;
220 | requestWindowStart = now;
221 | }
222 |
223 | requestCount++;
224 |
225 | // Calculate adaptive delay based on request rate
226 | let minDelay = 100; // Base delay
227 |
228 | // If we're approaching rate limit, increase delay
229 | if (requestCount > maxRequestsPerWindow * 0.7) {
230 | minDelay = 500; // Slow down significantly
231 | } else if (requestCount > maxRequestsPerWindow * 0.5) {
232 | minDelay = 200; // Moderate slowdown
233 | }
234 |
235 | const timeSinceLastRequest = now - lastRequestTime;
236 |
237 | if (timeSinceLastRequest < minDelay) {
238 | // Add a small delay using setTimeout to avoid blocking
239 | const delay = minDelay - timeSinceLastRequest;
240 | console.log(`[TrelloExport] Delaying request by ${delay}ms to prevent rate limiting`);
241 |
242 | // Use synchronous delay for simplicity (not ideal but works for this use case)
243 | const start = Date.now();
244 | while (Date.now() - start < delay) {
245 | // Small delay
246 | }
247 | }
248 |
249 | lastRequestTime = Date.now();
250 | });
251 |
252 | // Provide manual retry function
253 | window.TrelloExportRetry = function() {
254 | cleanOldErrors();
255 | errorTracker.errors429 = [];
256 | errorTracker.errors504 = [];
257 |
258 | $.growl.notice({
259 | title: "Ready to Retry",
260 | message: "Error counters reset. You can try exporting again.",
261 | duration: 3000
262 | });
263 | };
264 |
265 | console.log('[TrelloExport] Ready - monitoring for 429 and 504 errors');
266 | })();
--------------------------------------------------------------------------------
/bower_components/tingle/dist/tingle.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * tingle.js
3 | * @author robin_parisi
4 | * @version 0.13.2
5 | * @url
6 | */
7 | (function(root, factory) {
8 | if (typeof define === 'function' && define.amd) {
9 | define(factory);
10 | } else if (typeof exports === 'object') {
11 | module.exports = factory();
12 | } else {
13 | root.tingle = factory();
14 | }
15 | }(this, function() {
16 |
17 | /* ----------------------------------------------------------- */
18 | /* == modal */
19 | /* ----------------------------------------------------------- */
20 |
21 | var transitionEvent = whichTransitionEvent();
22 |
23 | function Modal(options) {
24 |
25 | var defaults = {
26 | onClose: null,
27 | onOpen: null,
28 | beforeOpen: null,
29 | beforeClose: null,
30 | stickyFooter: false,
31 | footer: false,
32 | cssClass: [],
33 | closeLabel: 'Close',
34 | closeMethods: ['overlay', 'button', 'escape']
35 | };
36 |
37 | // extends config
38 | this.opts = extend({}, defaults, options);
39 |
40 | // init modal
41 | this.init();
42 | }
43 |
44 | Modal.prototype.init = function() {
45 | if (this.modal) {
46 | return;
47 | }
48 |
49 | _build.call(this);
50 | _bindEvents.call(this);
51 |
52 | // insert modal in dom
53 | document.body.insertBefore(this.modal, document.body.firstChild);
54 |
55 | if (this.opts.footer) {
56 | this.addFooter();
57 | }
58 | };
59 |
60 | Modal.prototype.destroy = function() {
61 | if (this.modal === null) {
62 | return;
63 | }
64 |
65 | // unbind all events
66 | _unbindEvents.call(this);
67 |
68 | // remove modal from dom
69 | this.modal.parentNode.removeChild(this.modal);
70 |
71 | this.modal = null;
72 | };
73 |
74 |
75 | Modal.prototype.open = function() {
76 |
77 | var self = this;
78 |
79 | // before open callback
80 | if (typeof self.opts.beforeOpen === 'function') {
81 | self.opts.beforeOpen();
82 | }
83 |
84 | if (this.modal.style.removeProperty) {
85 | this.modal.style.removeProperty('display');
86 | } else {
87 | this.modal.style.removeAttribute('display');
88 | }
89 |
90 | // prevent double scroll
91 | this._scrollPosition = window.pageYOffset;
92 | document.body.classList.add('tingle-enabled');
93 | document.body.style.top = -this._scrollPosition + 'px';
94 |
95 | // sticky footer
96 | this.setStickyFooter(this.opts.stickyFooter);
97 |
98 | // show modal
99 | this.modal.classList.add('tingle-modal--visible');
100 |
101 | if (transitionEvent) {
102 | this.modal.addEventListener(transitionEvent, function handler() {
103 | if (typeof self.opts.onOpen === 'function') {
104 | self.opts.onOpen.call(self);
105 | }
106 |
107 | // detach event after transition end (so it doesn't fire multiple onOpen)
108 | self.modal.removeEventListener(transitionEvent, handler, false);
109 |
110 | }, false);
111 | } else {
112 | if (typeof self.opts.onOpen === 'function') {
113 | self.opts.onOpen.call(self);
114 | }
115 | }
116 |
117 | // check if modal is bigger than screen height
118 | this.checkOverflow();
119 | };
120 |
121 | Modal.prototype.isOpen = function() {
122 | return !!this.modal.classList.contains("tingle-modal--visible");
123 | };
124 |
125 | Modal.prototype.close = function() {
126 |
127 | // before close
128 | if (typeof this.opts.beforeClose === "function") {
129 | var close = this.opts.beforeClose.call(this);
130 | if (!close) return;
131 | }
132 |
133 | document.body.classList.remove('tingle-enabled');
134 | window.scrollTo(0, this._scrollPosition);
135 | document.body.style.top = null;
136 |
137 | this.modal.classList.remove('tingle-modal--visible');
138 |
139 | //Using similar setup as onOpen
140 | //Reference to the Modal that's created
141 | var self = this;
142 |
143 | if (transitionEvent) {
144 | //Track when transition is happening then run onClose on complete
145 | this.modal.addEventListener(transitionEvent, function handler() {
146 | // detach event after transition end (so it doesn't fire multiple onClose)
147 | self.modal.removeEventListener(transitionEvent, handler, false);
148 |
149 | self.modal.style.display = 'none';
150 |
151 | // on close callback
152 | if (typeof self.opts.onClose === "function") {
153 | self.opts.onClose.call(this);
154 | }
155 |
156 | }, false);
157 | } else {
158 | self.modal.style.display = 'none';
159 | // on close callback
160 | if (typeof self.opts.onClose === "function") {
161 | self.opts.onClose.call(this);
162 | }
163 | }
164 | };
165 |
166 | Modal.prototype.setContent = function(content) {
167 | // check type of content : String or Node
168 | if (typeof content === 'string') {
169 | this.modalBoxContent.innerHTML = content;
170 | } else {
171 | this.modalBoxContent.innerHTML = "";
172 | this.modalBoxContent.appendChild(content);
173 | }
174 | };
175 |
176 | Modal.prototype.getContent = function() {
177 | return this.modalBoxContent;
178 | };
179 |
180 | Modal.prototype.addFooter = function() {
181 | // add footer to modal
182 | _buildFooter.call(this);
183 | };
184 |
185 | Modal.prototype.setFooterContent = function(content) {
186 | // set footer content
187 | this.modalBoxFooter.innerHTML = content;
188 | };
189 |
190 | Modal.prototype.getFooterContent = function() {
191 | return this.modalBoxFooter;
192 | };
193 |
194 | Modal.prototype.setStickyFooter = function(isSticky) {
195 | // if the modal is smaller than the viewport height, we don't need sticky
196 | if (!this.isOverflow()) {
197 | isSticky = false;
198 | }
199 |
200 | if (isSticky) {
201 | if (this.modalBox.contains(this.modalBoxFooter)) {
202 | this.modalBox.removeChild(this.modalBoxFooter);
203 | this.modal.appendChild(this.modalBoxFooter);
204 | this.modalBoxFooter.classList.add('tingle-modal-box__footer--sticky');
205 | _recalculateFooterPosition.call(this);
206 | this.modalBoxContent.style['padding-bottom'] = this.modalBoxFooter.clientHeight + 20 + 'px';
207 | }
208 | } else if (this.modalBoxFooter) {
209 | if (!this.modalBox.contains(this.modalBoxFooter)) {
210 | this.modal.removeChild(this.modalBoxFooter);
211 | this.modalBox.appendChild(this.modalBoxFooter);
212 | this.modalBoxFooter.style.width = 'auto';
213 | this.modalBoxFooter.style.left = '';
214 | this.modalBoxContent.style['padding-bottom'] = '';
215 | this.modalBoxFooter.classList.remove('tingle-modal-box__footer--sticky');
216 | }
217 | }
218 | };
219 |
220 |
221 | Modal.prototype.addFooterBtn = function(label, cssClass, callback) {
222 | var btn = document.createElement("button");
223 |
224 | // set label
225 | btn.innerHTML = label;
226 |
227 | // bind callback
228 | btn.addEventListener('click', callback);
229 |
230 | if (typeof cssClass === 'string' && cssClass.length) {
231 | // add classes to btn
232 | cssClass.split(" ").forEach(function(item) {
233 | btn.classList.add(item);
234 | });
235 | }
236 |
237 | this.modalBoxFooter.appendChild(btn);
238 |
239 | return btn;
240 | };
241 |
242 | Modal.prototype.resize = function() {
243 | console.warn('Resize is deprecated and will be removed in version 1.0');
244 | };
245 |
246 |
247 | Modal.prototype.isOverflow = function() {
248 | var viewportHeight = window.innerHeight;
249 | var modalHeight = this.modalBox.clientHeight;
250 |
251 | return modalHeight >= viewportHeight;
252 | };
253 |
254 | Modal.prototype.checkOverflow = function() {
255 | // only if the modal is currently shown
256 | if (this.modal.classList.contains('tingle-modal--visible')) {
257 | if (this.isOverflow()) {
258 | this.modal.classList.add('tingle-modal--overflow');
259 | } else {
260 | this.modal.classList.remove('tingle-modal--overflow');
261 | }
262 |
263 | // TODO: remove offset
264 | //_offset.call(this);
265 | if (!this.isOverflow() && this.opts.stickyFooter) {
266 | this.setStickyFooter(false);
267 | } else if (this.isOverflow() && this.opts.stickyFooter) {
268 | _recalculateFooterPosition.call(this);
269 | this.setStickyFooter(true);
270 | }
271 | }
272 | }
273 |
274 |
275 | /* ----------------------------------------------------------- */
276 | /* == private methods */
277 | /* ----------------------------------------------------------- */
278 |
279 | function _recalculateFooterPosition() {
280 | if (!this.modalBoxFooter) {
281 | return;
282 | }
283 | this.modalBoxFooter.style.width = this.modalBox.clientWidth + 'px';
284 | this.modalBoxFooter.style.left = this.modalBox.offsetLeft + 'px';
285 | }
286 |
287 | function _build() {
288 |
289 | // wrapper
290 | this.modal = document.createElement('div');
291 | this.modal.classList.add('tingle-modal');
292 |
293 | // remove cusor if no overlay close method
294 | if (this.opts.closeMethods.length === 0 || this.opts.closeMethods.indexOf('overlay') === -1) {
295 | this.modal.classList.add('tingle-modal--noOverlayClose');
296 | }
297 |
298 | this.modal.style.display = 'none';
299 |
300 | // custom class
301 | this.opts.cssClass.forEach(function(item) {
302 | if (typeof item === 'string') {
303 | this.modal.classList.add(item);
304 | }
305 | }, this);
306 |
307 | // close btn
308 | if (this.opts.closeMethods.indexOf('button') !== -1) {
309 | this.modalCloseBtn = document.createElement('button');
310 | this.modalCloseBtn.classList.add('tingle-modal__close');
311 |
312 | this.modalCloseBtnIcon = document.createElement('span');
313 | this.modalCloseBtnIcon.classList.add('tingle-modal__closeIcon');
314 | this.modalCloseBtnIcon.innerHTML = '×';
315 |
316 | this.modalCloseBtnLabel = document.createElement('span');
317 | this.modalCloseBtnLabel.classList.add('tingle-modal__closeLabel');
318 | this.modalCloseBtnLabel.innerHTML = this.opts.closeLabel;
319 |
320 | this.modalCloseBtn.appendChild(this.modalCloseBtnIcon);
321 | this.modalCloseBtn.appendChild(this.modalCloseBtnLabel);
322 | }
323 |
324 | // modal
325 | this.modalBox = document.createElement('div');
326 | this.modalBox.classList.add('tingle-modal-box');
327 |
328 | // modal box content
329 | this.modalBoxContent = document.createElement('div');
330 | this.modalBoxContent.classList.add('tingle-modal-box__content');
331 |
332 | this.modalBox.appendChild(this.modalBoxContent);
333 |
334 | if (this.opts.closeMethods.indexOf('button') !== -1) {
335 | this.modal.appendChild(this.modalCloseBtn);
336 | }
337 |
338 | this.modal.appendChild(this.modalBox);
339 |
340 | }
341 |
342 | function _buildFooter() {
343 | this.modalBoxFooter = document.createElement('div');
344 | this.modalBoxFooter.classList.add('tingle-modal-box__footer');
345 | this.modalBox.appendChild(this.modalBoxFooter);
346 | }
347 |
348 | function _bindEvents() {
349 |
350 | this._events = {
351 | clickCloseBtn: this.close.bind(this),
352 | clickOverlay: _handleClickOutside.bind(this),
353 | resize: this.checkOverflow.bind(this),
354 | keyboardNav: _handleKeyboardNav.bind(this)
355 | };
356 |
357 | if (this.opts.closeMethods.indexOf('button') !== -1) {
358 | this.modalCloseBtn.addEventListener('click', this._events.clickCloseBtn);
359 | }
360 |
361 | this.modal.addEventListener('mousedown', this._events.clickOverlay);
362 | window.addEventListener('resize', this._events.resize);
363 | document.addEventListener("keydown", this._events.keyboardNav);
364 | }
365 |
366 | function _handleKeyboardNav(event) {
367 | // escape key
368 | if (this.opts.closeMethods.indexOf('escape') !== -1 && event.which === 27 && this.isOpen()) {
369 | this.close();
370 | }
371 | }
372 |
373 | function _handleClickOutside(event) {
374 | // if click is outside the modal
375 | if (this.opts.closeMethods.indexOf('overlay') !== -1 && !_findAncestor(event.target, 'tingle-modal') &&
376 | event.clientX < this.modal.clientWidth) {
377 | this.close();
378 | }
379 | }
380 |
381 | function _findAncestor(el, cls) {
382 | while ((el = el.parentElement) && !el.classList.contains(cls));
383 | return el;
384 | }
385 |
386 | function _unbindEvents() {
387 | if (this.opts.closeMethods.indexOf('button') !== -1) {
388 | this.modalCloseBtn.removeEventListener('click', this._events.clickCloseBtn);
389 | }
390 | this.modal.removeEventListener('mousedown', this._events.clickOverlay);
391 | window.removeEventListener('resize', this._events.resize);
392 | document.removeEventListener("keydown", this._events.keyboardNav);
393 | }
394 |
395 | /* ----------------------------------------------------------- */
396 | /* == confirm */
397 | /* ----------------------------------------------------------- */
398 |
399 | // coming soon
400 |
401 | /* ----------------------------------------------------------- */
402 | /* == alert */
403 | /* ----------------------------------------------------------- */
404 |
405 | // coming soon
406 |
407 | /* ----------------------------------------------------------- */
408 | /* == helpers */
409 | /* ----------------------------------------------------------- */
410 |
411 | function extend() {
412 | for (var i = 1; i < arguments.length; i++) {
413 | for (var key in arguments[i]) {
414 | if (arguments[i].hasOwnProperty(key)) {
415 | arguments[0][key] = arguments[i][key];
416 | }
417 | }
418 | }
419 | return arguments[0];
420 | }
421 |
422 | function whichTransitionEvent() {
423 | var t;
424 | var el = document.createElement('tingle-test-transition');
425 | var transitions = {
426 | 'transition': 'transitionend',
427 | 'OTransition': 'oTransitionEnd',
428 | 'MozTransition': 'transitionend',
429 | 'WebkitTransition': 'webkitTransitionEnd'
430 | };
431 |
432 | for (t in transitions) {
433 | if (el.style[t] !== undefined) {
434 | return transitions[t];
435 | }
436 | }
437 | }
438 |
439 | /* ----------------------------------------------------------- */
440 | /* == return */
441 | /* ----------------------------------------------------------- */
442 |
443 | return {
444 | modal: Modal
445 | };
446 |
447 | }));
448 |
--------------------------------------------------------------------------------
/lib/tooltip.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.7 (http://getbootstrap.com)
3 | * Copyright 2011-2016 Twitter, Inc.
4 | * Licensed under the MIT license
5 | */
6 | var Tooltip = function(element, options) {
7 | this.type = null
8 | this.options = null
9 | this.enabled = null
10 | this.timeout = null
11 | this.hoverState = null
12 | this.$element = null
13 | this.inState = null
14 |
15 | this.init('tooltip', element, options)
16 | }
17 |
18 | Tooltip.VERSION = '3.3.7'
19 |
20 | Tooltip.TRANSITION_DURATION = 150
21 |
22 | Tooltip.DEFAULTS = {
23 | animation: true,
24 | placement: 'top',
25 | selector: false,
26 | template: '',
27 | trigger: 'hover focus',
28 | title: '',
29 | delay: 0,
30 | html: false,
31 | container: false,
32 | viewport: {
33 | selector: 'body',
34 | padding: 0
35 | }
36 | }
37 |
38 | Tooltip.prototype.init = function(type, element, options) {
39 | this.enabled = true
40 | this.type = type
41 | this.$element = $(element)
42 | this.options = this.getOptions(options)
43 | this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
44 | this.inState = { click: false, hover: false, focus: false }
45 |
46 | if (this.$element[0] instanceof document.constructor && !this.options.selector) {
47 | throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
48 | }
49 |
50 | var triggers = this.options.trigger.split(' ')
51 |
52 | for (var i = triggers.length; i--;) {
53 | var trigger = triggers[i]
54 |
55 | if (trigger == 'click') {
56 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
57 | } else if (trigger != 'manual') {
58 | var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
59 | var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
60 |
61 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
62 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
63 | }
64 | }
65 |
66 | this.options.selector ?
67 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
68 | this.fixTitle()
69 | }
70 |
71 | Tooltip.prototype.getDefaults = function() {
72 | return Tooltip.DEFAULTS
73 | }
74 |
75 | Tooltip.prototype.getOptions = function(options) {
76 | options = $.extend({}, this.getDefaults(), this.$element.data(), options)
77 |
78 | if (options.delay && typeof options.delay == 'number') {
79 | options.delay = {
80 | show: options.delay,
81 | hide: options.delay
82 | }
83 | }
84 |
85 | return options
86 | }
87 |
88 | Tooltip.prototype.getDelegateOptions = function() {
89 | var options = {}
90 | var defaults = this.getDefaults()
91 |
92 | this._options && $.each(this._options, function(key, value) {
93 | if (defaults[key] != value) options[key] = value
94 | })
95 |
96 | return options
97 | }
98 |
99 | Tooltip.prototype.enter = function(obj) {
100 | var self = obj instanceof this.constructor ?
101 | obj : $(obj.currentTarget).data('bs.' + this.type)
102 |
103 | if (!self) {
104 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
105 | $(obj.currentTarget).data('bs.' + this.type, self)
106 | }
107 |
108 | if (obj instanceof $.Event) {
109 | self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
110 | }
111 |
112 | if (self.tip().hasClass('in') || self.hoverState == 'in') {
113 | self.hoverState = 'in'
114 | return
115 | }
116 |
117 | clearTimeout(self.timeout)
118 |
119 | self.hoverState = 'in'
120 |
121 | if (!self.options.delay || !self.options.delay.show) return self.show()
122 |
123 | self.timeout = setTimeout(function() {
124 | if (self.hoverState == 'in') self.show()
125 | }, self.options.delay.show)
126 | }
127 |
128 | Tooltip.prototype.isInStateTrue = function() {
129 | for (var key in this.inState) {
130 | if (this.inState[key]) return true
131 | }
132 |
133 | return false
134 | }
135 |
136 | Tooltip.prototype.leave = function(obj) {
137 | var self = obj instanceof this.constructor ?
138 | obj : $(obj.currentTarget).data('bs.' + this.type)
139 |
140 | if (!self) {
141 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
142 | $(obj.currentTarget).data('bs.' + this.type, self)
143 | }
144 |
145 | if (obj instanceof $.Event) {
146 | self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
147 | }
148 |
149 | if (self.isInStateTrue()) return
150 |
151 | clearTimeout(self.timeout)
152 |
153 | self.hoverState = 'out'
154 |
155 | if (!self.options.delay || !self.options.delay.hide) return self.hide()
156 |
157 | self.timeout = setTimeout(function() {
158 | if (self.hoverState == 'out') self.hide()
159 | }, self.options.delay.hide)
160 | }
161 |
162 | Tooltip.prototype.show = function() {
163 | var e = $.Event('show.bs.' + this.type)
164 |
165 | if (this.hasContent() && this.enabled) {
166 | this.$element.trigger(e)
167 |
168 | var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
169 | if (e.isDefaultPrevented() || !inDom) return
170 | var that = this
171 |
172 | var $tip = this.tip()
173 |
174 | var tipId = this.getUID(this.type)
175 |
176 | this.setContent()
177 | $tip.attr('id', tipId)
178 | this.$element.attr('aria-describedby', tipId)
179 |
180 | if (this.options.animation) $tip.addClass('fade')
181 |
182 | var placement = typeof this.options.placement == 'function' ?
183 | this.options.placement.call(this, $tip[0], this.$element[0]) :
184 | this.options.placement
185 |
186 | var autoToken = /\s?auto?\s?/i
187 | var autoPlace = autoToken.test(placement)
188 | if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
189 |
190 | $tip
191 | .detach()
192 | .css({ top: 0, left: 0, display: 'block' })
193 | .addClass(placement)
194 | .data('bs.' + this.type, this)
195 |
196 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
197 | this.$element.trigger('inserted.bs.' + this.type)
198 |
199 | var pos = this.getPosition()
200 | var actualWidth = $tip[0].offsetWidth
201 | var actualHeight = $tip[0].offsetHeight
202 |
203 | if (autoPlace) {
204 | var orgPlacement = placement
205 | var viewportDim = this.getPosition(this.$viewport)
206 |
207 | placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
208 | placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
209 | placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
210 | placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
211 | placement
212 |
213 | $tip
214 | .removeClass(orgPlacement)
215 | .addClass(placement)
216 | }
217 |
218 | var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
219 |
220 | this.applyPlacement(calculatedOffset, placement)
221 |
222 | var complete = function() {
223 | var prevHoverState = that.hoverState
224 | that.$element.trigger('shown.bs.' + that.type)
225 | that.hoverState = null
226 |
227 | if (prevHoverState == 'out') that.leave(that)
228 | }
229 |
230 | $.support.transition && this.$tip.hasClass('fade') ?
231 | $tip
232 | .one('bsTransitionEnd', complete)
233 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
234 | complete()
235 | }
236 | }
237 |
238 | Tooltip.prototype.applyPlacement = function(offset, placement) {
239 | var $tip = this.tip()
240 | var width = $tip[0].offsetWidth
241 | var height = $tip[0].offsetHeight
242 |
243 | // manually read margins because getBoundingClientRect includes difference
244 | var marginTop = parseInt($tip.css('margin-top'), 10)
245 | var marginLeft = parseInt($tip.css('margin-left'), 10)
246 |
247 | // we must check for NaN for ie 8/9
248 | if (isNaN(marginTop)) marginTop = 0
249 | if (isNaN(marginLeft)) marginLeft = 0
250 |
251 | offset.top += marginTop
252 | offset.left += marginLeft
253 |
254 | // $.fn.offset doesn't round pixel values
255 | // so we use setOffset directly with our own function B-0
256 | $.offset.setOffset($tip[0], $.extend({
257 | using: function(props) {
258 | $tip.css({
259 | top: Math.round(props.top),
260 | left: Math.round(props.left)
261 | })
262 | }
263 | }, offset), 0)
264 |
265 | $tip.addClass('in')
266 |
267 | // check to see if placing tip in new offset caused the tip to resize itself
268 | var actualWidth = $tip[0].offsetWidth
269 | var actualHeight = $tip[0].offsetHeight
270 |
271 | if (placement == 'top' && actualHeight != height) {
272 | offset.top = offset.top + height - actualHeight
273 | }
274 |
275 | var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
276 |
277 | if (delta.left) offset.left += delta.left
278 | else offset.top += delta.top
279 |
280 | var isVertical = /top|bottom/.test(placement)
281 | var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
282 | var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
283 |
284 | $tip.offset(offset)
285 | this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
286 | }
287 |
288 | Tooltip.prototype.replaceArrow = function(delta, dimension, isVertical) {
289 | this.arrow()
290 | .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
291 | .css(isVertical ? 'top' : 'left', '')
292 | }
293 |
294 | Tooltip.prototype.setContent = function() {
295 | var $tip = this.tip()
296 | var title = this.getTitle()
297 |
298 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
299 | $tip.removeClass('fade in top bottom left right')
300 | }
301 |
302 | Tooltip.prototype.hide = function(callback) {
303 | var that = this
304 | var $tip = $(this.$tip)
305 | var e = $.Event('hide.bs.' + this.type)
306 |
307 | function complete() {
308 | if (that.hoverState != 'in') $tip.detach()
309 | if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
310 | that.$element
311 | .removeAttr('aria-describedby')
312 | .trigger('hidden.bs.' + that.type)
313 | }
314 | callback && callback()
315 | }
316 |
317 | this.$element.trigger(e)
318 |
319 | if (e.isDefaultPrevented()) return
320 |
321 | $tip.removeClass('in')
322 |
323 | $.support.transition && $tip.hasClass('fade') ?
324 | $tip
325 | .one('bsTransitionEnd', complete)
326 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
327 | complete()
328 |
329 | this.hoverState = null
330 |
331 | return this
332 | }
333 |
334 | Tooltip.prototype.fixTitle = function() {
335 | var $e = this.$element
336 | if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
337 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
338 | }
339 | }
340 |
341 | Tooltip.prototype.hasContent = function() {
342 | return this.getTitle()
343 | }
344 |
345 | Tooltip.prototype.getPosition = function($element) {
346 | $element = $element || this.$element
347 |
348 | var el = $element[0]
349 | var isBody = el.tagName == 'BODY'
350 |
351 | var elRect = el.getBoundingClientRect()
352 | if (elRect.width == null) {
353 | // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
354 | elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
355 | }
356 | var isSvg = window.SVGElement && el instanceof window.SVGElement
357 | // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
358 | // See https://github.com/twbs/bootstrap/issues/20280
359 | var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
360 | var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
361 | var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
362 |
363 | return $.extend({}, elRect, scroll, outerDims, elOffset)
364 | }
365 |
366 | Tooltip.prototype.getCalculatedOffset = function(placement, pos, actualWidth, actualHeight) {
367 | return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
368 | placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
369 | placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
370 | /* placement == 'right' */
371 | { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
372 |
373 | }
374 |
375 | Tooltip.prototype.getViewportAdjustedDelta = function(placement, pos, actualWidth, actualHeight) {
376 | var delta = { top: 0, left: 0 }
377 | if (!this.$viewport) return delta
378 |
379 | var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
380 | var viewportDimensions = this.getPosition(this.$viewport)
381 |
382 | if (/right|left/.test(placement)) {
383 | var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
384 | var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
385 | if (topEdgeOffset < viewportDimensions.top) { // top overflow
386 | delta.top = viewportDimensions.top - topEdgeOffset
387 | } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
388 | delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
389 | }
390 | } else {
391 | var leftEdgeOffset = pos.left - viewportPadding
392 | var rightEdgeOffset = pos.left + viewportPadding + actualWidth
393 | if (leftEdgeOffset < viewportDimensions.left) { // left overflow
394 | delta.left = viewportDimensions.left - leftEdgeOffset
395 | } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
396 | delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
397 | }
398 | }
399 |
400 | return delta
401 | }
402 |
403 | Tooltip.prototype.getTitle = function() {
404 | var title
405 | var $e = this.$element
406 | var o = this.options
407 |
408 | title = $e.attr('data-original-title') ||
409 | (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
410 |
411 | return title
412 | }
413 |
414 | Tooltip.prototype.getUID = function(prefix) {
415 | do prefix += ~~(Math.random() * 1000000)
416 | while (document.getElementById(prefix))
417 | return prefix
418 | }
419 |
420 | Tooltip.prototype.tip = function() {
421 | if (!this.$tip) {
422 | this.$tip = $(this.options.template)
423 | if (this.$tip.length != 1) {
424 | throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
425 | }
426 | }
427 | return this.$tip
428 | }
429 |
430 | Tooltip.prototype.arrow = function() {
431 | return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
432 | }
433 |
434 | Tooltip.prototype.enable = function() {
435 | this.enabled = true
436 | }
437 |
438 | Tooltip.prototype.disable = function() {
439 | this.enabled = false
440 | }
441 |
442 | Tooltip.prototype.toggleEnabled = function() {
443 | this.enabled = !this.enabled
444 | }
445 |
446 | Tooltip.prototype.toggle = function(e) {
447 | var self = this
448 | if (e) {
449 | self = $(e.currentTarget).data('bs.' + this.type)
450 | if (!self) {
451 | self = new this.constructor(e.currentTarget, this.getDelegateOptions())
452 | $(e.currentTarget).data('bs.' + this.type, self)
453 | }
454 | }
455 |
456 | if (e) {
457 | self.inState.click = !self.inState.click
458 | if (self.isInStateTrue()) self.enter(self)
459 | else self.leave(self)
460 | } else {
461 | self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
462 | }
463 | }
464 |
465 | Tooltip.prototype.destroy = function() {
466 | var that = this
467 | clearTimeout(this.timeout)
468 | this.hide(function() {
469 | that.$element.off('.' + that.type).removeData('bs.' + that.type)
470 | if (that.$tip) {
471 | that.$tip.detach()
472 | }
473 | that.$tip = null
474 | that.$arrow = null
475 | that.$viewport = null
476 | that.$element = null
477 | })
478 | }
479 |
480 |
481 | // TOOLTIP PLUGIN DEFINITION
482 | // =========================
483 |
484 | function Plugin(option) {
485 | return this.each(function() {
486 | var $this = $(this)
487 | var data = $this.data('bs.tooltip')
488 | var options = typeof option == 'object' && option
489 |
490 | if (!data && /destroy|hide/.test(option)) return
491 | if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
492 | if (typeof option == 'string') data[option]()
493 | })
494 | }
495 |
496 | var old = $.fn.tooltip
497 |
498 | $.fn.tooltip = Plugin
499 | $.fn.tooltip.Constructor = Tooltip
500 |
501 |
502 | // TOOLTIP NO CONFLICT
503 | // ===================
504 |
505 | $.fn.tooltip.noConflict = function() {
506 | $.fn.tooltip = old
507 | return this
508 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TrelloExport
2 |
3 | TrelloExport is a Chrome extension to export data from Trello to Excel, Markdown, HTML (with Twig templates) OPML and CSV.
4 |
5 | You can find it on [Chrome Web Store](https://chrome.google.com/webstore/detail/trelloexport/kmmnaeamjfdnbhljpedgfchjbkbomahp).
6 |
7 | TrelloExport now has a dedicated website at [https://trelloexport.trapias.it/](https://trelloexport.trapias.it/)
8 |
9 | ## How to use
10 |
11 | Open a Trello Board, click Show Menu, More, Print and Export, TrelloExport.
12 |
13 | ## Support
14 |
15 | Please check [TrelloExport Wiki](https://github.com/trapias/trelloExport/wiki) for help first. If you have some problems, check the [troubleshooting guide](https://github.com/trapias/trelloExport/wiki/Troubleshooting).
16 |
17 | If you cannot find a solution, or would like some new feature to be implemented, please open issues at [Github](https://github.com/trapias/trelloExport/issues).
18 |
19 |
20 |
21 | ## Release history
22 |
23 | ### Version 1.9.81
24 | - fix loop in loading cards when using "select lists in current board" option
25 |
26 | ### Version 1.9.80
27 | - complete async data loading
28 |
29 | ### Version 1.9.79
30 | - async data loading
31 | - fix exporting cards in a list when requesting to export checklists
32 |
33 | ### Version 1.9.78
34 | - Injection of TrelloExport button for Chinese (Traditional) language
35 |
36 | ### Version 1.9.77
37 | - Improved injection of TrelloExport button in menu for more languages
38 |
39 |
40 | ### Version 1.9.76
41 | - Improved injection of TrelloExport button in menu
42 | - Added error monitoring for 429 (rate limit) and 504 (timeout) errors
43 | - Added small delays between API requests to reduce rate limit issues
44 |
45 | ### Version 1.9.75
46 | - fixed injection to adapt to modified Trello website
47 |
48 | ### Version 1.9.73:
49 | - update jquery
50 | - fix OPML export of comments due date, issue #91
51 | - improvements for issue #29
52 |
53 | ### Version 1.9.72:
54 | - finally restored the capability to load templates from external URLs, issues #86 and #87
55 |
56 | ### Version 1.9.71:
57 | - Manifest v3
58 | - checklist items' due date, assignee and status added to checklists' mode excel export
59 |
60 | ### Version 1.9.70:
61 | - Load Trello Plus Spent/Estimate in comments
62 |
63 | ### Version 1.9.69:
64 |
65 | - bugfix columns handling in loading data (issue #74)
66 |
67 | ### Version 1.9.68:
68 |
69 | - avoid duplicate header row before archived cards in CSV export (issue #76)
70 | - export the cards "start" field (issue #84)
71 |
72 | ### Version 1.9.67
73 |
74 | - Added the HTTP header "x-trello-user-agent-extension" to all AJAX calls to Trello, trying to find a solution for https://github.com/trapias/TrelloExport/issues/81
75 |
76 | ### Version 1.9.66
77 |
78 | - Added the dueComplete (bool) field to exported columns
79 |
80 | ### Version 1.9.65
81 |
82 | - fix exporting of Archived items to Excel and CSV
83 |
84 | ### Version 1.9.64
85 |
86 | - fix some UI defects for the "export columns" dropdown
87 | - new CSV export type
88 |
89 | ### Version 1.9.63
90 |
91 | - fix unshowing button on team boards (issue #65, thanks [Teemu](https://github.com/varmais)
92 |
93 | ### Version 1.9.62
94 |
95 | - fix [issue #55](https://github.com/trapias/TrelloExport/issues/55), Export Done and Done By is missing for archived cards
96 | - sort labels alphabetically
97 |
98 | ### Version 1.9.61
99 |
100 | - fix error in markdown export [issue #56](https://github.com/trapias/TrelloExport/issues/56)
101 |
102 | ### Version 1.9.60
103 |
104 | - added MIT License (thanks [Mathias](https://github.com/mtn-gc))
105 | - updated [Bridge24](https://bridge24.com/trello/?afmc=1w) adv
106 |
107 | ### Version 1.9.59
108 |
109 | - HTML Twig: added "linkdoi" function to automatically link Digital Object Identifier (DOI) numbers to their URL, see [http://www.doi.org/](http://www.doi.org/), used in Bibliography template
110 | - Apply filters with AND (all must match) or OR (match any) condition, [Issue #38](https://github.com/trapias/TrelloExport/issues/38)
111 |
112 | ### Version 1.9.58
113 |
114 | - modified description in manifest to hopefully improve Chrome Web Store indexing
115 | - really fix columns loading
116 | - fix custom fields duplicates in excel
117 |
118 | ### Version 1.9.57
119 |
120 | - fix columns loading
121 |
122 | ### Version 1.9.56
123 |
124 | - enable export of custom fields for the 'Multiple Boards' type of export (please see the [Wiki](https://github.com/trapias/TrelloExport/wiki) for limits)
125 |
126 | ### Version 1.9.55
127 |
128 | Fixing errors in Excel export:
129 |
130 | - fix exporting of custom fields (include only if requested)
131 | - fix exporting of custom fields saved to localstorage
132 |
133 | ### Version 1.9.54
134 |
135 | - bugfix: export checklists with no items when selecting "one row per each checklist item"
136 | - new feature: save selected columns to localStorage ([issue #48](https://github.com/trapias/TrelloExport/issues/48))
137 |
138 | ### Version 1.9.53
139 |
140 | - new look: the options dialog is now built with [Tingle](https://robinparisi.github.io/tingle/)
141 | - new sponsor: support open source development! [read the blog post](https://trapias.github.io/blog/2018/06/19/TrelloExport-1.9.53)
142 |
143 | ### Version 1.9.52
144 |
145 | - avoid saving local CSS to localstorage
146 | - fix filters (reopened issue [issue #45](https://github.com/trapias/TrelloExport/issues/45)
147 | - paginate loading of cards in bunchs of 300 (fix [issue #47](https://github.com/trapias/TrelloExport/issues/47) due to recent API changes, see https://trello.com/c/8MJOLSCs/10-limit-actions-for-cards-requests)
148 |
149 | ### Version 1.9.51
150 |
151 | - bugfix export of checklists, comments and attachments to Excel
152 | - change "prefix" filters description to "string": all filters act as "string contains", no more "string starts with" since version 1.9.40
153 |
154 | ### Version 1.9.50
155 |
156 | - bugfix due date exported as "invalid date" in excel and markdown
157 | - filters back working, [issue #45](https://github.com/trapias/TrelloExport/issues/45)
158 |
159 | ### Version 1.9.49
160 |
161 | - bugfix encoding (again), [issue #43](https://github.com/trapias/TrelloExport/issues/43)
162 |
163 | ### Version 1.9.48
164 |
165 | - bugfix HTML encoding for multiple properties
166 | - small fixes in templates
167 | - two slightly different Newsletter templates
168 |
169 | ### Version 1.9.47
170 |
171 | - responsive images in Bibliography template
172 | - fix double encoding of card description
173 |
174 | ### Version 1.9.46
175 |
176 | - fix new "clear localStorage" button position
177 |
178 | ### Version 1.9.45
179 |
180 | - Added a button to clear all settings saved to localStorage
181 | - new jsonLabels array for labels in data
182 | - updated HTML default template with labels
183 |
184 | ### Version 1.9.44
185 |
186 | Dummy release needed to update Chrome Web Store, wrong blog article link!
187 |
188 | ### Version 1.9.43
189 |
190 | New SPONSORED feature: Twig templates for HTML export. See the [BLOG POST](http://trapias.github.io/blog/2018/04/27/TrelloExport-1.9.43) for more info!
191 |
192 | ### Version 1.9.42
193 |
194 | Released 04/14/2018:
195 |
196 | - new organization name column in Excel exports ([issue #30](https://github.com/trapias/TrelloExport/issues30))
197 | - custom fields working again following Trello API changes ([issue #31](https://github.com/trapias/TrelloExport/issues30)), but not for 'multiple boards' export option.
198 |
199 | ### Version 1.9.41
200 |
201 | Released 03/27/2018:
202 |
203 | - persist TrelloExport options to localStorage: CSS, selected export mode, selected export type, name of 'Done' list ([issue #24](https://github.com/trapias/TrelloExport/issues/24))
204 | - fix due date locale
205 | - expand flag to export archived cards to all kind of items, and filter consequently
206 | - list boards from all available organizations with the "multiple boards" export type
207 |
208 | ### Version 1.9.40
209 |
210 | A couple of fixes, released 11/12/2017:
211 |
212 | - https://github.com/trapias/TrelloExport/issues/28 ok with Done prefix
213 | - contains vs startsWith filters for the "done" function
214 |
215 | ### Version 1.9.39
216 |
217 | Released 08/02/2017:
218 |
219 | - fix custom fields loading ([issue #27](https://github.com/trapias/TrelloExport/issues/27))
220 | - fix card info export to MD ([issue #25](https://github.com/trapias/TrelloExport/issues/25))
221 |
222 | ### Version 1.9.38
223 |
224 | Released 05/12/2017:
225 |
226 | - css cleanup
227 | - re-enabled tooltips
228 | - export custom fields (pluginData handled with the "Custom Fields" Power-Up) to Excel, (issue #22 https://github.com/trapias/TrelloExport/issues/22)
229 |
230 | ### Version 1.9.37
231 |
232 | Released 05/07/2017:
233 |
234 | Bugfix multiple css issues and a bad bug avoiding the "add member" function to work properly, all due to the introduction of bootstrap css and javascript to use the bootstrap-multiselect plugin; now removed bootstrap and manually handled multiselect missing functionalities. Temporary disabled tooltips, based on bootstrap.
235 |
236 | ### Version 1.9.36
237 |
238 | Released 04/25/2017:
239 |
240 | - filter by list name, card name or label name
241 | - help tooltips
242 |
243 | ### Version 1.9.35
244 |
245 | Fixed a css conflict that caused Trello header bar to loose height.
246 |
247 | ### Version 1.9.34
248 |
249 | Released 04/24/2017:
250 |
251 | - only show columns chooser for Excel exports
252 | - can now set a custom css for HTML export
253 | - can now check/uncheck all columns to export
254 |
255 | ### Version 1.9.33
256 |
257 | Released 04/24/2017:
258 |
259 | - new data field dateLastActivity exported (issue #18 https://github.com/trapias/TrelloExport/issues/18)
260 | - new data field numberOfComments exported (issue #19 https://github.com/trapias/TrelloExport/issues/19)
261 | - new option to choose which columns to export to Excel (issue #17 https://github.com/trapias/TrelloExport/issues/17)
262 |
263 | ### Version 1.9.32
264 |
265 | Enhancements:
266 |
267 | - hopefully fixed bug with member fullName reading
268 | - new option to export labels and members to Excel rows, like already available for checklist items (issue #15 https://github.com/trapias/TrelloExport/issues/15)
269 | - new option to show attached images inline for Markdown and HTML exports (issue #16 https://github.com/trapias/TrelloExport/issues/16)
270 |
271 | ### Version 1.9.31
272 |
273 | Bugfix release:
274 |
275 | - fix due date format in Excel export (issue #12)
276 | - fix missing export of archived cards (issue #13)
277 |
278 | ### Version 1.9.30
279 |
280 | New CSS and options to format HTML exported files.
281 |
282 | - fix 1.9.29 beta (not published to Chrome Web Store)
283 | - finalize new css for HTML exports
284 |
285 | ### Version 1.9.28
286 |
287 | - fix cards loading: something is broken with the paginated loading introduced with version 1.9.25; to be further investigated
288 |
289 | ### Version 1.9.27
290 |
291 | - fix ajax.fail functions
292 | - fix loading boards when current board does not belong to any organization
293 |
294 | ### Version 1.9.26
295 |
296 | - export points estimate and consumed from Card titles based on Scrum for Trello
297 | - improved regex for Trello Plus estimate/spent in card titles
298 |
299 | Changes merged from [pull request #11](https://github.com/trapias/TrelloExport/pull/11) by [Chris](https://github.com/collisdigital), thank you!
300 |
301 | ### Version 1.9.25
302 |
303 | New feature: paginate cards loading, so to be able to load all cards even when exceeding the Trello API limit of 1000 records per call.
304 |
305 | Please consider this a beta: it's not yet available on the Chrome Web Store, so if you want to try it please install locally (see below).
306 |
307 | ### Version 1.9.24
308 |
309 | New features:
310 |
311 | - new checkboxes to enable/disable exporting of comments, checklist items and attachments
312 | - new option to export checklist items to rows, for Excel only
313 |
314 | ### Version 1.9.23
315 |
316 | Added new capability to **export to OPML**.
317 |
318 | More in this [blog post](http://trapias.github.io/blog/trelloexport-1-9-23).
319 |
320 | ### Version 1.9.22
321 |
322 | A couple of enhancements:
323 |
324 | - fix improper .md encoding as per [issue #8](https://github.com/trapias/TrelloExport/issues/8)
325 | - new option to decide whether to export archived items
326 |
327 | ### Version 1.9.21
328 |
329 | Some small improvements, and a new function for **exporting to HTML**.
330 |
331 | Details:
332 |
333 | - some UI (CSS) improvements for the options dialog
334 | - improved options dialog, resetting options when switching export type
335 | - new columns for Excel export: 'Total Checklist items' and 'Completed Checklist items'
336 | - better checklists formatting for Excel export
337 | - export to HTML
338 |
339 | #### HTML export mode
340 |
341 | The produced file is based on the Markdown export: the same output is generated and then converted to HTML with [showdown](https://github.com/showdownjs/showdown). Suggestions and ideas about how to evolve this are welcome.
342 |
343 | ### Version 1.9.20
344 |
345 | Fixes due to Trello UI changes.
346 |
347 | ### Version 1.9.19
348 |
349 | Partial refactoring: export flow has been rewritten to better handle data to enable different export modes. **It is now possible to export to Excel and Markdown**, and more export formats could now more easily be added.
350 |
351 | - refactoring export flow
352 | - updated jQuery Growl to version 1.3.1
353 | - new Markdown export mode
354 |
355 | More info in this [blog post](http://trapias.github.io/blog/trelloexport-1-9-19).
356 |
357 | ### Version 1.9.18
358 |
359 | Improving UI:
360 |
361 | - improve UI: better feedback message timing, yet still blocking UI during export due to sync ajax requests
362 | - removed data limit setting from options dialog - just use 1000, maximum allowed by Trello APIs
363 | - fix filename (YYYYMMDDhhmmss)
364 | - fix some UI issues
365 |
366 | ### Version 1.9.17
367 |
368 | Finally fixed (really) exporting ALL comments per card. We're now loading comments per single card from Trello API, which is **much slower** but assures all comments are exported.
369 |
370 | ### Version 1.9.15
371 |
372 | Finally fixing comments export: should have finally fixed exporting of comments and 'done time' calculations: thanks @fepsch for sharing a board and allowing to identify this annoying bug.
373 |
374 | ### Version 1.9.14
375 |
376 | Some bugfix and some new features.
377 |
378 | **Fixes**:
379 |
380 | - fixed card completion calculation when exporting multiple boards (fix getMoveCardAction and getCreateCardAction)
381 | - loading comments with a new function (getCommentCardActions), trying to fix issues with comments reported by some users; please give feedback
382 |
383 | **New features**:
384 |
385 | - formatting dates in user (browser) locale
386 | - added support for multiple 'Done' list names
387 | - added capability to optionally filter exported lists by name when exporting multiple boards
388 |
389 | Both the 'Done list name' and 'Filter lists by name' input boxes accept a comma-separated list of partial list names, i.e. just specify multiple names in the textbox like 'Done,Completed' (without apices). Lists will then be (case insensitively) matched when their name starts with one of these values.
390 |
391 | More info in this [blog post](http://trapias.github.io/blog/trelloexport-1-9-14/).
392 |
393 | ### Version 1.9.13
394 |
395 | Some (interesting, hopefully!) improvements with this version:
396 |
397 | - new 'DoneTime' column holding card completion time in days, hours, minutes and seconds, formatted as per [ISO8601](https://en.wikipedia.org/wiki/ISO_8601)
398 | - name (prefix) of 'Done' lists is now configurable, default "Done"
399 | - larger options dialog to better show options
400 | - export multiple (selected) boards
401 | - export multiple (selected) cards in a list (i.e. export single cards)
402 |
403 | More info in this [blog post](http://trapias.github.io/blog/trelloexport-1-9-13/). Give feeback!
404 |
405 | ### Version 1.9.12
406 |
407 | Fixed a bug by which the previously used BoardID was kept when navigating to another board.
408 |
409 | ### Version 1.9.11
410 |
411 | - added a new Options dialog
412 | - export full board or choosen list(s) only
413 | - add who and when item was completed to checklist items as of [issue #5](https://github.com/trapias/trelloExport/issues/5)
414 |
415 | More info in this [blog post](http://trapias.github.io/blog/trelloexport-1-9-11/).
416 |
417 | Your feedback is welcome, just comment on the blog, on the dedicated [Trello board](https://trello.com/b/MBnwUMwM/trelloexport) or open new issues.
418 |
419 | ### Version 1.9.10
420 |
421 | - adapt inject script to modified Trello layout
422 |
423 | ### Version 1.9.9
424 |
425 | - MAXCHARSPERCELL limit to avoid import errors in Excel (see https://support.office.com/en-nz/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa)
426 | - removed commentLimit, all comments are loaded (but attention to MAXCHARSPERCELL limit above, since comments go to a single cell)
427 | - growl notifications with jquery-growl http://ksylvest.github.io/jquery-growl/
428 |
429 | ### Version 1.9.8
430 |
431 | Use Trello API to get data, thanks https://github.com/mjearhart and https://github.com/llad:
432 |
433 | - https://github.com/llad/export-for-trello/pull/20
434 | - https://github.com/mjearhart/export-for-trello/commit/2a07561fdcdfd696dee0988cbe414cfd8374b572
435 |
436 | ### Version 1.9.7
437 |
438 | - fix issue #3 (copied comments missing in export)
439 |
440 | ### Version 1.9.6
441 |
442 | - order checklist items by position (issue #4)
443 | - minor code changes
444 |
445 | ### Version 1.9.5
446 |
447 | - code lint
448 | - ignore case in finding 'Done' lists (thanks [AlvonsiusAlbertNainupu](https://disqus.com/by/AlvonsiusAlbertNainupu/))
449 |
450 | ### Version 1.9.4
451 |
452 | Fixed bug preventing export when there are no archived cards.
453 |
454 | ### Version 1.9.3
455 |
456 | Whatsnew for version 1.9.3:
457 |
458 | - restored archived cards sheet
459 |
460 | ### Version 1.9.2
461 |
462 | Whatsnew for version 1.9.2:
463 |
464 | - fixed blocking error when duedate specified - thanks @ggyaniv for help
465 | - new button loading function: the "Export Excel" button should always appear now
466 |
467 | ### Version 1.9.1
468 |
469 | Whatsnew for version 1.9.1:
470 |
471 | - fixed button loading
472 | - some code cleaning
473 |
474 | ### Version 1.9.0
475 |
476 | Whatsnew for version 1.9.0
477 |
478 | - switched to SheetJS library to export to excel, cfr [https://github.com/SheetJS/js-xlsx](https://github.com/SheetJS/js-xlsx "https://github.com/SheetJS/js-xlsx")
479 | - unicode characters are now correctly exported to xlsx
480 |
481 | ### Version 1.8.9
482 |
483 | Whatsnew for version 1.8.9:
484 |
485 | - added column Card #
486 | - added columns memberCreator, datetimeCreated, datetimeDone and memberDone pulling modifications from [https://github.com/bmccormack/export-for-trello/blob/5b2b8b102b98ed2c49241105cb9e00e44d4e1e86/trelloexport.js](https://github.com/bmccormack/export-for-trello/blob/5b2b8b102b98ed2c49241105cb9e00e44d4e1e86/trelloexport.js "https://github.com/bmccormack/export-for-trello/blob/5b2b8b102b98ed2c49241105cb9e00e44d4e1e86/trelloexport.js")
487 | - added linq.min.js library to support linq queries for the above modifications
488 |
489 | #### Notes
490 |
491 | I modified the **escapeXML** function in **xlsx.js** to avoid errors with XML characters when loading the spreadsheet in Excel. I tested exporting quite big boards like Trello Development or Trello Resources and no more have issues with invalid characters.
492 | I put a couple of sample export files in the xlsx subfolder.
493 |
494 | **Columns**: the list of columns exported is now:
495 |
496 | columnHeadings = ['List', 'Card #', 'Title', 'Link', 'Description', 'Checklists', 'Comments', 'Attachments', 'Votes', 'Spent', 'Estimate', 'Created', 'CreatedBy', 'Due', 'Done', 'DoneBy', 'Members', 'Labels']
497 |
498 | ##### datetimeDone and memberDone
499 |
500 | These fields are calculated intercepting when a card was moved to the Done list. While bmccormack's code only checks for this list, I check for cards being moved to any list whose name starts with "Done" (e.g. using lists named "Done Bugfix", "Done New Feature" and so will work).
501 |
502 | #### Formatting
503 |
504 | I tried formatting data in a readable format, suggest changes if you don't like how it is now.
505 |
506 | **Comments** are formatted with [date - username] comment, e.g.:
507 |
508 | [2014-01-31 18:38:31 - kathyschultz1] the add-on for exporting to Excel is a good start, but I'm w/all who dream of a report that includes checklists and comments. Thanks Trello warriors!
509 |
510 | I added **commentLimit** to limit the number of comments to extract: play with the value (default 100) as per your needs.
511 | There are currently no limits in the number of checklists, checklist items or attachments.
512 |
513 | **Attachments** are listed in a similar way, with [filename] (bytes) url, e.g.:
514 |
515 | [chrome.jpg] (62806) https://trello-attachments.s3.amazonaws.com/4d5ea62fd76aa1136000000c/520a29971618ecef3c002181/dc1d95c904a04a6a986b775e55f58bd9/chrome.jpg
516 |
517 | **Excel formatting**: after opening the excel you will have to adjust columns widths and formatting. I normally align cells on top and wrap text to have a readable format - see the samples in xlsx.
518 |
519 | ## How to install
520 |
521 | Get it on the [Chrome Web Store](https://chrome.google.com/webstore/detail/trelloexport/kmmnaeamjfdnbhljpedgfchjbkbomahp).
522 |
523 | If you want to install from source, just follow these steps:
524 |
525 | 1. Download the repository as a zip file
526 | 2. Extract zip
527 | 3. Go to Chrome Exensions: [chrome://chrome/extensions/](chrome://chrome/extensions/)
528 | 4. Click on Developer Mode checkbox
529 | 5. Click on Load unpacked extension...
530 | 6. Select the folder containing the source files
531 | 7. Reload Trello
532 |
533 | ## How to use
534 |
535 | 1. From a board, click to show the menu in right sidebar
536 | 2. Click on Share, print, and export...
537 | 3. Click on TrelloExport
538 | 4. Choose options for export
539 | 5. Click "Export", wait for the process to complete and you get your file downloaded.
540 |
541 | ## Credits
542 |
543 | This is a fork of the original "Export for Trello" extension, available at [https://github.com/llad/export-for-trello](https://github.com/llad/export-for-trello).
544 |
--------------------------------------------------------------------------------
/lib/linq.min.js:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * linq.js - LINQ for JavaScript
3 | * ver 2.2.0.2 (Jan. 21th, 2011)
4 | *
5 | * created and maintained by neuecc
6 | * licensed under Microsoft Public License(Ms-PL)
7 | * http://neue.cc/
8 | * http://linqjs.codeplex.com/
9 | *--------------------------------------------------------------------------*/
10 | Enumerable=function(){var m="Single:sequence contains more than one element.",e=true,b=null,a=false,c=function(a){this.GetEnumerator=a};c.Choice=function(){var a=arguments[0]instanceof Array?arguments[0]:arguments;return new c(function(){return new f(g.Blank,function(){return this.Yield(a[Math.floor(Math.random()*a.length)])},g.Blank)})};c.Cycle=function(){var a=arguments[0]instanceof Array?arguments[0]:arguments;return new c(function(){var b=0;return new f(g.Blank,function(){if(b>=a.length)b=0;return this.Yield(a[b++])},g.Blank)})};c.Empty=function(){return new c(function(){return new f(g.Blank,function(){return a},g.Blank)})};c.From=function(j){if(j==b)return c.Empty();if(j instanceof c)return j;if(typeof j==i.Number||typeof j==i.Boolean)return c.Repeat(j,1);if(typeof j==i.String)return new c(function(){var b=0;return new f(g.Blank,function(){return b=e})};c.Repeat=function(d,a){return a!=b?c.Repeat(d).Take(a):new c(function(){return new f(g.Blank,function(){return this.Yield(d)},g.Blank)})};c.RepeatWithFinalize=function(a,e){a=d.CreateLambda(a);e=d.CreateLambda(e);return new c(function(){var c;return new f(function(){c=a()},function(){return this.Yield(c)},function(){if(c!=b){e(c);c=b}})})};c.Generate=function(a,e){if(e!=b)return c.Generate(a).Take(e);a=d.CreateLambda(a);return new c(function(){return new f(g.Blank,function(){return this.Yield(a())},g.Blank)})};c.ToInfinity=function(d,a){if(d==b)d=0;if(a==b)a=1;return new c(function(){var b;return new f(function(){b=d-a},function(){return this.Yield(b+=a)},g.Blank)})};c.ToNegativeInfinity=function(d,a){if(d==b)d=0;if(a==b)a=1;return new c(function(){var b;return new f(function(){b=d+a},function(){return this.Yield(b-=a)},g.Blank)})};c.Unfold=function(h,b){b=d.CreateLambda(b);return new c(function(){var d=e,c;return new f(g.Blank,function(){if(d){d=a;c=h;return this.Yield(c)}c=b(c);return this.Yield(c)},g.Blank)})};c.prototype={CascadeBreadthFirst:function(g,b){var h=this;g=d.CreateLambda(g);b=d.CreateLambda(b);return new c(function(){var i,k=0,j=[];return new f(function(){i=h.GetEnumerator()},function(){while(e){if(i.MoveNext()){j.push(i.Current());return this.Yield(b(i.Current(),k))}var f=c.From(j).SelectMany(function(a){return g(a)});if(!f.Any())return a;else{k++;j=[];d.Dispose(i);i=f.GetEnumerator()}}},function(){d.Dispose(i)})})},CascadeDepthFirst:function(g,b){var h=this;g=d.CreateLambda(g);b=d.CreateLambda(b);return new c(function(){var j=[],i;return new f(function(){i=h.GetEnumerator()},function(){while(e){if(i.MoveNext()){var f=b(i.Current(),j.length);j.push(i);i=c.From(g(i.Current())).GetEnumerator();return this.Yield(f)}if(j.length<=0)return a;d.Dispose(i);i=j.pop()}},function(){try{d.Dispose(i)}finally{c.From(j).ForEach(function(a){a.Dispose()})}})})},Flatten:function(){var h=this;return new c(function(){var j,i=b;return new f(function(){j=h.GetEnumerator()},function(){while(e){if(i!=b)if(i.MoveNext())return this.Yield(i.Current());else i=b;if(j.MoveNext())if(j.Current()instanceof Array){d.Dispose(i);i=c.From(j.Current()).SelectMany(g.Identity).Flatten().GetEnumerator();continue}else return this.Yield(j.Current());return a}},function(){try{d.Dispose(j)}finally{d.Dispose(i)}})})},Pairwise:function(b){var e=this;b=d.CreateLambda(b);return new c(function(){var c;return new f(function(){c=e.GetEnumerator();c.MoveNext()},function(){var d=c.Current();return c.MoveNext()?this.Yield(b(d,c.Current())):a},function(){d.Dispose(c)})})},Scan:function(i,g,j){if(j!=b)return this.Scan(i,g).Select(j);var h;if(g==b){g=d.CreateLambda(i);h=a}else{g=d.CreateLambda(g);h=e}var k=this;return new c(function(){var b,c,j=e;return new f(function(){b=k.GetEnumerator()},function(){if(j){j=a;if(!h){if(b.MoveNext())return this.Yield(c=b.Current())}else return this.Yield(c=i)}return b.MoveNext()?this.Yield(c=g(c,b.Current())):a},function(){d.Dispose(b)})})},Select:function(b){var e=this;b=d.CreateLambda(b);return new c(function(){var c,g=0;return new f(function(){c=e.GetEnumerator()},function(){return c.MoveNext()?this.Yield(b(c.Current(),g++)):a},function(){d.Dispose(c)})})},SelectMany:function(g,e){var h=this;g=d.CreateLambda(g);if(e==b)e=function(b,a){return a};e=d.CreateLambda(e);return new c(function(){var j,i=undefined,k=0;return new f(function(){j=h.GetEnumerator()},function(){if(i===undefined)if(!j.MoveNext())return a;do{if(i==b){var f=g(j.Current(),k++);i=c.From(f).GetEnumerator()}if(i.MoveNext())return this.Yield(e(j.Current(),i.Current()));d.Dispose(i);i=b}while(j.MoveNext());return a},function(){try{d.Dispose(j)}finally{d.Dispose(i)}})})},Where:function(b){b=d.CreateLambda(b);var e=this;return new c(function(){var c,g=0;return new f(function(){c=e.GetEnumerator()},function(){while(c.MoveNext())if(b(c.Current(),g++))return this.Yield(c.Current());return a},function(){d.Dispose(c)})})},OfType:function(c){var a;switch(c){case Number:a=i.Number;break;case String:a=i.String;break;case Boolean:a=i.Boolean;break;case Function:a=i.Function;break;default:a=b}return a===b?this.Where(function(a){return a instanceof c}):this.Where(function(b){return typeof b===a})},Zip:function(e,b){b=d.CreateLambda(b);var g=this;return new c(function(){var i,h,j=0;return new f(function(){i=g.GetEnumerator();h=c.From(e).GetEnumerator()},function(){return i.MoveNext()&&h.MoveNext()?this.Yield(b(i.Current(),h.Current(),j++)):a},function(){try{d.Dispose(i)}finally{d.Dispose(h)}})})},Join:function(m,i,h,k,j){i=d.CreateLambda(i);h=d.CreateLambda(h);k=d.CreateLambda(k);j=d.CreateLambda(j);var l=this;return new c(function(){var n,q,o=b,p=0;return new f(function(){n=l.GetEnumerator();q=c.From(m).ToLookup(h,g.Identity,j)},function(){while(e){if(o!=b){var c=o[p++];if(c!==undefined)return this.Yield(k(n.Current(),c));c=b;p=0}if(n.MoveNext()){var d=i(n.Current());o=q.Get(d).ToArray()}else return a}},function(){d.Dispose(n)})})},GroupJoin:function(l,h,e,j,i){h=d.CreateLambda(h);e=d.CreateLambda(e);j=d.CreateLambda(j);i=d.CreateLambda(i);var k=this;return new c(function(){var m=k.GetEnumerator(),n=b;return new f(function(){m=k.GetEnumerator();n=c.From(l).ToLookup(e,g.Identity,i)},function(){if(m.MoveNext()){var b=n.Get(h(m.Current()));return this.Yield(j(m.Current(),b))}return a},function(){d.Dispose(m)})})},All:function(b){b=d.CreateLambda(b);var c=e;this.ForEach(function(d){if(!b(d)){c=a;return a}});return c},Any:function(c){c=d.CreateLambda(c);var b=this.GetEnumerator();try{if(arguments.length==0)return b.MoveNext();while(b.MoveNext())if(c(b.Current()))return e;return a}finally{d.Dispose(b)}},Concat:function(e){var g=this;return new c(function(){var i,h;return new f(function(){i=g.GetEnumerator()},function(){if(h==b){if(i.MoveNext())return this.Yield(i.Current());h=c.From(e).GetEnumerator()}return h.MoveNext()?this.Yield(h.Current()):a},function(){try{d.Dispose(i)}finally{d.Dispose(h)}})})},Insert:function(h,b){var g=this;return new c(function(){var j,i,l=0,k=a;return new f(function(){j=g.GetEnumerator();i=c.From(b).GetEnumerator()},function(){if(l==h&&i.MoveNext()){k=e;return this.Yield(i.Current())}if(j.MoveNext()){l++;return this.Yield(j.Current())}return!k&&i.MoveNext()?this.Yield(i.Current()):a},function(){try{d.Dispose(j)}finally{d.Dispose(i)}})})},Alternate:function(a){a=c.Return(a);return this.SelectMany(function(b){return c.Return(b).Concat(a)}).TakeExceptLast()},Contains:function(f,b){b=d.CreateLambda(b);var c=this.GetEnumerator();try{while(c.MoveNext())if(b(c.Current())===f)return e;return a}finally{d.Dispose(c)}},DefaultIfEmpty:function(b){var g=this;return new c(function(){var c,h=e;return new f(function(){c=g.GetEnumerator()},function(){if(c.MoveNext()){h=a;return this.Yield(c.Current())}else if(h){h=a;return this.Yield(b)}return a},function(){d.Dispose(c)})})},Distinct:function(a){return this.Except(c.Empty(),a)},Except:function(e,b){b=d.CreateLambda(b);var g=this;return new c(function(){var h,i;return new f(function(){h=g.GetEnumerator();i=new n(b);c.From(e).ForEach(function(a){i.Add(a)})},function(){while(h.MoveNext()){var b=h.Current();if(!i.Contains(b)){i.Add(b);return this.Yield(b)}}return a},function(){d.Dispose(h)})})},Intersect:function(e,b){b=d.CreateLambda(b);var g=this;return new c(function(){var h,i,j;return new f(function(){h=g.GetEnumerator();i=new n(b);c.From(e).ForEach(function(a){i.Add(a)});j=new n(b)},function(){while(h.MoveNext()){var b=h.Current();if(!j.Contains(b)&&i.Contains(b)){j.Add(b);return this.Yield(b)}}return a},function(){d.Dispose(h)})})},SequenceEqual:function(h,f){f=d.CreateLambda(f);var g=this.GetEnumerator();try{var b=c.From(h).GetEnumerator();try{while(g.MoveNext())if(!b.MoveNext()||f(g.Current())!==f(b.Current()))return a;return b.MoveNext()?a:e}finally{d.Dispose(b)}}finally{d.Dispose(g)}},Union:function(e,b){b=d.CreateLambda(b);var g=this;return new c(function(){var j,h,i;return new f(function(){j=g.GetEnumerator();i=new n(b)},function(){var b;if(h===undefined){while(j.MoveNext()){b=j.Current();if(!i.Contains(b)){i.Add(b);return this.Yield(b)}}h=c.From(e).GetEnumerator()}while(h.MoveNext()){b=h.Current();if(!i.Contains(b)){i.Add(b);return this.Yield(b)}}return a},function(){try{d.Dispose(j)}finally{d.Dispose(h)}})})},OrderBy:function(b){return new j(this,b,a)},OrderByDescending:function(a){return new j(this,a,e)},Reverse:function(){var b=this;return new c(function(){var c,d;return new f(function(){c=b.ToArray();d=c.length},function(){return d>0?this.Yield(c[--d]):a},g.Blank)})},Shuffle:function(){var b=this;return new c(function(){var c;return new f(function(){c=b.ToArray()},function(){if(c.length>0){var b=Math.floor(Math.random()*c.length);return this.Yield(c.splice(b,1)[0])}return a},g.Blank)})},GroupBy:function(i,h,e,g){var j=this;i=d.CreateLambda(i);h=d.CreateLambda(h);if(e!=b)e=d.CreateLambda(e);g=d.CreateLambda(g);return new c(function(){var c;return new f(function(){c=j.ToLookup(i,h,g).ToEnumerable().GetEnumerator()},function(){while(c.MoveNext())return e==b?this.Yield(c.Current()):this.Yield(e(c.Current().Key(),c.Current()));return a},function(){d.Dispose(c)})})},PartitionBy:function(j,i,g,h){var l=this;j=d.CreateLambda(j);i=d.CreateLambda(i);h=d.CreateLambda(h);var k;if(g==b){k=a;g=function(b,a){return new o(b,a)}}else{k=e;g=d.CreateLambda(g)}return new c(function(){var b,n,o,m=[];return new f(function(){b=l.GetEnumerator();if(b.MoveNext()){n=j(b.Current());o=h(n);m.push(i(b.Current()))}},function(){var d;while((d=b.MoveNext())==e)if(o===h(j(b.Current())))m.push(i(b.Current()));else break;if(m.length>0){var f=k?g(n,c.From(m)):g(n,m);if(d){n=j(b.Current());o=h(n);m=[i(b.Current())]}else m=[];return this.Yield(f)}return a},function(){d.Dispose(b)})})},BufferWithCount:function(e){var b=this;return new c(function(){var c;return new f(function(){c=b.GetEnumerator()},function(){var b=[],d=0;while(c.MoveNext()){b.push(c.Current());if(++d>=e)return this.Yield(b)}return b.length>0?this.Yield(b):a},function(){d.Dispose(c)})})},Aggregate:function(c,b,a){return this.Scan(c,b,a).Last()},Average:function(a){a=d.CreateLambda(a);var c=0,b=0;this.ForEach(function(d){c+=a(d);++b});return c/b},Count:function(a){a=a==b?g.True:d.CreateLambda(a);var c=0;this.ForEach(function(d,b){if(a(d,b))++c});return c},Max:function(a){if(a==b)a=g.Identity;return this.Select(a).Aggregate(function(a,b){return a>b?a:b})},Min:function(a){if(a==b)a=g.Identity;return this.Select(a).Aggregate(function(a,b){return aa(c)?b:c})},MinBy:function(a){a=d.CreateLambda(a);return this.Aggregate(function(b,c){return a(b)")})},Force:function(){var a=this.GetEnumerator();try{while(a.MoveNext());}finally{d.Dispose(a)}},Let:function(b){b=d.CreateLambda(b);var e=this;return new c(function(){var g;return new f(function(){g=c.From(b(e)).GetEnumerator()},function(){return g.MoveNext()?this.Yield(g.Current()):a},function(){d.Dispose(g)})})},Share:function(){var e=this,d;return new c(function(){return new f(function(){if(d==b)d=e.GetEnumerator()},function(){return d.MoveNext()?this.Yield(d.Current()):a},g.Blank)})},MemoizeAll:function(){var h=this,e,d;return new c(function(){var c=-1;return new f(function(){if(d==b){d=h.GetEnumerator();e=[]}},function(){c++;return e.length<=c?d.MoveNext()?this.Yield(e[c]=d.Current()):a:this.Yield(e[c])},g.Blank)})},Catch:function(b){b=d.CreateLambda(b);var e=this;return new c(function(){var c;return new f(function(){c=e.GetEnumerator()},function(){try{return c.MoveNext()?this.Yield(c.Current()):a}catch(d){b(d);return a}},function(){d.Dispose(c)})})},Finally:function(b){b=d.CreateLambda(b);var e=this;return new c(function(){var c;return new f(function(){c=e.GetEnumerator()},function(){return c.MoveNext()?this.Yield(c.Current()):a},function(){try{d.Dispose(c)}finally{b()}})})},Trace:function(c,a){if(c==b)c="Trace";a=d.CreateLambda(a);return this.Do(function(b){console.log(c,":",a(b))})}};var g={Identity:function(a){return a},True:function(){return e},Blank:function(){}},i={Boolean:typeof e,Number:typeof 0,String:typeof"",Object:typeof{},Undefined:typeof undefined,Function:typeof function(){}},d={CreateLambda:function(a){if(a==b)return g.Identity;if(typeof a==i.String)if(a=="")return g.Identity;else if(a.indexOf("=>")==-1)return new Function("$,$$,$$$,$$$$","return "+a);else{var c=a.match(/^[(\s]*([^()]*?)[)\s]*=>(.*)/);return new Function(c[1],"return "+c[2])}return a},IsIEnumerable:function(b){if(typeof Enumerator!=i.Undefined)try{new Enumerator(b);return e}catch(c){}return a},Compare:function(a,b){return a===b?0:a>b?1:-1},Dispose:function(a){a!=b&&a.Dispose()}},k={Before:0,Running:1,After:2},f=function(d,f,g){var c=new p,b=k.Before;this.Current=c.Current;this.MoveNext=function(){try{switch(b){case k.Before:b=k.Running;d();case k.Running:if(f.apply(c))return e;else{this.Dispose();return a}case k.After:return a}}catch(g){this.Dispose();throw g;}};this.Dispose=function(){if(b!=k.Running)return;try{g()}finally{b=k.After}}},p=function(){var a=b;this.Current=function(){return a};this.Yield=function(b){a=b;return e}},j=function(f,b,c,e){var a=this;a.source=f;a.keySelector=d.CreateLambda(b);a.descending=c;a.parent=e};j.prototype=new c;j.prototype.CreateOrderedEnumerable=function(a,b){return new j(this.source,a,b,this)};j.prototype.ThenBy=function(b){return this.CreateOrderedEnumerable(b,a)};j.prototype.ThenByDescending=function(a){return this.CreateOrderedEnumerable(a,e)};j.prototype.GetEnumerator=function(){var h=this,d,c,e=0;return new f(function(){d=[];c=[];h.source.ForEach(function(b,a){d.push(b);c.push(a)});var a=l.Create(h,b);a.GenerateKeys(d);c.sort(function(b,c){return a.Compare(b,c)})},function(){return e0:c.prototype.Any.apply(this,arguments)};h.prototype.Count=function(a){return a==b?this.source.length:c.prototype.Count.apply(this,arguments)};h.prototype.ElementAt=function(a){return 0<=a&&a0?this.source[0]:c.prototype.First.apply(this,arguments)};h.prototype.FirstOrDefault=function(a,d){return d!=b?c.prototype.FirstOrDefault.apply(this,arguments):this.source.length>0?this.source[0]:a};h.prototype.Last=function(d){var a=this;return d==b&&a.source.length>0?a.source[a.source.length-1]:c.prototype.Last.apply(a,arguments)};h.prototype.LastOrDefault=function(d,e){var a=this;return e!=b?c.prototype.LastOrDefault.apply(a,arguments):a.source.length>0?a.source[a.source.length-1]:d};h.prototype.Skip=function(d){var b=this.source;return new c(function(){var c;return new f(function(){c=d<0?0:d},function(){return c0?this.Yield(b[--c]):a},g.Blank)})};h.prototype.SequenceEqual=function(d,e){return(d instanceof h||d instanceof Array)&&e==b&&c.From(d).Count()!=this.Count()?a:c.prototype.SequenceEqual.apply(this,arguments)};h.prototype.ToString=function(a,d){if(d!=b||!(this.source instanceof Array))return c.prototype.ToString.apply(this,arguments);if(a==b)a="";return this.source.join(a)};h.prototype.GetEnumerator=function(){var b=this.source,c=0;return new f(g.Blank,function(){return c