PageSourceSearch

https://adamkaramanlis.com/modules/system/assets/js/framework.js

js adamkaramanlis.com collected 2026-10-04 21:17:55 UTC 32,368 bytes, 893 lines download raw bytes

1/* ========================================================================
2 * OctoberCMS: front-end JavaScript framework
3 * http://octobercms.com
4 * ========================================================================
5 * Copyright 2016-2020 Alexey Bobkov, Samuel Georges
6 * ======================================================================== */
7
8if (window.jQuery === undefined) {
9    throw new Error('The jQuery library is not loaded. The OctoberCMS framework cannot be initialized.');
10}
11if (window.jQuery.request !== undefined) {
12    throw new Error('The OctoberCMS framework is already loaded.');
13}
14
15+function ($) { "use strict";
16
17    var Request = function (element, handler, options) {
18        var $el = this.$el = $(element);
19        this.options = options || {};
20
21        /*
22         * Validate handler name
23         */
24        if (handler === undefined) {
25            throw new Error('The request handler name is not specified.')
26        }
27
28        if (!handler.match(/^(?:\w+\:{2})?on*/)) {
29            throw new Error('Invalid handler name. The correct handler name format is: "onEvent".')
30        }
31
32        /*
33         * Prepare the options and execute the request
34         */
35        var $form = options.form ? $(options.form) : $el.closest('form'),
36            $triggerEl = !!$form.length ? $form : $el,
37            context = { handler: handler, options: options }
38
39        $el.trigger('ajaxSetup', [context])
40        var _event = jQuery.Event('oc.beforeRequest')
41        $triggerEl.trigger(_event, context)
42        if (_event.isDefaultPrevented()) return
43
44        var loading = options.loading !== undefined ? options.loading : null,
45            url = options.url !== undefined ? options.url : window.location.href,
46            isRedirect = options.redirect !== undefined && options.redirect.length,
47            useFlash = options.flash !== undefined,
48            useFiles = options.files !== undefined
49
50        if (useFiles && typeof FormData === 'undefined') {
51            console.warn('This browser does not support file uploads via FormData')
52            useFiles = false
53        }
54
55        if ($.type(loading) == 'string') {
56            loading = $(loading)
57        }
58
59        /*
60         * Request headers
61         */
62        var requestHeaders = {
63            'X-OCTOBER-REQUEST-HANDLER': handler,
64            'X-OCTOBER-REQUEST-PARTIALS': this.extractPartials(options.update)
65        }
66
67        if (useFlash) {
68            requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1
69        }
70
71        var csrfToken = getXSRFToken()
72        if (csrfToken) {
73            requestHeaders['X-XSRF-TOKEN'] = csrfToken
74        }
75
76        /*
77         * Request data
78         */
79        var requestData,
80            inputName,
81            data = {}
82
83        $.each($el.parents('[data-request-data]').toArray().reverse(), function extendRequest() {
84            $.extend(data, paramToObj('data-request-data', $(this).data('request-data')))
85        })
86
87        if ($el.is(':input') && !$form.length) {
88            inputName = $el.attr('name')
89            if (inputName !== undefined && options.data[inputName] === undefined) {
90                options.data[inputName] = $el.val()
91            }
92        }
93
94        if (options.data !== undefined && !$.isEmptyObject(options.data)) {
95            $.extend(data, options.data)
96        }
97
98        if (useFiles) {
99            requestData = new FormData($form.length ? $form.get(0) : undefined)
100
101            if ($el.is(':file') && inputName) {
102                $.each($el.prop('files'), function() {
103                    requestData.append(inputName, this)
104                })
105
106                delete data[inputName]
107            }
108
109            $.each(data, function(key) {
110                requestData.append(key, this)
111            })
112        }
113        else {
114            requestData = [$form.serialize(), $.param(data)].filter(Boolean).join('&')
115        }
116
117        /*
118         * Request options
119         */
120        var requestOptions = {
121            url: url,
122            crossDomain: false,
123            global: options.ajaxGlobal,
124            context: context,
125            headers: requestHeaders,
126            success: function(data, textStatus, jqXHR) {
127                /*
128                 * Halt here if beforeUpdate() or data-request-before-update returns false
129                 */
130                if (this.options.beforeUpdate.apply(this, [data, textStatus, jqXHR]) === false) return
131                if (options.evalBeforeUpdate && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalBeforeUpdate+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') === false) return
132
133                /*
134                 * Trigger 'ajaxBeforeUpdate' on the form, halt if event.preventDefault() is called
135                 */
136                var _event = jQuery.Event('ajaxBeforeUpdate')
137                $triggerEl.trigger(_event, [context, data, textStatus, jqXHR])
138                if (_event.isDefaultPrevented()) return
139
140                if (useFlash && data['X_OCTOBER_FLASH_MESSAGES']) {
141                    $.each(data['X_OCTOBER_FLASH_MESSAGES'], function(type, message) {
142                        requestOptions.handleFlashMessage(message, type)
143                    })
144                }
145
146                /*
147                 * Proceed with the update process
148                 */
149                var updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR)
150
151                updatePromise.done(function() {
152                    $triggerEl.trigger('ajaxSuccess', [context, data, textStatus, jqXHR])
153                    options.evalSuccess && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalSuccess+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))')
154                })
155
156                return updatePromise
157            },
158            error: function(jqXHR, textStatus, errorThrown) {
159                var errorMsg,
160                    updatePromise = $.Deferred()
161
162                if ((window.ocUnloading !== undefined && window.ocUnloading) || errorThrown == 'abort')
163                    return
164
165                /*
166                 * Disable redirects
167                 */
168                isRedirect = false
169                options.redirect = null
170
171                /*
172                 * Error 406 is a "smart error" that returns response object that is
173                 * processed in the same fashion as a successful response.
174                 */
175                if (jqXHR.status == 406 && jqXHR.responseJSON) {
176                    errorMsg = jqXHR.responseJSON['X_OCTOBER_ERROR_MESSAGE']
177                    updatePromise = requestOptions.handleUpdateResponse(jqXHR.responseJSON, textStatus, jqXHR)
178                }
179                /*
180                 * Standard error with standard response text
181                 */
182                else {
183                    errorMsg = jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText
184                    updatePromise.resolve()
185                }
186
187                updatePromise.done(function() {
188                    $el.data('error-message', errorMsg)
189
190                    /*
191                     * Trigger 'ajaxError' on the form, halt if event.preventDefault() is called
192                     */
193                    var _event = jQuery.Event('ajaxError')
194                    $triggerEl.trigger(_event, [context, errorMsg, textStatus, jqXHR])
195                    if (_event.isDefaultPrevented()) return
196
197                    /*
198                     * Halt here if the data-request-error attribute returns false
199                     */
200                    if (options.evalError && eval('(function($el, context, errorMsg, textStatus, jqXHR) {'+options.evalError+'}.call($el.get(0), $el, context, errorMsg, textStatus, jqXHR))') === false)
201                        return
202
203                    requestOptions.handleErrorMessage(errorMsg)
204                })
205
206                return updatePromise
207            },
208            complete: function(data, textStatus, jqXHR) {
209                $triggerEl.trigger('ajaxComplete', [context, data, textStatus, jqXHR])
210                options.evalComplete && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalComplete+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))')
211            },
212
213            /*
214             * Custom function, requests confirmation from the user
215             */
216            handleConfirmMessage: function(message) {
217                var _event = jQuery.Event('ajaxConfirmMessage')
218
219                _event.promise = $.Deferred()
220                if ($(window).triggerHandler(_event, [message]) !== undefined) {
221                    _event.promise.done(function() {
222                        options.confirm = null
223                        new Request(element, handler, options)
224                    })
225                    return false
226                }
227
228                if (_event.isDefaultPrevented()) return
229                if (message) return confirm(message)
230            },
231
232            /*
233             * Custom function, display an error message to the user
234             */
235            handleErrorMessage: function(message) {
236                var _event = jQuery.Event('ajaxErrorMessage')
237                $(window).trigger(_event, [message])
238                if (_event.isDefaultPrevented()) return
239                if (message) alert(message)
240            },
241
242            /*
243             * Custom function, focus fields with errors
244             */
245            handleValidationMessage: function(message, fields) {
246                $triggerEl.trigger('ajaxValidation', [context, message, fields])
247
248                var isFirstInvalidField = true
249                $.each(fields, function focusErrorField(fieldName, fieldMessages) {
250                    fieldName = fieldName.replace(/\.(\w+)/g, '[$1]')
251
252                    var fieldElement = $form.find('[name="'+fieldName+'"], [name="'+fieldName+'[]"], [name$="['+fieldName+']"], [name$="['+fieldName+'][]"]').filter(':enabled').first()
253                    if (fieldElement.length > 0) {
254
255                        var _event = jQuery.Event('ajaxInvalidField')
256                        $(window).trigger(_event, [fieldElement.get(0), fieldName, fieldMessages, isFirstInvalidField])
257
258                        if (isFirstInvalidField) {
259                            if (!_event.isDefaultPrevented()) fieldElement.focus()
260                            isFirstInvalidField = false
261                        }
262                    }
263                })
264            },
265
266            /*
267             * Custom function, display a flash message to the user
268             */
269            handleFlashMessage: function(message, type) {},
270
271            /*
272             * Custom function, redirect the browser to another location
273             */
274            handleRedirectResponse: function(url) {
275                window.location.assign(url)
276            },
277
278            /*
279             * Custom function, handle any application specific response values
280             * Using a promisary object here in case injected assets need time to load
281             */
282            handleUpdateResponse: function(data, textStatus, jqXHR) {
283
284                /*
285                 * Update partials and finish request
286                 */
287                var updatePromise = $.Deferred().done(function() {
288                    for (var partial in data) {
289                        /*
290                         * If a partial has been supplied on the client side that matches the server supplied key, look up
291                         * it's selector and use that. If not, we assume it is an explicit selector reference.
292                         */
293                        var selector = (options.update[partial]) ? options.update[partial] : partial
294                        if ($.type(selector) == 'string' && selector.charAt(0) == '@') {
295                            $(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR])
296                        }
297                        else if ($.type(selector) == 'string' && selector.charAt(0) == '^') {
298                            $(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR])
299                        }
300                        else {
301                            $(selector).trigger('ajaxBeforeReplace')
302                            $(selector).html(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR])
303                        }
304                    }
305
306                    /*
307                     * Wait for .html() method to finish rendering from partial updates
308                     */
309                    setTimeout(function() {
310                        $(window)
311                            .trigger('ajaxUpdateComplete', [context, data, textStatus, jqXHR])
312                            .trigger('resize')
313                    }, 0)
314                })
315
316                /*
317                 * Handle redirect
318                 */
319                if (data['X_OCTOBER_REDIRECT']) {
320                    options.redirect = data['X_OCTOBER_REDIRECT']
321                    isRedirect = true
322                }
323
324                if (isRedirect) {
325                    requestOptions.handleRedirectResponse(options.redirect)
326                }
327
328                /*
329                 * Handle validation
330                 */
331                if (data['X_OCTOBER_ERROR_FIELDS']) {
332                    requestOptions.handleValidationMessage(data['X_OCTOBER_ERROR_MESSAGE'], data['X_OCTOBER_ERROR_FIELDS'])
333                }
334
335                /*
336                 * Handle asset injection
337                 */
338                 if (data['X_OCTOBER_ASSETS']) {
339                    assetManager.load(data['X_OCTOBER_ASSETS'], $.proxy(updatePromise.resolve, updatePromise))
340                 }
341                 else {
342                    updatePromise.resolve()
343                }
344
345                return updatePromise
346            }
347        }
348
349        if (useFiles) {
350            requestOptions.processData = requestOptions.contentType = false
351        }
352
353        /*
354         * Allow default business logic to be called from user functions
355         */
356        context.success = requestOptions.success
357        context.error = requestOptions.error
358        context.complete = requestOptions.complete
359        requestOptions = $.extend(requestOptions, options)
360        requestOptions.data = requestData
361
362        /*
363         * Initiate request
364         */
365        if (options.confirm && !requestOptions.handleConfirmMessage(options.confirm)) {
366            return
367        }
368
369        if (loading) loading.show()
370        $(window).trigger('ajaxBeforeSend', [context])
371        $el.trigger('ajaxPromise', [context])
372
373        return $.ajax(requestOptions)
374            .fail(function(jqXHR, textStatus, errorThrown) {
375                if (!isRedirect) {
376                    $el.trigger('ajaxFail', [context, textStatus, jqXHR])
377                }
378                if (loading) loading.hide()
379            })
380            .done(function(data, textStatus, jqXHR) {
381                if (!isRedirect) {
382                    $el.trigger('ajaxDone', [context, data, textStatus, jqXHR])
383                }
384                if (loading) loading.hide()
385            })
386            .always(function(dataOrXhr, textStatus, xhrOrError) {
387                $el.trigger('ajaxAlways', [context, dataOrXhr, textStatus, xhrOrError])
388            })
389    }
390
391    Request.DEFAULTS = {
392        update: {},
393        type : 'POST',
394        beforeUpdate: function(data, textStatus, jqXHR) {},
395        evalBeforeUpdate: null,
396        evalSuccess: null,
397        evalError: null,
398        evalComplete: null,
399        ajaxGlobal: false
400    }
401
402    /*
403     * Internal function, build a string of partials and their update elements.
404     */
405    Request.prototype.extractPartials = function(update) {
406        var result = []
407
408        for (var partial in update)
409            result.push(partial)
410
411        return result.join('&')
412    }
413
414    // REQUEST PLUGIN DEFINITION
415    // ============================
416
417    var old = $.fn.request
418
419    $.fn.request = function(handler, option) {
420        var args = arguments
421
422        var $this = $(this).first()
423        var data  = {
424            evalBeforeUpdate: $this.data('request-before-update'),
425            evalSuccess: $this.data('request-success'),
426            evalError: $this.data('request-error'),
427            evalComplete: $this.data('request-complete'),
428            ajaxGlobal: $this.data('request-ajax-global'),
429            confirm: $this.data('request-confirm'),
430            redirect: $this.data('request-redirect'),
431            loading: $this.data('request-loading'),
432            flash: $this.data('request-flash'),
433            files: $this.data('request-files'),
434            form: $this.data('request-form'),
435            url: $this.data('request-url'),
436            update: paramToObj('data-request-update', $this.data('request-update')),
437            data: paramToObj('data-request-data', $this.data('request-data'))
438        }
439        if (!handler) handler = $this.data('request')
440        var options = $.extend(true, {}, Request.DEFAULTS, data, typeof option == 'object' && option)
441        return new Request($this, handler, options)
442    }
443
444    $.fn.request.Constructor = Request
445
446    $.request = function(handler, option) {
447        return $(document).request(handler, option)
448    }
449
450    // REQUEST NO CONFLICT
451    // =================
452
453    $.fn.request.noConflict = function() {
454        $.fn.request = old
455        return this
456    }
457
458    // REQUEST DATA-API
459    // ==============
460
461    function paramToObj(name, value) {
462        if (value === undefined) value = ''
463        if (typeof value == 'object') return value
464
465        try {
466            return ocJSON("{" + value + "}")
467        }
468        catch (e) {
469            throw new Error('Error parsing the '+name+' attribute value. '+e)
470        }
471    }
472
473    function getXSRFToken() {
474        var cookieValue = null
475        if (document.cookie && document.cookie != '') {
476            var cookies = document.cookie.split(';')
477            for (var i = 0; i < cookies.length; i++) {
478                var cookie = jQuery.trim(cookies[i])
479                if (cookie.substring(0, 11) == ('XSRF-TOKEN' + '=')) {
480                    cookieValue = decodeURIComponent(cookie.substring(11))
481                    break
482                }
483            }
484        }
485        return cookieValue
486    }
487
488    $(document).on('change', 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]', function documentOnChange() {
489        $(this).request()
490    })
491
492    $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function documentOnClick(e) {
493        e.preventDefault()
494
495        $(this).request()
496
497        if ($(this).is('[type=submit]'))
498            return false
499    })
500
501    $(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function documentOnKeydown(e) {
502        if (e.key === 'Enter') {
503            if (this.dataTrackInputTimer !== undefined)
504                window.clearTimeout(this.dataTrackInputTimer)
505
506            $(this).request()
507            return false
508        }
509    })
510
511    $(document).on('input', 'input[data-request][data-track-input]', function documentOnKeyup(e) {
512        var
513            $el = $(this),
514            lastValue = $el.data('oc.lastvalue')
515
516        if (!$el.is('[type=email],[type=number],[type=password],[type=search],[type=text]'))
517            return
518
519        if (lastValue !== undefined && lastValue == this.value)
520            return
521
522        $el.data('oc.lastvalue', this.value)
523
524        if (this.dataTrackInputTimer !== undefined)
525            window.clearTimeout(this.dataTrackInputTimer)
526
527        var interval = $(this).data('track-input')
528        if (!interval)
529            interval = 300
530
531        var self = this
532        this.dataTrackInputTimer = window.setTimeout(function() {
533            if (self.lastDataTrackInputRequest) {
534                self.lastDataTrackInputRequest.abort();
535            }
536            self.lastDataTrackInputRequest = $(self).request();
537        }, interval)
538    })
539
540    $(document).on('submit', '[data-request]', function documentOnSubmit() {
541        $(this).request()
542        return false
543    })
544
545    $(window).on('beforeunload', function documentOnBeforeUnload() {
546        window.ocUnloading = true
547    })
548
549    /*
550     * Invent our own event that unifies document.ready with window.ajaxUpdateComplete
551     *
552     * $(document).render(function() { })
553     * $(document).on('render', function() { })
554     */
555
556    $(document).ready(function triggerRenderOnReady() {
557        $(document).trigger('render')
558    })
559
560    $(window).on('ajaxUpdateComplete', function triggerRenderOnAjaxUpdateComplete() {
561        $(document).trigger('render')
562    })
563
564    $.fn.render = function(callback) {
565        $(document).on('render', callback)
566    }
567
568}(window.jQuery);
569
570/*
571 * October CMS JSON Parser
572 */
573+function(window) { "use strict";
574    function parseKey(str, pos, quote) {
575        var key = "";
576        for (var i = pos; i < str.length; i++) {
577            if (quote && quote === str[i]) {
578                return key;
579            } else if (!quote && (str[i] === " " || str[i] === ":")) {
580                return key;
581            }
582
583            key += str[i];
584
585            if (str[i] === "\\" && i + 1 < str.length) {
586                key += str[i + 1];
587                i++;
588            }
589        }
590        throw new Error("Broken JSON syntax near " + key);
591    }
592
593    function getBody(str, pos) {
594        // parse string body
595        if (str[pos] === "\"" || str[pos] === "'") {
596            var body = str[pos];
597            for (var i = pos + 1; i < str.length; i++) {
598                if (str[i] === "\\") {
599                    body += str[i];
600                    if (i + 1 < str.length) body += str[i + 1];
601                    i++;
602                } else if (str[i] === str[pos]) {
603                    body += str[pos];
604                    return {
605                        originLength: body.length,
606                        body: body
607                    };
608                } else body += str[i];
609            }
610            throw new Error("Broken JSON string body near " + body);
611        }
612
613        // parse true / false
614        if (str[pos] === "t") {
615            if (str.indexOf("true", pos) === pos) {
616                return {
617                    originLength: "true".length,
618                    body: "true"
619                };
620            }
621            throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
622        }
623        if (str[pos] === "f") {
624            if (str.indexOf("f", pos) === pos) {
625                return {
626                    originLength: "false".length,
627                    body: "false"
628                };
629            }
630            throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
631        }
632
633        // parse null
634        if (str[pos] === "n") {
635            if (str.indexOf("null", pos) === pos) {
636                return {
637                    originLength: "null".length,
638                    body: "null"
639                };
640            }
641            throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
642        }
643
644        // parse number
645        if (str[pos] === "-" || str[pos] === "+" || str[pos] === "." || (str[pos] >= "0" && str[pos] <= "9")) {
646            var body = "";
647            for (var i = pos; i < str.length; i++) {
648                if (str[i] === "-" || str[i] === "+" || str[i] === "." || (str[i] >= "0" && str[i] <= "9")) {
649                    body += str[i];
650                } else {
651                    return {
652                        originLength: body.length,
653                        body: body
654                    };
655                }
656            }
657            throw new Error("Broken JSON number body near " + body);
658        }
659
660        // parse object
661        if (str[pos] === "{" || str[pos] === "[") {
662            var stack = [str[pos]];
663            var body = str[pos];
664            for (var i = pos + 1; i < str.length; i++) {
665                body += str[i];
666                if (str[i] === "\\") {
667                    if (i + 1 < str.length) body += str[i + 1];
668                    i++;
669                } else if (str[i] === "\"") {
670                    if (stack[stack.length - 1] === "\"") {
671                        stack.pop();
672                    } else if (stack[stack.length - 1] !== "'") {
673                        stack.push(str[i]);
674                    }
675                } else if (str[i] === "'") {
676                    if (stack[stack.length - 1] === "'") {
677                        stack.pop();
678                    } else if (stack[stack.length - 1] !== "\"") {
679                        stack.push(str[i]);
680                    }
681                } else if (stack[stack.length - 1] !== "\"" && stack[stack.length - 1] !== "'") {
682                    if (str[i] === "{") {
683                        stack.push("{");
684                    } else if (str[i] === "}") {
685                        if (stack[stack.length - 1] === "{") {
686                            stack.pop();
687                        } else {
688                            throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body);
689                        }
690                    } else if (str[i] === "[") {
691                        stack.push("[");
692                    } else if (str[i] === "]") {
693                        if (stack[stack.length - 1] === "[") {
694                            stack.pop();
695                        } else {
696                            throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body);
697                        }
698                    }
699                }
700                if (!stack.length) {
701                    return {
702                        originLength: i - pos,
703                        body: body
704                    };
705                }
706            }
707            throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body);
708        }
709        throw new Error("Broken JSON body near " + str.substr((pos - 5 >= 0) ? pos - 5 : 0, 50));
710    }
711
712    function canBeKeyHead(ch) {
713        if (ch[0] === "\\") return false;
714        if ((ch[0] >= 'a' && ch[0] <= 'z') || (ch[0] >= 'A' && ch[0] <= 'Z') || ch[0] === '_') return true;
715        if (ch[0] >= '0' && ch[0] <= '9') return true;
716        if (ch[0] === '$') return true;
717        if (ch.charCodeAt(0) > 255) return true;
718        return false;
719    }
720
721    function isBlankChar(ch) {
722        return ch === " " || ch === "\n" || ch === "\t";
723    }
724
725    function parse(str) {
726        str = str.trim();
727        if (!str.length) throw new Error("Broken JSON object.");
728        var result = "";
729
730        /*
731         * the mistake ','
732         */
733        while (str && str[0] === ",") {
734            str = str.substr(1);
735        }
736
737        /*
738         * string
739         */
740        if (str[0] === "\"" || str[0] === "'") {
741            if (str[str.length - 1] !== str[0]) {
742                throw new Error("Invalid string JSON object.");
743            }
744
745            var body = "\"";
746            for (var i = 1; i < str.length; i++) {
747                if (str[i] === "\\") {
748                    if (str[i + 1] === "'") {
749                        body += str[i + 1]
750                    } else {
751                        body += str[i];
752                        body += str[i + 1];
753                    }
754                    i++;
755                } else if (str[i] === str[0]) {
756                    body += "\"";
757                    return body
758                } else if (str[i] === "\"") {
759                    body += "\\\""
760                } else body += str[i];
761            }
762            throw new Error("Invalid string JSON object.");
763        }
764
765        /*
766         * boolean
767         */
768        if (str === "true" || str === "false") {
769            return str;
770        }
771
772        /*
773         * null
774         */
775        if (str === "null") {
776            return "null";
777        }
778
779        /*
780         * number
781         */
782        var num = parseFloat(str);
783        if (!isNaN(num)) {
784            return num.toString();
785        }
786
787        /*
788         * object
789         */
790        if (str[0] === "{") {
791            var type = "needKey";
792            var result = "{";
793
794            for (var i = 1; i < str.length; i++) {
795                if (isBlankChar(str[i])) {
796                    continue;
797                } else if (type === "needKey" && (str[i] === "\"" || str[i] === "'")) {
798                    var key = parseKey(str, i + 1, str[i]);
799                    result += "\"" + key + "\"";
800                    i += key.length;
801                    i += 1;
802                    type = "afterKey";
803                } else if (type === "needKey" && canBeKeyHead(str[i])) {
804                    var key = parseKey(str, i);
805                    result += "\"";
806                    result += key;
807                    result += "\"";
808                    i += key.length - 1;
809                    type = "afterKey";
810                } else if (type === "afterKey" && str[i] === ":") {
811                    result += ":";
812                    type = ":";
813                } else if (type === ":") {
814                    var body = getBody(str, i);
815
816                    i = i + body.originLength - 1;
817                    result += parse(body.body);
818
819                    type = "afterBody";
820                } else if (type === "afterBody" || type === "needKey") {
821                    var last = i;
822                    while (str[last] === "," || isBlankChar(str[last])) {
823                        last++;
824                    }
825                    if (str[last] === "}" && last === str.length - 1) {
826                        while (result[result.length - 1] === ",") {
827                            result = result.substr(0, result.length - 1);
828                        }
829                        result += "}";
830                        return result;
831                    } else if (last !== i && result !== "{") {
832                        result += ",";
833                        type = "needKey";
834                        i = last - 1;
835                    }
836                }
837            }
838            throw new Error("Broken JSON object near " + result);
839        }
840
841        /*
842         * array
843         */
844        if (str[0] === "[") {
845            var result = "[";
846            var type = "needBody";
847            for (var i = 1; i < str.length; i++) {
848                if (" " === str[i] || "\n" === str[i] || "\t" === str[i]) {
849                    continue;
850                } else if (type === "needBody") {
851                    if (str[i] === ",") {
852                        result += "null,";
853                        continue;
854                    }
855                    if (str[i] === "]" && i === str.length - 1) {
856                        if (result[result.length - 1] === ",") result = result.substr(0, result.length - 1);
857                        result += "]";
858                        return result;
859                    }
860
861                    var body = getBody(str, i);
862
863                    i = i + body.originLength - 1;
864                    result += parse(body.body);
865
866                    type = "afterBody";
867                } else if (type === "afterBody") {
868                    if (str[i] === ",") {
869                        result += ",";
870                        type = "needBody";
871
872                        // deal with mistake ","
873                        while (str[i + 1] === "," || isBlankChar(str[i + 1])) {
874                            if (str[i + 1] === ",") result += "null,";
875                            i++;
876                        }
877                    } else if (str[i] === "]" && i === str.length - 1) {
878                        result += "]";
879                        return result;
880                    }
881                }
882            }
883            throw new Error("Broken JSON array near " + result);
884        }
885    }
886
887    // Global function
888    window.ocJSON = function(json) {
889        var jsonString = parse(json);
890        return JSON.parse(jsonString);
891    };
892
893}(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.