1/**
2 * DokuWiki Plugin imapmarkers (Javascript Component)
3 *
4 * @license MIT https://en.wikipedia.org/wiki/MIT_License
5 * @author  Kai Thoene <k.git.thoene@gmx.net>
6 */
7if (window.jQuery) {
8  globalThis[Symbol.for('imapmarkers_storage')] = (function () {
9    var defaults = {
10      'debug': false, // false = no debug on console
11      'marker_color': "#00AEEF",
12      'clicked-reference-css': { 'font-weight': 'bold', 'color': '#00AEEF' }
13    };
14    var a_maps = [];
15    var a_areas = [];
16    var a_references = [];
17    var a_clicked_css_properties = [];
18    var a_cfg = [];
19    var resize_timeout = null;
20    var a_last_clicked_id = [];
21    var a_imap_div = [];
22    var imap_div_timeout = null;
23    var nr_startup_intervals = 0;
24    var mapster_decoration_obj_default = {
25      fillColor: 'ffffff',
26      fillOpacity: 0.3,
27      wrapClass: true,
28      wrapCss: true,
29      clickNavigate: true
30    };
31    var is_imagemapster_existing = false;
32
33    function append_html_to_jquery_object(html, object) {
34      object[0].insertAdjacentHTML(
35        "beforeEnd",
36        html
37      );
38    }  // function append_html_to_jquery_object
39
40    function calc_marker_pos(coords) {
41      let split_coords = coords.split(",");
42      var x = 0;
43      var y = 0;
44      switch (split_coords.length) {
45        case 3:  // circle
46          x = parseInt(split_coords[0]);
47          y = parseInt(split_coords[1]);
48          break;
49        case 4:  // rectangle
50          x = Math.floor((parseInt(split_coords[0]) + parseInt(split_coords[2])) / 2);
51          y = Math.floor((parseInt(split_coords[1]) + parseInt(split_coords[3])) / 2);
52          break;
53        default:
54          if (split_coords.length >= 6) {
55            var num_coords = 0;
56            for (var i = 0; i < (split_coords.length - 1); i += 2) {
57              x += parseInt(split_coords[i]);
58              y += parseInt(split_coords[i + 1]);
59              num_coords++;
60            }
61            x = Math.floor(x / num_coords);
62            y = Math.floor(y / num_coords);;
63          }
64      }
65      return [x, y];
66    }  // function calc_marker_pos
67
68    function get_id_from_string(s) {
69      const a_ids = s.split(" ");
70      if (a_ids.length >= 1) {
71        if (a_ids[0].length > 0) {
72          return String(a_ids[0]);
73        }
74      }
75      return null;
76    }  // function get_id_from_string
77
78    function get_index_from_string_end(s) {
79      return parseInt(s.replace(/^[^\d]*/, ""));
80    }  // function get_index_from_string_end
81
82    function find_area_by_jquery_id(id, imap_index = null) {
83      try {
84        if (imap_index === null) {
85          for (var i = 0; i < a_areas.length; i++) {
86            if (a_areas[i] !== undefined) {
87              for (var j = 0; j < a_areas[i].length; j++) {
88                const area = a_areas[i][j];
89                if (area['id'] == id) {
90                  return [i, area['area'], j];
91                }
92              }
93            }
94          }
95        } else {
96          if (a_areas[imap_index] !== undefined) {
97            for (var j = 0; j < a_areas[imap_index].length; j++) {
98              const area = a_areas[imap_index][j];
99              if (area['id'] == id) {
100                return [imap_index, area['area'], j];
101              }
102            }
103          }
104        }
105      } catch (e) { console.error("plugin:imapmarkers: EXCEPTION=" + e); }
106      return [-1, null, -1];
107    }  // function find_area_by_jquery_id
108
109    return {
110      // exported variables:
111      defaults: defaults,
112      a_maps: a_maps,
113      a_areas: a_areas,
114      a_references: a_references,
115      a_clicked_css_properties: a_clicked_css_properties,
116      a_cfg: a_cfg,
117      resize_timeout: resize_timeout,
118      a_last_clicked_id: a_last_clicked_id,
119      a_imap_div: a_imap_div,
120      imap_div_timeout: imap_div_timeout,
121      nr_startup_intervals: nr_startup_intervals,
122      mapster_decoration_obj_default: mapster_decoration_obj_default,
123      is_imagemapster_existing: is_imagemapster_existing,
124      // exported functions:
125      append_html_to_jquery_object: append_html_to_jquery_object,
126      calc_marker_pos: calc_marker_pos,
127      get_id_from_string: get_id_from_string,
128      get_index_from_string_end: get_index_from_string_end,
129      find_area_by_jquery_id: find_area_by_jquery_id,
130    };
131  })();
132  var _g = globalThis[Symbol.for('imapmarkers_storage')];
133
134  _g.is_imagemapster_existing = jQuery.isFunction(jQuery.fn.mapster);
135  if (!_g.is_imagemapster_existing) {
136    // Load jQuery Imagemapster Library. SEE: http://www.outsharked.com/imagemapster/
137    /* DOKUWIKI:include_once jquery.imagemapster.js */
138  }
139
140  addEventListener("DOMContentLoaded", (event) => {
141    (function ($) {
142      //SEE:https://www.geeksforgeeks.org/how-to-get-all-css-styles-associated-with-an-element-using-jquery/
143      $.fn.cssSpecific = function (str) {
144        var ob = {};
145        if (this.length) {
146          var css = str.split(', ');
147          var prms = [];
148          for (var i = 0, ii = css.length; i < ii; i++) {
149            prms = css[i].split(':');
150            ob[$.trim(prms[0])] = $(this).css($.trim(prms[1] || prms[0]));
151          }
152        }
153        return ob;
154      };  // $.fn.cssSpecific
155
156      function get_marker_width_and_height(id) {
157        return [$(id).width(), $(id).height()];
158      }  // function get_marker_width_and_height
159
160      function do_marker_if_resize() {
161        _g.a_imap_div.forEach((object, index) => {
162          if (_g.a_last_clicked_id[index] != undefined) {
163            let marker_id_jquery = "#imapmarkers-marker-" + index;
164            let id = _g.a_last_clicked_id[index]['id'];
165            if (_g.defaults['debug']) { console.log("RESIZE::[" + index + "] LAST CLICKED ID=" + id); }
166            if ((_g.a_last_clicked_id[index]['id'] !== undefined) && $(marker_id_jquery).is(":visible")) {
167              let area_index = _g.a_last_clicked_id[index]['area_index'];
168              if (_g.defaults['debug']) { console.log("RESIZE::[" + index + "] AREA-INDEX=" + area_index); }
169              try {
170                let found_area = _g.a_areas[index][area_index]['area'];
171                let coords = found_area.attr("coords");
172                let xy = _g.calc_marker_pos(coords);
173                let wh = get_marker_width_and_height(marker_id_jquery);
174                $(marker_id_jquery).css({ top: xy[1] - wh[1] + 3, left: xy[0] - (wh[0] / 2) });
175              } catch (e) { console.error("plugin:imapmarkers: EXCEPTION=" + e); }
176            }
177          }
178        });
179      }  // do_marker_if_resize
180
181      function do_resize() {
182        $('img[usemap]').each(function () {
183          let parent = $(this.offsetParent);
184          let parentparent = $(parent).parent();
185          $(this).mapster('resize', ($(this)[0].naturalWidth < parentparent.width()) ? $(this)[0].naturalWidth : parentparent.width());
186        });
187        do_marker_if_resize();
188      }  // function do_resize
189
190      $(window).resize(function () {
191        if (_g.resize_timeout != null) { clearTimeout(_g.resize_timeout); }
192        _g.resize_timeout = setTimeout(do_resize, 100);
193      });
194
195      let imap_do_main_function = function () {
196        if (_g.defaults['debug']) { console.log("imapmarkers::START IMAGEMAPPING MARKER"); }
197        _g.nr_startup_intervals++;
198        if (_g.nr_startup_intervals >= 10) {
199          // stop after 5 s searching:
200          if (_g.imap_div_timeout !== null) { clearTimeout(_g.imap_div_timeout); }
201          if (_g.defaults['debug']) { console.log("imapmarkers::GIVE UP IMAGEMAPPING SEARCH"); }
202          return;
203        }
204        //
205        // wait for another ImageMapster or not ...:
206        //setTimeout(function () {
207          if ($("img.mapster_el").length == 0) {
208            // initialize ImageMapster
209            $('img[usemap].imapmarkers').mapster(_g.mapster_decoration_obj_default);
210            if (_g.defaults['debug']) { console.log("NOT FOUND: img.mapster_el => INIT"); }
211          }  else {
212            if (_g.defaults['debug']) { console.log("FOUND: img.mapster_el"); }
213          }
214        //}, ((_g.is_imagemapster_existing) ? 1000 : 0)); // 1000 : 0
215        //
216        // find container:
217        var imap_index = 0;
218        $(".imapmarkers-container").each(function (index, object) {
219          let imap_index = _g.get_index_from_string_end($(this).attr("id"));
220          if (_g.a_imap_div[imap_index] === undefined) {
221            if (_g.defaults['debug']) { console.log("[" + imap_index + "] CONTAINER FOUND ID='" + $(this).attr("id") + "'"); }
222            _g.a_imap_div[imap_index] = $(this);
223          }
224        });
225        if ((_g.a_imap_div[0] !== undefined) && (_g.a_imap_div[0] !== null) && ($("img.mapster_el").length != 0)) {
226          // resize image:
227          do_resize();
228          // find maps:
229          $(".imapmarkers-map").each(function (index, object) {
230            let imap_index = _g.get_index_from_string_end($(this).attr("name"));
231            if (_g.a_maps[imap_index] === undefined) {
232              _g.a_maps[imap_index] = $(this);
233              if (_g.defaults['debug']) { console.log("[" + imap_index + "] MAP FOUND NAME='" + $(this).attr("name") + "'"); }
234            }
235          });
236          // set img z-index and collect areas:
237          _g.a_imap_div.forEach((object, index) => {
238            let imap_div_index = index;
239            if (_g.defaults['debug']) { console.log("[" + index + "] Z-INDEX THIS TAG=" + object.prop("tagName") + " ID=" + object.attr("id") + " NAME=" + object.attr("name")); }
240            // set z-index for images:
241            object.find("img").css("z-index", "0");
242            // collect areas:
243            if (_g.a_maps[index] !== undefined) {
244              _g.a_maps[index].find("area").each(function (area_index, object) {
245                if (_g.a_areas[index] === undefined) {
246                  _g.a_areas[index] = [];
247                }
248                const area_id = $(this).attr("location_id");
249                _g.a_areas[index].push({ 'id': area_id, 'area': $(this) });
250                if (_g.defaults['debug']) { console.log("[" + index + "] ADD AREA #" + area_index + " ID='" + area_id + "'"); }
251              });
252            }
253          });
254          // find configurations:
255          $(".imapmarkers-config").each(function (index, object) {
256            try {
257              let cfg_id = $(this).attr("id");
258              let imap_index = _g.get_index_from_string_end(cfg_id);
259              let cfg_text = $(this).text();
260              cfg_text = cfg_text.replaceAll('„', '"').replaceAll('“', '"');
261              let cfg = JSON.parse(cfg_text);
262              if (imap_index >= 0) {
263                _g.a_cfg[imap_index] = cfg;
264                if (cfg['clicked-reference-css'] !== undefined) {
265                  _g.a_clicked_css_properties[imap_index] = Object.keys(cfg['clicked-reference-css']).join(", ");
266                  if (_g.defaults['debug']) { console.log("[" + imap_index + "] CLICKED CSS PROPERTIES='" + _g.a_clicked_css_properties[imap_index] + "'"); }
267                }
268                if (_g.defaults['debug']) { console.log("[" + imap_index + "] CFG FOUND ID=" + cfg_id + " CFG='" + cfg_text + "'"); }
269                var is_changed = false;
270                var mapster_decoration_obj = JSON.parse(JSON.stringify(_g.mapster_decoration_obj_default));
271                if (cfg['area-fillColor'] != undefined) {
272                  is_changed = true;
273                  mapster_decoration_obj.fillColor = String(cfg['area-fillColor']);
274                }
275                if (cfg['area-fillOpacity'] != undefined) {
276                  is_changed = true;
277                  mapster_decoration_obj.fillOpacity = parseFloat(String(cfg['area-fillOpacity']));
278                }
279                if (is_changed) {
280                  let img_jquery_id = "#imapmarkers-img-" + imap_index;
281                  if (_g.defaults['debug']) { console.log("[" + imap_index + "] APPLY LOOK TO IMG-JQUERY-ID='" + img_jquery_id + "'"); }
282                  $(img_jquery_id).mapster('set_options', mapster_decoration_obj);
283                }
284              } else {
285                throw new Error("Id not found in any mapping! CONFIG-ID='" + cfg_id + "'");
286              }
287            } catch (e) {
288              let msg = "EXCEPTION=" + e;
289              console.error("plugin:imapmarkers: " + msg);
290              $(this).css("background-color", "#FF0000");
291              $(this).append(' <span style="color:white; background-color:red;">' + msg + '</span>');
292              $(this).show();
293            }
294          });
295          // create marker:
296          _g.a_imap_div.forEach((object, index) => {
297            let imap_div_index = index;
298            let marker_id = "imapmarkers-marker-" + index;
299            let marker_id_jquery = "#" + marker_id;
300            let is_marker_internal = true;
301            if ((_g.a_cfg[imap_div_index] !== undefined) && (_g.a_cfg[imap_div_index]['marker'] !== undefined) && (String(_g.a_cfg[imap_div_index]['marker']).length > 0)) {
302              let marker_loc = String(_g.a_cfg[imap_div_index]['marker']);
303              if (marker_loc != "internal") {
304                var marker_src;
305                if (marker_loc.match(/^https{0,1}:\/\//) !== null) {
306                  // http[s] URI:
307                  marker_src = '<img id="' + marker_id + '" src="' + marker_loc + '">';
308                } else {
309                  // media link:
310                  marker_src = '<img id="' + marker_id + '" src="/lib/exe/fetch.php?media=' + marker_loc + '">';
311                }
312                let imagemapster_div = _g.a_imap_div[imap_div_index].find("div.imapmarkers\\-image");
313                _g.append_html_to_jquery_object(marker_src, imagemapster_div);
314                if ((_g.a_cfg[imap_div_index] !== undefined) && (_g.a_cfg[imap_div_index]['marker-width'] !== undefined)) {
315                  $(marker_id_jquery).css("width", parseInt(_g.a_cfg[imap_div_index]['marker-width']));
316                }
317                if ((_g.a_cfg[imap_div_index] !== undefined) && (_g.a_cfg[imap_div_index]['marker-height'] !== undefined)) {
318                  $(marker_id_jquery).css("height", parseInt(_g.a_cfg[imap_div_index]['marker-height']));
319                }
320                is_marker_internal = false;
321              }
322            }
323            if (is_marker_internal) {
324              // create internal, default, marker:
325              let svg_marker = '<svg version="1.1" id="' + marker_id + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 365 560" enable-background="new 0 0 365 560" xml:space="preserve"> ' +
326                '<g> ' +
327                '  <path fill="#00AEEF" d="M182.9,551.7c0,0.1,0.2,0.3,0.2,0.3S358.3,283,358.3,194.6c0-130.1-88.8-186.7-175.4-186.9 ' +
328                '    C96.3,7.9,7.5,64.5,7.5,194.6c0,88.4,175.3,357.4,175.3,357.4S182.9,551.7,182.9,551.7z M122.2,187.2c0-33.6,27.2-60.8,60.8-60.8 ' +
329                '    c33.6,0,60.8,27.2,60.8,60.8S216.5,248,182.9,248C149.4,248,122.2,220.8,122.2,187.2z"/> ' +
330                '</g> ' +
331                '</svg>';
332              let imagemapster_div = _g.a_imap_div[imap_div_index].find("div.imapmarkers\\-image");
333              _g.append_html_to_jquery_object(svg_marker, imagemapster_div);
334              $(marker_id_jquery).css("width", 21);
335              $(marker_id_jquery).css("height", 32);
336              if ((_g.a_cfg[imap_div_index] !== undefined) && (_g.a_cfg[imap_div_index]['marker-width'] !== undefined)) {
337                $(marker_id_jquery).css("width", parseInt(_g.a_cfg[imap_div_index]['marker-width']));
338              }
339              if ((_g.a_cfg[imap_div_index] !== undefined) && (_g.a_cfg[imap_div_index]['marker-height'] !== undefined)) {
340                $(marker_id_jquery).css("height", parseInt(_g.a_cfg[imap_div_index]['marker-height']));
341              }
342              $(marker_id_jquery).css("top", -32);
343              $(marker_id_jquery).css("left", -32);
344              $("path", $(marker_id_jquery)).attr("style", "fill:" + _g.defaults['marker_color']);
345              if ((_g.a_cfg[imap_div_index] !== undefined) && (_g.a_cfg[imap_div_index]['marker-color'] !== undefined)) {
346                $("path", $(marker_id_jquery)).attr("style", "fill:" + _g.a_cfg[imap_div_index]['marker-color']);
347              }
348            }
349            $(marker_id_jquery).css("position", "absolute");
350            $(marker_id_jquery).css("z-index", 1000);
351            $(marker_id_jquery).hide();
352            if (_g.defaults['debug']) { console.log("[" + index + "] ADD MARKER ID='" + marker_id + "'"); }
353          });
354          // setup clicked css style keywords:
355          _g.a_imap_div.forEach((object, index) => {
356            try {
357              if (_g.a_clicked_css_properties[index] === undefined) {
358                _g.a_clicked_css_properties[index] = Object.keys(_g.defaults['clicked-reference-css']).join(", ");
359                if (_g.defaults['debug']) { console.log("[" + index + "] CLICKED CSS PROPERTIES='" + _g.a_clicked_css_properties[index] + "'"); }
360              }
361            } catch (e) { console.error("plugin:imapmarkers: EXCEPTION=" + e); }
362          });
363          // search for references:
364          $(".imapmarkers-location, .wrap_imapmloc").each(function (index, object) {
365            try {
366              var loc_id = null;
367              if ($(this).hasClass("imapmarkers-location")) {
368                loc_id = $(this).attr("location_id");
369                if (_g.defaults['debug']) { console.log("FOUND IMAPMLOC NORMAL ID='" + loc_id + "' FONT-WEIGHT=" + $(this).css("font-weight") + " COLOR=" + $(this).css("color")); }
370              }
371              if ($(this).hasClass("wrap_imapmloc")) {
372                loc_id = _g.get_id_from_string($(this).text());
373                if (_g.defaults['debug']) { console.log("FOUND IMAPMLOC WRAP ID='" + loc_id + "' FONT-WEIGHT=" + $(this).css("font-weight") + " COLOR=" + $(this).css("color")); }
374              }
375              if (loc_id !== null) {
376                // find corresponding area
377                let ia = _g.find_area_by_jquery_id(loc_id);
378                //if (_g.defaults['debug']) { console.log("SEARCH AREA -> '" + JSON.stringify(ia) + "'"); }
379                let imap_index = ia[0];
380                let found_area = ia[1];
381                let area_index = ia[2];
382                if (found_area != null) {
383                  if (_g.a_references[imap_index] === undefined) {
384                    _g.a_references[imap_index] = [];
385                  }
386                  _g.a_references[imap_index].push({ 'id': loc_id, 'imap_index': imap_index, 'area_index': area_index, 'reference': $(this), 'css': $(this).cssSpecific(_g.a_clicked_css_properties[imap_index]) });
387                  let reference_index = _g.a_references[imap_index].length - 1;
388                  if (_g.defaults['debug']) { console.log("FOUND AREA FOR IMAPMLOC ID='" + loc_id + "' AREA IMAP-INDEX=" + imap_index + " AREA-INDEX=" + area_index + " CSS='" + JSON.stringify($(this).cssSpecific(_g.a_clicked_css_properties[imap_index])) + "'"); }
389                  $(this).on("click", { 'area': found_area, 'id': loc_id, 'imap_index': imap_index, 'area_index': area_index, 'reference_index': reference_index }, function (e) {
390                    var data = e.data;
391                    let imap_index = data['imap_index'];
392                    let marker_id_jquery = "#imapmarkers-marker-" + imap_index;
393                    let area_index = data['area_index'];
394                    let id = data['id'];
395                    let reference_index = data['reference_index'];
396                    if (_g.defaults['debug']) { console.log("CLICK ID='" + id + "' AREA IMAP-INDEX=" + imap_index + " AREA-INDEX=" + area_index); }
397                    if ((_g.a_last_clicked_id[imap_index] !== undefined) && (_g.a_last_clicked_id[imap_index]['id'] == data['id']) && $(marker_id_jquery).is(":visible")) {
398                      // hide marker
399                      $(marker_id_jquery).hide();
400                      $(marker_id_jquery).css('cursor', '');
401                      _g.a_references[imap_index].forEach((object, index) => {
402                        if (object['id'] == id) {
403                          object['reference'].css(object['css']);
404                        }
405                      });
406                      $(this).css(_g.a_references[imap_index][reference_index]['css']);
407                    } else {
408                      // show marker
409                      let coords = data['area'].attr("coords");
410                      let xy = _g.calc_marker_pos(coords);
411                      let wh = get_marker_width_and_height(marker_id_jquery);
412                      $(marker_id_jquery).css({ top: xy[1] - wh[1] + 3, left: xy[0] - (wh[0] / 2) });
413                      let href = _g.a_areas[imap_index][area_index]['area'].attr('href');
414                      if (String(href).length > 0) {
415                        $(marker_id_jquery).css('cursor', 'pointer');
416                        $(marker_id_jquery).on("click", { 'imap_index': imap_index, 'area_index': area_index }, function () {
417                          let href = _g.a_areas[imap_index][area_index]['area'].attr('href');;
418                          if (_g.defaults['debug']) { console.log("[" + imap_index + "] CLICK ON MARKER HREF='" + href + "'"); }
419                          window.location.href = href;
420                        });
421                      }
422                      $(marker_id_jquery).show();
423                      _g.a_references[imap_index].forEach((object, index) => {
424                        if (object['id'] == id) {
425                          if ((_g.a_cfg[imap_index] !== undefined) && (_g.a_cfg[imap_index]['clicked-reference-css'] !== undefined)) {
426                            object['reference'].css(_g.a_cfg[imap_index]['clicked-reference-css']);
427                          } else {
428                            object['reference'].css(_g.defaults['clicked-reference-css']);
429                          }
430                        } else {
431                          object['reference'].css(object['css']);
432                        }
433                      });
434                    }
435                    if (_g.defaults['debug']) { console.log("[" + imap_index + "] LAST CLICKED ID='" + data['id'] + " MARKER-ID='" + marker_id_jquery + "'"); }
436                    _g.a_last_clicked_id[imap_index] = { 'imap_index': imap_index, 'area_index': area_index, 'id': data['id'] };
437                  });
438                  $(this).css('cursor', 'pointer');
439                }
440              }
441            } catch (e) { console.error("plugin:imapmarkers: EXCEPTION=" + e); }
442          });
443        } else {
444          if (_g.imap_div_timeout !== null) { clearTimeout(_g.imap_div_timeout); }
445          _g.imap_div_timeout = setTimeout(imap_do_main_function, 500);
446        }
447      };
448      _g.imap_div_timeout = setTimeout(imap_do_main_function, 100);
449    })(jQuery);
450  });
451} else {
452  console.error("plugin:imapmarkers: jQuery is undefined!");
453}
454