1jQuery(function() {
2    //another json plugin may have allready initialized this
3    if(!json_plugin.initialized) {
4        json_plugin.init();
5    }
6});
7
8//global variable accessible by other json-type plugins
9var json_plugin = {
10    initialized: false,
11
12    init: function() {
13        //if user wants to leave the page and if this variable is not empty,
14        //then alert will ask user, if he wants to leave the page or stay on it.
15        var leavePageList = {};
16
17        json_plugin.initialized = true;
18
19        //initialize <jsonxxx> elements
20        jQuery('.json-tabs').each(function() {
21
22            var $tabs = jQuery(this),
23                id = $tabs.data('json-id'),
24                hash = $tabs.data('json-hash'),
25                active = $tabs.data('active'),
26                $button = $tabs.find('.json-save-button'),
27                $data_original = $tabs.find('.json-data-original'),
28                $textarea = $tabs.find('.json-data-inline'),
29                $data_combined = $tabs.find('.json-data-combined'),
30                $highlight = $tabs.find('.lang-json');
31
32
33            //save object with interface to this tabs, so other json plugins can use it
34            var o = {
35                //json data, before inline data was combined, readonly
36                get data_original() {
37                    return ($data_original.length === 1) ? $data_original.text() : undefined;
38                },
39
40                //inline json data from textarea, writeable
41                get data_inline() {
42                    return ($textarea.length === 1) ? $textarea.val() : undefined;
43                },
44                set data_inline(text) {
45                    if ($textarea.length === 1) {
46                        $textarea.val(text);
47                    }
48                },
49
50                //complete json data, data_original combined with data_inline
51                get data_combined() {
52                    return ($data_combined.length === 1) ? $data_combined.text() : undefined;
53                },
54
55                //jQuery button element. It triggers 'jsonBeforeSave' and 'jsonAfterSave' events.
56                $button: $button,
57
58                //function must be called by other json plugins on change of data
59                showButton: function() {
60                    if(jQuery.isEmptyObject(leavePageList)) {
61                        window.onbeforeunload = function(e) {
62                            e.preventDefault();
63                            e.returnValue = '';
64                        };
65                    }
66                    leavePageList[id] = true;
67                    $button.show('slow');
68                }
69            };
70            $tabs.data('o', o);
71
72
73            //generate jQuery UI tabs
74            $tabs.tabs({
75                collapsible: true,
76                active: (active === "false") ? false : parseInt(active)
77            });
78
79
80            //button will save data to dokuwiki via ajax call
81            $button.on('click', function (event) {
82                //other json plugins may prepare data here
83                $button.trigger('jsonBeforeSave');
84
85                var text = o.data_inline;
86
87                //validate JSON
88                if(text.trim().length > 0) {
89                    try {
90                        JSON.parse(text.replace(/%\$.*?%/g, 'null'));
91                    }
92                    catch(e) {
93                        alert(e);
94                        return;
95                    }
96                }
97
98                //save data
99                jQuery.post(
100                    DOKU_BASE + 'lib/exe/ajax.php',
101                    {
102                        call: 'json_plugin_save_inline',//server function
103                        file: JSINFO.id,    //dokuwiki file id
104                        id: id,             //id of the <json id=___> element
105                        hash: hash,         //MD5 hash of the old_text from <json ...>old_text</json> element
106                        text: text          //new text to write into <json ...>text</json> element
107                    },
108                    function(data) {
109                        if(data.response === 'OK') {
110                            hash = data.hash;
111
112                            //save successful, prepare event handlers, hide button
113                            $textarea.one('keydown change', o.showButton);
114                            $button.trigger('jsonAfterSave');
115                            $button.hide('slow');
116
117                            delete(leavePageList[id]);
118                            if(jQuery.isEmptyObject(leavePageList)) {
119                                window.onbeforeunload = null;
120                            }
121                        }
122                        else if(data.response === 'error') {
123                            alert(data.error);
124                        }
125                        else {
126                            alert('Internal communication error.');
127                        }
128                    },
129                    'json'
130                );
131            });
132
133
134            //show 'save' button once after first key is pressed
135            $textarea.one('keydown change', o.showButton);
136
137
138            //highlight json code
139            $highlight.each(function() {
140                hljs.highlightBlock(this);
141            });
142        });
143
144        // Make button, which will trigger ajax call for archiving data
145        // When JSON data are archived, then all json-data-original
146        // will be stored into <json> element itself. 'src' and 'src_ext'
147        // attibutes from the element will then be ignored.
148        var archives = jQuery('.json-tabs.json-make-archive');
149        if(archives.length > 0) {
150            var $archive_button = jQuery('<button class="json-archive-button">'+LANG.plugins.json.archive_button+'</button>');
151
152            jQuery('#dokuwiki__header').append($archive_button);
153
154            $archive_button.on('click', function (event) {
155                var json_data_original = [],
156                    subdir = prompt(LANG.plugins.json.archive_move);
157
158                if (typeof subdir != 'string') {
159                    return;
160                }
161
162                archives.each(function() {
163                    json_data_original.push(jQuery(this).find('.json-data-original').text());
164                });
165
166                //save data
167                jQuery.post(
168                    DOKU_BASE + 'lib/exe/ajax.php',
169                    {
170                        call: 'json_plugin_archive',//server function
171                        file: JSINFO.id,            //dokuwiki file id
172                        lastmod: JSINFO.json_lastmod,//dokuwiki timestamp of the last modification to the current page
173                        data: json_data_original,   //array of data
174                        subdir: subdir              //move to subdirectory
175                    },
176                    function(data) {
177                        if(data.response === 'OK') {
178                            //save successful, hide button
179                            $archive_button.hide('slow');
180                        }
181                        else if(data.response === 'error') {
182                            alert(data.error);
183                        }
184                        else {
185                            alert('Internal communication error.');
186                        }
187                    },
188                    'json'
189                );
190            });
191        }
192
193
194        //initialize some extractors
195        //highlight json code
196        jQuery('.json-extract-code').each(function() {
197            hljs.highlightBlock(this);
198        });
199
200        //ejs template
201        if(JSINFO.enable_ejs) {
202            jQuery('.json-extract-ejs').each(function() {
203                var $span = jQuery(this),
204                    data = JSON.parse($span.find('#data').text()),
205                    template = $span.find('#template').text(),
206                    result = '';
207                try {
208                    result = ejs.render(template, {d : data});
209                }
210                catch (e) {
211                    console.log(e);
212                }
213                $span.text(result);
214            });
215        }
216    },
217
218
219    //helper function: get diff of two objects (data_original, data_combined)
220    //https://stackoverflow.com/questions/8431651/getting-a-diff-of-two-json-objects
221    diff: function (obj1, obj2) {
222        if(!obj1 || typeof obj1 !== 'object' || !obj2 || typeof obj2 !== 'object') {
223            return obj2;
224        }
225        const result = {};
226        Object.keys(obj2).forEach(key => {
227            if(typeof obj2[key] === 'object' && typeof obj1[key] === 'object') {
228                const value = this.diff(obj1[key], obj2[key]);
229                if (Object.keys(value).length !== 0) {
230                    result[key] = value;
231                }
232            }
233            else if(obj2[key] !== obj1[key] && !Object.is(obj1[key], obj2[key])) {
234                result[key] = obj2[key];
235            }
236        });
237        return result;
238    }
239};
240