Увага: Після публікування слід очистити кеш браузера, щоб побачити зміни.

  • Firefox / Safari: тримайте Shift, коли натискаєте Оновити, або натисніть Ctrl-F5 чи Ctrl-Shift-R (⌘-R на Apple Mac)
  • Google Chrome: натисніть Ctrl-Shift-R (⌘-Shift-R на Apple Mac)
  • Internet Explorer / Edge: тримайте Ctrl, коли натискаєте Оновити, або натисніть Ctrl-F5
  • Opera: натисніть Ctrl-F5
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  hasProp = {}.hasOwnProperty;

if (typeof unsafeWindow !== "undefined" && unsafeWindow !== null) {
  window.$ = unsafeWindow.$;
  window.etSubsets = unsafeWindow.etSubsets;
}

window.rast = {
  arrayMove: function(array, from, to) {
    return array.splice(to, 0, array.splice(from, 1)[0]);
  },
  $getTextarea: function() {
    return $('#wpTextbox1');
  },
  $getCurrentInput: function() {
    return $(document.activeElement);
  },
  insertion: {
    replaceSpecsymbols: function(s, symbols, toFunc) {
      var c, i, res;
      res = '';
      c = void 0;
      i = 0;
      while (i < s.length) {
        c = s.charAt(i);
        if (rast.insertion.isEscaped(s, i)) {
          res += c;
        } else if (symbols.indexOf(c) > -1) {
          res += toFunc(c);
        } else {
          res += c;
        }
        i++;
      }
      return res;
    },
    isEscaped: function(s, i) {
      var escSymbols;
      escSymbols = 0;
      i--;
      while (i > -1 && s.charAt(i) === '\\') {
        escSymbols++;
        i--;
      }
      return escSymbols % 2 === 1;
    },
    indexOfUnescaped: function(s, symbol) {
      var i, index;
      index = -1;
      i = 0;
      while (i < s.length) {
        if (s.charAt(i) === symbol && !rast.insertion.isEscaped(s, i)) {
          index = i;
          break;
        }
        i++;
      }
      return index;
    }
  },
  installJQueryPlugins: function() {
    $.fn.extend({
      throbber: function(visibility, position, size) {
        var $elem, $throbber;
        $elem = $(this);
        $throbber = $elem.data('rastThrobber');
        if ($throbber) {
          $throbber.toggle(visibility);
        } else {
          size = size || '20px';
          $throbber = $('<img>');
          $throbber.attr('src', 'https://upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif');
          $throbber.css('width', size);
          $throbber.css('height', size);
          $elem.data('rastThrobber', $throbber);
          $elem[position]($throbber);
          $elem.addClass('withRastThrobber');
        }
        return $elem;
      },
      asnavSelect: function(id) {
        var $tabs, first, tabContent;
        $tabs = $(this);
        $tabs.find('.asnav-content').hide();
        $tabs.find('.asnav-tabs .asnav-selectedtab').removeClass('asnav-selectedtab');
        tabContent = $tabs.find('.asnav-tabs [data-contentid="' + id + '"]:first');
        if (tabContent.length) {
          tabContent.addClass('asnav-selectedtab');
          return $tabs.find('#' + id).show();
        } else {
          first = $tabs.find('.asnav-tabs [data-contentid]:first').addClass('asnav-selectedtab');
          return $tabs.find('#' + first.attr('data-contentid')).show();
        }
      },
      etMakeTabs: function(activeTabId) {
        var selectFunc, tabs;
        tabs = $(this);
        selectFunc = function(a) {
          var $a;
          $a = $(a);
          tabs.asnavSelect($a.attr('data-contentid'));
          return $a.trigger('asNav:select', $a.attr('data-contentid'));
        };
        tabs.on('click', '.asnav-tabs [data-contentid]', function() {
          return selectFunc(this);
        });
        return tabs.asnavSelect(activeTabId);
      }
    });
    return $.fn.extend({
      insertTag: function(beginTag, endTag) {
        return this.each(function() {
          var SelReplace, pos, sel;
          SelReplace = void 0;
          pos = void 0;
          sel = void 0;
          SelReplace = function(s) {
            return rast.insertion.replaceSpecsymbols(s, '\\$', function(c) {
              if (c === '\\') {
                return '';
              } else if (c === '$') {
                return sel;
              }
            });
          };
          $(this).focus();
          sel = $(this).textSelection('getSelection');
          beginTag = SelReplace(beginTag);
          endTag = endTag ? SelReplace(endTag) : '';
          $(this).textSelection('encapsulateSelection', {
            pre: beginTag || '',
            peri: '',
            post: endTag || '',
            replace: true
          });
          if (endTag && sel !== '') {
            pos = $(this).textSelection('getCaretPosition');
            return $(this).textSelection('setSelection', {
              start: pos - endTag.length
            });
          }
        });
      },
      setSelection: function(text) {
        return this.textSelection('encapsulateSelection', {
          post: text,
          replace: true
        });
      },
      getSelection: function(text) {
        return this.textSelection('getSelection');
      }
    });
  },
  name: function(constructor) {
    return 'rast.' + constructor.name;
  },
  clone: (function() {
    var clone;

    /**
     * Clones (copies) an Object using deep copying.
    #
     * This function supports circular references by default, but if you are certain
     * there are no circular references in your object, you can save some CPU time
     * by calling clone(obj, false).
    #
     * Caution: if `circular` is false and `parent` contains circular references,
     * your program may enter an infinite loop and crash.
    #
     * @param `parent` - the object to be cloned
     * @param `circular` - set to true if the object to be cloned may contain
     *    circular references. (optional - true by default)
     * @param `depth` - set to a number if the object is only to be cloned to
     *    a particular depth. (optional - defaults to Infinity)
     * @param `prototype` - sets the prototype to be used when cloning an object.
     *    (optional - defaults to parent prototype).
     */
    var __getRegExpFlags, __isArray, __isDate, __isRegExp, __objToStr, clone;
    clone = function(parent, circular, depth, prototype) {
      var _clone, allChildren, allParents, filter, useBuffer;
      filter = void 0;
      _clone = function(parent, depth) {
        var attrs, child, i, index, proto;
        if (parent === null) {
          return null;
        }
        if (depth === 0) {
          return parent;
        }
        child = void 0;
        proto = void 0;
        if (typeof parent !== 'object') {
          return parent;
        }
        if (clone.__isArray(parent)) {
          child = [];
        } else if (clone.__isRegExp(parent)) {
          child = new RegExp(parent.source, __getRegExpFlags(parent));
          if (parent.lastIndex) {
            child.lastIndex = parent.lastIndex;
          }
        } else if (clone.__isDate(parent)) {
          child = new Date(parent.getTime());
        } else if (useBuffer && Buffer.isBuffer(parent)) {
          child = new Buffer(parent.length);
          parent.copy(child);
          return child;
        } else {
          if (typeof prototype === 'undefined') {
            proto = Object.getPrototypeOf(parent);
            child = Object.create(proto);
          } else {
            child = Object.create(prototype);
            proto = prototype;
          }
        }
        if (circular) {
          index = allParents.indexOf(parent);
          if (index !== -1) {
            return allChildren[index];
          }
          allParents.push(parent);
          allChildren.push(child);
        }
        for (i in parent) {
          attrs = void 0;
          if (proto) {
            attrs = Object.getOwnPropertyDescriptor(proto, i);
          }
          if (attrs && attrs.set === null) {
            continue;
          }
          child[i] = _clone(parent[i], depth - 1);
        }
        return child;
      };
      if (typeof circular === 'object') {
        depth = circular.depth;
        prototype = circular.prototype;
        filter = circular.filter;
        circular = circular.circular;
      }
      allParents = [];
      allChildren = [];
      useBuffer = typeof Buffer !== 'undefined';
      if (typeof circular === 'undefined') {
        circular = true;
      }
      if (typeof depth === 'undefined') {
        depth = Infinity;
      }
      return _clone(parent, depth);
    };
    __objToStr = function(o) {
      return Object.prototype.toString.call(o);
    };
    __isDate = function(o) {
      return typeof o === 'object' && __objToStr(o) === '[object Date]';
    };
    __isArray = function(o) {
      return typeof o === 'object' && __objToStr(o) === '[object Array]';
    };
    __isRegExp = function(o) {
      return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
    };
    __getRegExpFlags = function(re) {
      var flags;
      flags = '';
      if (re.global) {
        flags += 'g';
      }
      if (re.ignoreCase) {
        flags += 'i';
      }
      if (re.multiline) {
        flags += 'm';
      }
      return flags;
    };
    'use strict';

    /**
     * Simple flat clone using prototype, accepts only objects, usefull for property
     * override on FLAT configuration object (no nested props).
    #
     * USE WITH CAUTION! This may not behave as you wish if you do not know how this
     * works.
     */
    clone.clonePrototype = function(parent) {
      var c;
      if (parent === null) {
        return null;
      }
      c = function() {};
      c.prototype = parent;
      return new c;
    };
    clone.__objToStr = __objToStr;
    clone.__isDate = __isDate;
    clone.__isArray = __isArray;
    clone.__isRegExp = __isRegExp;
    clone.__getRegExpFlags = __getRegExpFlags;
    return clone;
  })(),
  focusWithoutScroll: function(elem) {
    var x, y;
    x = void 0;
    y = void 0;
    x = void 0;
    y = void 0;
    if (typeof window.pageXOffset !== 'undefined') {
      x = window.pageXOffset;
      y = window.pageYOffset;
    } else if (typeof window.scrollX !== 'undefined') {
      x = window.scrollX;
      y = window.scrollY;
    } else if (document.documentElement && typeof document.documentElement.scrollLeft !== 'undefined') {
      x = document.documentElement.scrollLeft;
      y = document.documentElement.scrollTop;
    } else {
      x = document.body.scrollLeft;
      y = document.body.scrollTop;
    }
    elem.focus();
    if (typeof x !== 'undefined') {
      return setTimeout((function() {
        window.scrollTo(x, y);
      }), 100);
    }
  },
  processSelection: function(txtFunc) {
    var $textarea, txt;
    $textarea = rast.$getTextarea();
    txt = $textarea.getSelection();
    return $textarea.setSelection(txtFunc(txt));
  },
  perLineReplace: function(str, regex, to) {
    var i, len;
    str = str.split('\n');
    len = str.length;
    i = 0;
    while (i < len) {
      str[i] = str[i].replace(regex, to);
      i += 1;
    }
    return str.join('\n');
  },
  linkifyList: function(s) {
    return rast.perLineReplace(s, /[^*;#—\s,][^*\.#—;,]+/g, '[[$&]]');
  },
  simpleList: function(s) {
    return rast.perLineReplace(s, /(([\*#]*)\s*)(.+)/g, '*$2 $3');
  },
  numericList: function(s) {
    return rast.perLineReplace(s, /(([\*#]*)\s*)(.+)/g, '#$2 $3');
  },
  dot: '·п',
  searchAndReplace: {
    getReplaceForm: function() {
      return '\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div id="et-replace-message">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div id="et-replace-nomatch">Нема збігів</div>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div id="et-replace-success">Заміни виконано</div>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div id="et-replace-emptysearch">Вкажіть рядок до пошуку</div>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div id="et-replace-invalidregex">Неправильний регулярний вираз</div>\u0009\u0009\u0009\u0009\u0009\u0009\u0009</div>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<span class="et-field-wrapper">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<label for="et-replace-search" style="float: left; min-width: 6em;">Шукати</label>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<span style="display: block; overflow: hidden;">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009  <input type="text" id="et-replace-search" style="width: 100%;"/>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009</span>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009</span>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div style="clear: both;"/>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<span class="et-field-wrapper">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<label for="et-replace-replace" style="float: left; min-width: 6em;">Заміна</label>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<span style="display: block; overflow: hidden;">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009  <input type="text" id="et-replace-replace" style="width: 100%;"/>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009</span>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009</span>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<div style="clear: both;"/>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<input id="et-tool-replace-button-findnext" type="button" value="Шукати" />\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<input id="et-tool-replace-button-replace" type="button" value="Замінити" />\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<input id="et-tool-replace-button-replaceall" type="button" value="Замінити все" />\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<span class="et-field-wrapper">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<input type="checkbox" id="et-replace-case"/>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<label for="et-replace-case">Враховувати регістр</label>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009</span>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<span class="et-field-wrapper">\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<input type="checkbox" id="et-replace-regex"/>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009<label for="et-replace-regex">Регулярний вираз</label>\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009</span>\u0009\u0009\u0009';
    },
    replaceFormInit: function() {
      rast.searchAndReplace.offset = 0;
      rast.searchAndReplace.matchIndex = 0;
      $(document).off('click', '#et-tool-replace-button-findnext').on('click', '#et-tool-replace-button-findnext', function(e) {
        return rast.searchAndReplace.doSearchReplace('find');
      });
      $(document).off('click', '#et-tool-replace-button-replace').on('click', '#et-tool-replace-button-replace', function(e) {
        return rast.searchAndReplace.doSearchReplace('replace');
      });
      $(document).off('click', '#et-tool-replace-button-replaceall').on('click', '#et-tool-replace-button-replaceall', function(e) {
        return rast.searchAndReplace.doSearchReplace('replaceAll');
      });
      return $('#et-replace-nomatch, #et-replace-success,\u0009\u0009\u0009 #et-replace-emptysearch, #et-replace-invalidregex').hide();
    },
    doSearchReplace: function(mode) {
      var $textarea, actualReplacement, context, e, end, flags, i, index, isRegex, match, matchCase, newText, offset, regex, replaceStr, searchStr, start, text, textRemainder;
      offset = void 0;
      textRemainder = void 0;
      regex = void 0;
      index = void 0;
      i = void 0;
      start = void 0;
      end = void 0;
      $('#et-replace-nomatch, #et-replace-success,\u0009\u0009\u0009 #et-replace-emptysearch, #et-replace-invalidregex').hide();
      searchStr = $('#et-replace-search').val();
      if (searchStr === '') {
        $('#et-replace-emptysearch').show();
        return;
      }
      replaceStr = $('#et-replace-replace').val();
      flags = 'm';
      matchCase = $('#et-replace-case').is(':checked');
      if (!matchCase) {
        flags += 'i';
      }
      isRegex = $('#et-replace-regex').is(':checked');
      if (!isRegex) {
        searchStr = mw.RegExp.escape(searchStr);
      }
      if (mode === 'replaceAll') {
        flags += 'g';
      }
      try {
        regex = new RegExp(searchStr, flags);
      } catch (_error) {
        e = _error;
        $('#et-replace-invalidregex').show();
        return;
      }
      $textarea = rast.$getTextarea();
      text = $textarea.textSelection('getContents');
      match = false;
      if (mode !== 'replaceAll') {
        if (mode === 'replace') {
          offset = rast.searchAndReplace.matchIndex;
        } else {
          offset = rast.searchAndReplace.offset;
        }
        textRemainder = text.substr(offset);
        match = textRemainder.match(regex);
      }
      if (!match) {
        offset = 0;
        textRemainder = text;
        match = textRemainder.match(regex);
      }
      if (!match) {
        $('#et-replace-nomatch').show();
        return;
      }
      if (mode === 'replaceAll') {
        newText = text.replace(regex, replaceStr);
        $textarea.select().textSelection('encapsulateSelection', {
          'peri': newText,
          'replace': true
        });
        $('#et-replace-success').text('Здійснено замін: ' + match.length).show();
        rast.searchAndReplace.offset = 0;
        return rast.searchAndReplace.matchIndex = 0;
      } else {
        if (mode === 'replace') {
          actualReplacement = void 0;
          if (isRegex) {
            actualReplacement = match[0].replace(regex, replaceStr);
          } else {
            actualReplacement = replaceStr;
          }
          if (match) {
            $textarea.textSelection('encapsulateSelection', {
              'peri': actualReplacement,
              'replace': true
            });
            text = $textarea.textSelection('getContents');
          }
          offset = offset + match[0].length + actualReplacement.length;
          textRemainder = text.substr(offset);
          match = textRemainder.match(regex);
          if (match) {
            start = offset + match.index;
            end = start + match[0].length;
          } else {
            textRemainder = text;
            match = textRemainder.match(regex);
            if (match) {
              start = match.index;
              end = start + match[0].length;
            } else {
              start = 0;
              end = 0;
            }
          }
        } else {
          start = offset + match.index;
          end = start + match[0].length;
        }
        rast.searchAndReplace.matchIndex = start;
        $textarea.textSelection('setSelection', {
          'start': start,
          'end': end
        });
        $textarea.textSelection('scrollToCaretPosition');
        rast.searchAndReplace.offset = end;
        context = rast.searchAndReplace.context;
        return $textarea[0].focus();
      }
    }
  },
  ieVersion: function() {
    var all, div, v;
    v = 3;
    div = document.createElement('div');
    all = div.getElementsByTagName('i');
    while ((div.innerHTML = '<!--[if gt IE ' + ++v + ']><i></i><![endif]-->', all[0])) {
      0;
    }
    if (v > 4) {
      return v;
    } else {
      return void 0;
    }
  }
};

rast.PanelDrawer = (function() {
  function PanelDrawer($panel1, subsetWrapper1, index1, mode1, subsets1, eventsHandler) {
    this.$panel = $panel1;
    this.subsetWrapper = subsetWrapper1;
    this.index = index1;
    this.mode = mode1;
    this.subsets = subsets1;
    this.eventsHandler = eventsHandler;
  }

  PanelDrawer.prototype.draw = function() {
    var generateMethod;
    if (this.mode === 'edit') {
      return this.drawEditMode();
    } else if (this.mode === 'view') {
      generateMethod = 'generateHtml';
      return this.generateHtml(this.$panel, this.subsetWrapper.slots, generateMethod);
    }
  };

  PanelDrawer.prototype.sortableSlots = function($slots) {
    return $($slots).sortable({
      delay: 150,
      containment: $slots,
      forceHelperSize: true,
      forcePlaceholderSize: true,
      items: '[data-id]',
      start: function(event, ui) {
        var copy;
        return copy = $(ui.item[0].outerHTML).clone();
      },
      placeholder: {
        element: function(copy, ui) {
          return $('<span class="ui-state-highlight">' + copy[0].innerHTML + '</li>');
        },
        update: function() {}
      },
      receive: (function(_this) {
        return function(event, ui) {
          var index, newSlot, slotClass;
          slotClass = eval(ui.item.attr('data-slot-class'));
          index = $(event.target).data().sortable.currentItem.index();
          newSlot = _this.subsets.addSlot(slotClass, _this.subsetWrapper, index);
          return _this.eventsHandler.onSlotAdded(newSlot);
        };
      })(this),
      update: (function(_this) {
        return function(event, ui) {
          var newSlotIndex, slot, slotId;
          newSlotIndex = ui.item.index('[data-id]') - 1;
          if (newSlotIndex < 0) {
            return;
          }
          if (!$(ui.item).attr('data-id')) {
            return;
          }
          slotId = parseInt($(ui.item).attr('data-id'));
          slot = _this.subsets.slotById(slotId);
          _this.rearrangeSlot(slot, newSlotIndex);
          return _this.updatePreview();
        };
      })(this),
      revert: true
    });
  };

  PanelDrawer.prototype.drawEditMode = function() {
    var $nameInput, $nameInputContainer, $nameLabel, $panelRemoveButton, $preview, $previewContent, $removeIcon, $slots, generateMethod;
    $nameLabel = $('<label class="panelNameLabel">Назва:</label>');
    $nameInputContainer = $('<div>');
    $nameInputContainer.append($nameLabel);
    $nameInput = $('<input type="text">').val(this.subsetWrapper.caption);
    $nameInput.change({
      subsetWrapper: this.subsetWrapper
    }, this.eventsHandler.onTabNameChanged);
    $nameInputContainer.append($nameInput);
    $nameInputContainer.appendTo(this.$panel);
    $panelRemoveButton = $('<span class="panelRemoveButton">Вилучити панель</span>');
    $panelRemoveButton.click((function(_this) {
      return function() {
        return _this.eventsHandler.onRemoveSubsetClick(_this.subsetWrapper);
      };
    })(this));
    $removeIcon = $('<span class="removeIcon">');
    $nameInputContainer.append($panelRemoveButton);
    $panelRemoveButton.append($removeIcon);
    $slots = $('<div class="slots">');
    this.$panel.append($slots);
    generateMethod = 'generateEditHtml';
    this.sortableSlots($slots);
    if (!this.subsetWrapper.slots.length) {
      $slots.append('<span>Щоб додати комірку, сюди перетягніть потрібний вид з бічної панелі.</span>');
    } else {
      this.generateHtml($slots, this.subsetWrapper.slots, generateMethod);
    }
    $preview = $('<div>').css('border-top', '1px solid color: #aaa').addClass('preview');
    $preview.append($('<div>Попередній перегляд:</div>'));
    $previewContent = $('<div class="content">');
    this.generateHtml($previewContent, this.subsetWrapper.slots, 'generateHtml');
    $preview.append($previewContent);
    return this.$panel.append($preview);
  };

  PanelDrawer.prototype.generateHtml = function($slotsContainer, slots, generateMethod) {
    var k, len1, results1, slot;
    results1 = [];
    for (k = 0, len1 = slots.length; k < len1; k++) {
      slot = slots[k];
      results1.push($slotsContainer.append(slot[generateMethod]()));
    }
    return results1;
  };

  PanelDrawer.prototype.updatePreview = function(subsetWrapper) {
    var $previewContent;
    $previewContent = this.$panel.find('.preview .content');
    $previewContent.empty();
    return this.generateHtml($previewContent, this.subsetWrapper.slots, 'generateHtml');
  };

  PanelDrawer.prototype.rearrangeSlot = function(slot, newSlotIndex) {
    var slotIndex;
    slotIndex = this.subsets.slotIndex(slot);
    return rast.arrayMove(this.subsetWrapper.slots, slotIndex, newSlotIndex);
  };

  return PanelDrawer;

})();

rast.Drawer = (function() {
  function Drawer() {}

  Drawer.prototype.$editButtonIcon = function() {
    return $('<div class="gear">');
  };

  Drawer.prototype.$editButton = function() {
    var $editButton;
    $editButton = this.$editButtonIcon();
    $editButton.addClass('menuButton edit');
    return $editButton.attr('title', 'Редагувати символи.');
  };

  Drawer.prototype.drawMenu = function() {
    var $aboutLink, $cancelButton, $dot, $editButton, $menu, $persistButton, $resetButton, $saveButton;
    $menu = $('<div class="rastMenu">');
    $menu.addClass(this.mode);
    if (this.mode === 'view') {
      $editButton = this.$editButton();
      $editButton.click(this.eventsHandler.onEditClick);
      $menu.append($editButton);
    } else if (this.mode === 'edit') {
      $dot = function() {
        return $('<span> · </span>');
      };
      $persistButton = $('<span class="menuButton">').attr('title', 'Символи буде збережено у Вашому особистому просторі. Для цього виконається редагування підсторінки від Вашого імени.');
      $persistButton.text('зберегти на підсторінку').click(this.eventsHandler.onPersistClick);
      $saveButton = $('<span class="menuButton">').attr('title', 'Зміни збережуться тільки на час редагування сторінки і втратяться після закриття або перевантаження сторінки.');
      $saveButton.text('зберегти').click(this.eventsHandler.onSaveClick);
      $cancelButton = $('<span class="menuButton">');
      $cancelButton.text('скасувати').click(this.eventsHandler.onCancelClick).attr('title', 'Всі зміни цієї сесії редагування будуть відкинуті.');
      $resetButton = $('<span class="menuButton">');
      $resetButton.text('відновити звичаєві').click(this.eventsHandler.onResetClick).attr('title', 'Буде відновлено набір символів за промовчанням.');
      $aboutLink = $("<a class=\"aboutLink\" target=\"_blank\" href=\"" + this.docLink + "\">про додаток</a>");
      $menu.append($persistButton, $dot(), $saveButton, $dot(), $cancelButton, $dot(), $resetButton, $aboutLink);
    }
    return this.$container.append($menu);
  };

  Drawer.prototype.drawTab = function($container, text) {
    var $a, $adiv;
    $a = $('<a>');
    $adiv = $('<div>');
    $a.text(text);
    $adiv.append($a);
    $container.append($adiv);
    return $adiv;
  };

  Drawer.prototype.drawTabs = function($container) {
    var $adiv, i, id;
    i = 0;
    while (i < this.subsets.subsets.length) {
      $adiv = this.drawTab($container, this.subsets.subsets[i].caption);
      id = 'etTabContent' + i;
      if (this.activeTab === id) {
        $adiv.addClass('asnav-selectedtab');
      }
      $adiv.attr('data-contentid', id);
      $adiv.click(this.eventsHandler.onTabClick);
      i++;
    }
    return $container;
  };

  Drawer.prototype.drawNavigation = function() {
    var $addNewdiv, $outline, $tabs;
    $outline = $('<span>').addClass('asnav-tabs').addClass('specialchars-tabs');
    $tabs = $('<div class="existingTabs">');
    this.drawTabs($tabs);
    $outline.append($tabs);
    if (this.mode === 'edit') {
      $addNewdiv = this.drawTab($outline, '+ панель');
      $addNewdiv.addClass('newPanelButton');
      $addNewdiv.attr('title', 'Додати нову панель');
      $addNewdiv.click(this.eventsHandler.onAddSubsetClick);
    }
    this.$container.append($outline);
    if (this.mode === 'edit' && this.subsets.subsets.length) {
      return this.drawSlotClasses($outline);
    }
  };

  Drawer.prototype.drawSlotClasses = function($outline) {
    var $hint, $slot, $slots, k, len1, ref, slotClass;
    $slots = $('<div class="slotClasses">');
    $hint = $('<div title="Щоб створити комірку, перетягніть потрібний вид в область редагування (область редагування обведена штриховим обідком).">Види комірок:</div>');
    $slots.append($hint);
    ref = this.slotClasses;
    for (k = 0, len1 = ref.length; k < len1; k++) {
      slotClass = ref[k];
      $slot = $('<div class="slotClass">');
      $slot.attr('data-slot-class', rast.name(slotClass));
      $slot.text(slotClass.caption);
      $slot.attr('title', 'Перетягніть на панель, щоб вставити цей вид комірки');
      $slots.append($slot);
    }
    $outline.append($slots);
    return $slots.find('.slotClass').draggable({
      connectToSortable: '.etPanel .slots',
      helper: 'clone'
    });
  };

  Drawer.prototype.draw = function() {
    this.$container.find('[original-title]').each(function(i, elem) {
      var base;
      return typeof (base = $(elem)).tipsy === "function" ? base.tipsy('hide') : void 0;
    });
    return mw.loader.using(['jquery.ui', 'jquery.tipsy'], (function(_this) {
      return function() {
        var $titled;
        _this.$container.empty();
        _this.drawMenu();
        _this.drawMessage();
        _this.drawNavigation();
        _this.drawPanels();
        $titled = _this.$container.find('[title]');
        $titled.tipsy({
          trigger: 'manual'
        });
        $titled.mouseenter(function() {
          var $this, hideTimeout;
          $this = $(this);
          $this.tipsy('show');
          hideTimeout = setTimeout(function() {
            return $this.tipsy('hide');
          }, 3000);
          return $this.data('hideTimeout', hideTimeout);
        });
        return $titled.mouseleave(function() {
          var $this, hideTimeout;
          $this = $(this);
          $this.tipsy('hide');
          hideTimeout = $this.data('hideTimeout');
          if (hideTimeout) {
            return clearTimeout(hideTimeout);
          }
        });
      };
    })(this));
  };

  Drawer.prototype.drawMessage = function() {
    return this.$container.append(this.message);
  };

  Drawer.prototype.drawPanels = function() {
    var $content, $subset, $subsetDiv, i;
    $content = $('<div>').attr('id', 'etContent').addClass('overflowHidden');
    this.$container.append($content);
    i = 0;
    while (i < this.subsets.subsets.length) {
      $subset = this.drawPanel(this.subsets.subsets[i], i);
      $subsetDiv = $('<div>').attr('id', 'etTabContent' + i).attr('data-id', this.subsets.subsets[i].id).appendTo($content).addClass('asnav-content').append($subset);
      i++;
    }
    this.$container.etMakeTabs(true);
    this.$container.append($('<div>').css('clear', 'both'));
    return this.$container.asnavSelect(this.activeTab);
  };

  Drawer.prototype.drawPanel = function(subsetWrapper, index) {
    var $panel, panelDrawer;
    $panel = $('<div>').attr('id', 'spchars-' + index).addClass('etPanel');
    panelDrawer = new rast.PanelDrawer($panel, subsetWrapper, index, this.mode, this.subsets, this.eventsHandler);
    panelDrawer.draw();
    return $panel;
  };

  return Drawer;

})();

rast.PlainObjectParser = (function() {
  function PlainObjectParser() {}

  PlainObjectParser.charinsertDivider = ' ';

  PlainObjectParser.parseTokens = function(arr, hotkeysHandler) {
    var k, len1, slot, slots, token;
    slots = [];
    for (k = 0, len1 = arr.length; k < len1; k++) {
      token = arr[k];
      if (typeof token === 'string') {
        slots = slots.concat(this.strToMultipleInsertionsSlot(token));
      } else if (Object.prototype.toString.call(token) === '[object Array]') {
        slots.push(this.slotFromArr(token));
      } else if (typeof token === 'object') {
        slot = this.slotFromPlainObj(token, hotkeysHandler);
        if (slot) {
          slots.push(slot);
        }
      }
    }
    return slots;
  };

  PlainObjectParser.slotFromArr = function(arr) {
    return new rast.InsertionSlot({
      insertion: arr[0],
      caption: arr[1]
    });
  };

  PlainObjectParser.slotsFromStr = function(str) {
    var k, len1, slot, slots, token, tokens;
    tokens = str.split(' ');
    slots = [];
    slot = void 0;
    for (k = 0, len1 = tokens.length; k < len1; k++) {
      token = tokens[k];
      slot = this.slotFromStr(token);
      slots.push(slot);
    }
    return slots;
  };

  PlainObjectParser.strToMultipleInsertionsSlot = function(str) {
    var slot, slots;
    slots = [];
    slot = new rast.MultipleInsertionsSlot({
      insertion: str
    });
    slots.push(slot);
    return slots;
  };

  PlainObjectParser.lineReplace = function(c) {
    if (c === '\\') {
      return '\\';
    } else if (c === '_') {
      return ' ';
    }
  };

  PlainObjectParser.slotFromStr = function(token) {
    var modifiers, readModifiers, slot, tags;
    readModifiers = function() {
      var c, i, res;
      res = {
        bold: false,
        plain: false,
        italic: false
      };
      i = token.length - 1;
      c = void 0;
      while (i > -1 && !rast.insertion.isEscaped(token, i)) {
        c = token.charAt(i).toLowerCase();
        if (c === 'ж') {
          res.bold = true;
        } else if (c === 'н') {
          res.italic = true;
        } else if (c === 'п') {
          res.plain = true;
        } else {
          break;
        }
        token = token.substring(0, i);
        i--;
      }
      return res;
    };
    modifiers = readModifiers();
    slot = void 0;
    if (modifiers.plain || token === '' || token === '_') {
      slot = new rast.PlainTextSlot({
        bold: modifiers.bold,
        italic: modifiers.italic
      });
      if (token === '' || token === '_') {
        slot.text = this.charinsertDivider + ' ';
      } else {
        slot.text = rast.insertion.replaceSpecsymbols(token, '\\_', this.lineReplace) + ' ';
      }
    } else {
      tags = this.parseInsertion(token, '');
      slot = new rast.InsertionSlot({
        bold: modifiers.bold,
        italic: modifiers.italic,
        insertion: token,
        caption: tags.caption
      });
    }
    return slot;
  };

  PlainObjectParser.generateLink = function(obj) {
    var slot;
    slot = void 0;
    if (obj.ins || obj.insert) {
      slot = new rast.InsertionSlot({});
      $.extend(slot, this.parseInsertion(obj.ins || obj.insert, obj.cap || obj.caption, {
        bold: obj.b || obj.bold,
        italic: obj.i || obj.italic
      }));
    } else if (obj.func) {
      slot = new rast.InsertionSlot({
        clickFunc: obj.func,
        useClickFunc: true,
        caption: obj.cap || obj.caption || obj.ins
      });
      $.extend(slot, {
        bold: obj.b || obj.bold,
        italic: obj.i || obj.italic
      });
    }
    return slot;
  };

  PlainObjectParser.parseInsertion = function(token, caption) {
    var n, tagClose, tagOpen;
    tagOpen = token;
    tagClose = '';
    n = rast.insertion.indexOfUnescaped(token, '+');
    if (n > -1) {
      tagOpen = token.substring(0, n);
      tagClose = token.substring(n + 1);
    }
    tagOpen = rast.insertion.replaceSpecsymbols(tagOpen, '\\_', this.lineReplace);
    tagClose = rast.insertion.replaceSpecsymbols(tagClose, '\\_', this.lineReplace);
    if (!caption) {
      caption = tagOpen + tagClose + ' ';
      caption = rast.insertion.replaceSpecsymbols(caption, '\\$', function(c) {
        if (c === '$') {
          return '';
        } else if (c === '\\') {
          return '';
        }
      });
    }
    return {
      caption: caption,
      tagOpen: tagOpen,
      tagClose: tagClose
    };
  };

  PlainObjectParser.slotFromPlainObj = function(obj, hotkeysHandler) {
    var slot;
    slot = void 0;
    if (obj.plain) {
      slot = new rast.PlainTextSlot({
        text: obj.cap || obj.caption,
        bold: obj.b || obj.bold,
        italic: obj.i || obj.italic
      });
    } else if (obj.html) {
      slot = new rast.HtmlSlot({
        html: obj.html,
        onload: obj.onload
      });
    } else {
      slot = this.generateLink(obj);
      if (!slot) {
        return;
      }
      hotkeysHandler.processShortcut(slot, obj);
    }
    return slot;
  };

  return PlainObjectParser;

})();

rast.SubsetsManager = (function() {
  function SubsetsManager() {
    this.reset();
  }

  SubsetsManager.prototype.slotById = function(id) {
    var k, l, len1, len2, ref, ref1, slot, subset;
    ref = this.subsets;
    for (k = 0, len1 = ref.length; k < len1; k++) {
      subset = ref[k];
      ref1 = subset.slots;
      for (l = 0, len2 = ref1.length; l < len2; l++) {
        slot = ref1[l];
        if (slot.id === id) {
          return slot;
        }
      }
    }
    return null;
  };

  SubsetsManager.prototype.slotIndex = function(slot) {
    var k, len1, ref, slotIndex, subset;
    ref = this.subsets;
    for (k = 0, len1 = ref.length; k < len1; k++) {
      subset = ref[k];
      slotIndex = subset.slots.indexOf(slot);
      if (slotIndex > -1) {
        return slotIndex;
      }
    }
    return null;
  };

  SubsetsManager.prototype.subsetBySlot = function(slot) {
    var k, len1, ref, subset;
    ref = this.subsets;
    for (k = 0, len1 = ref.length; k < len1; k++) {
      subset = ref[k];
      if (subset.slots.indexOf(slot) > -1) {
        return subset;
      }
    }
    return null;
  };

  SubsetsManager.prototype.subsetBySlotId = function(slot) {
    var k, l, len1, len2, ref, ref1, subset;
    ref = this.subsets;
    for (k = 0, len1 = ref.length; k < len1; k++) {
      subset = ref[k];
      ref1 = subset.slots;
      for (l = 0, len2 = ref1.length; l < len2; l++) {
        slot = ref1[l];
        if (slot.id === id) {
          return subset;
        }
      }
    }
    return null;
  };

  SubsetsManager.prototype.subsetById = function(id) {
    var k, len1, ref, subset;
    ref = this.subsets;
    for (k = 0, len1 = ref.length; k < len1; k++) {
      subset = ref[k];
      if (subset.id === id) {
        return subset;
      }
    }
    return null;
  };

  SubsetsManager.prototype.processShortcut = function(slot, obj) {
    var key;
    if (obj.key) {
      if (typeof obj.key === 'string') {
        key = obj.key[0].toUpperCase();
        slot.key = key;
        if (obj.func) {
          this.hotkeys[key] = obj.func;
        }
        if (obj.ins || obj.insert) {
          return this.hotkeys[key] = (function(a) {
            return a;
          })(slot);
        }
      }
    }
  };

  SubsetsManager.prototype.addSubset = function(caption, index) {
    var subset;
    subset = {
      caption: caption,
      slots: [],
      id: this.uniqueSubsetId()
    };
    return this.insertOrAppend(this.subsets, index, subset);
  };

  SubsetsManager.prototype.deleteSubset = function(subsetToBeRemoved) {
    return this.subsets = $.grep(this.subsets, function(subset, index) {
      return subsetToBeRemoved.id !== subset.id;
    });
  };

  SubsetsManager.prototype.addSlot = function(slotClassOrSlot, subset, index) {
    var slot;
    if (slotClassOrSlot instanceof rast.Slot) {
      slot = slotClassOrSlot;
      slot.id = this.uniqueSlotId();
    } else {
      slot = new slotClassOrSlot({
        id: this.uniqueSlotId()
      });
    }
    return this.insertOrAppend(subset.slots, index, slot);
  };

  SubsetsManager.prototype.deleteSlot = function(slotId) {
    var slot, slotIndex, subset;
    if (!(typeof slotId === 'number')) {
      return;
    }
    slot = this.slotById(slotId);
    slotIndex = this.slotIndex(slot);
    subset = this.subsetBySlot(slot);
    return subset.slots.splice(slotIndex, 1);
  };

  SubsetsManager.prototype.uniqueSlotId = function() {
    var result;
    result = this.slotId;
    this.slotId++;
    return result;
  };

  SubsetsManager.prototype.uniqueSubsetId = function() {
    var result;
    result = this.subsetId;
    this.subsetId++;
    return result;
  };

  SubsetsManager.prototype.insertOrAppend = function(arr, index, item) {
    if (index) {
      arr.splice(index, 0, item);
    } else {
      arr.push(item);
    }
    return item;
  };

  SubsetsManager.prototype.reset = function() {
    var self;
    this.subsets = [];
    this.hotkeys = {};
    self = this;
    this.slotId = 0;
    return this.subsetId = 0;
  };

  SubsetsManager.prototype.readEncodedSubsets = function(encodedSubsets) {
    var j, len, results, subset;
    results = [];
    j = 0;
    len = encodedSubsets.length;
    while (j < len) {
      subset = encodedSubsets[j];
      results.push(this.readEncodedSubset(subset));
      j++;
    }
    return results;
  };

  SubsetsManager.prototype.decodeSubset = function(encodedSubset) {
    var slots;
    slots = rast.PlainObjectParser.parseTokens(encodedSubset.symbols, this, this);
    return {
      slots: slots,
      caption: encodedSubset.caption
    };
  };

  SubsetsManager.prototype.readEncodedSubset = function(encodedSubset) {
    var internalSubset, k, len1, ref, results1, slot, subset;
    subset = this.decodeSubset(encodedSubset);
    internalSubset = this.addSubset(subset.caption);
    ref = subset.slots;
    results1 = [];
    for (k = 0, len1 = ref.length; k < len1; k++) {
      slot = ref[k];
      results1.push(this.addSlot(slot, internalSubset));
    }
    return results1;
  };

  SubsetsManager.prototype.toJSON = function() {
    return this.subsets;
  };

  SubsetsManager.prototype.deserialize = function(subsets) {
    return $.each(subsets, (function(_this) {
      return function(i, subset) {
        var cons, s, slot;
        s = _this.addSubset(subset.caption);
        cons = null;
        slot = null;
        return $.each(subset.slots, function(i, plainSlot) {
          cons = eval(plainSlot['class']);
          slot = new cons(plainSlot);
          return _this.addSlot(slot, s);
        });
      };
    })(this));
  };

  return SubsetsManager;

})();

rast.UIwindow = (function() {
  function UIwindow() {}

  UIwindow.show = function($content) {
    var EditDialog, editDialog, windowManager;
    EditDialog = function(config) {
      return EditDialog["super"].call(this, config);
    };
    OO.inheritClass(EditDialog, OO.ui.Dialog);
    EditDialog.static.title = 'Simple dialog';
    EditDialog.static.name = 'Edit dialog';
    EditDialog.prototype.initialize = function() {
      EditDialog["super"].prototype.initialize.call(this);
      this.$body.append($content);
    };
    EditDialog.prototype.getBodyHeight = function() {
      return $content.outerHeight(true);
    };
    editDialog = new EditDialog({
      size: 'large'
    });
    windowManager = new OO.ui.WindowManager;
    $('body').append(windowManager.$element);
    windowManager.addWindows([editDialog]);
    windowManager.openWindow(editDialog);
    return editDialog;
  };

  return UIwindow;

})();

rast.SlotAttributesEditor = (function() {
  function SlotAttributesEditor(options) {
    this.slot = options.slot;
    this.slotsManager = options.slotsManager;
    this.allInputs = [];
  }

  SlotAttributesEditor.prototype.fieldsetForAttrs = function(fieldsetName, attrs) {
    var OOinput, attribute, fieldOptions, fields, fieldset, inputData, inputs, k, len1, type, value;
    inputs = [];
    for (k = 0, len1 = attrs.length; k < len1; k++) {
      attribute = attrs[k];
      value = this.slot[attribute.name];
      type = attribute.type;
      OOinput = type === 'string' || type === 'text' ? (fieldOptions = {
        value: value,
        multiline: type === 'text',
        rows: 3,
        autosize: true
      }, {
        getValue: 'getValue',
        OOobject: new OO.ui.TextInputWidget(fieldOptions)
      }) : type === 'boolean' ? {
        getValue: 'getValue',
        OOobject: new OO.ui.ToggleSwitchWidget({
          value: value
        })
      } : void 0;
      inputData = {
        attribute: attribute.name,
        label: attribute.caption,
        input: OOinput.OOobject,
        getValueFunc: OOinput.getValue,
        labelAlignment: attribute.labelAlignment || 'left',
        helpText: attribute.help
      };
      if (OOinput) {
        this.allInputs.push(inputData);
        inputs.push(inputData);
      }
    }
    fieldset = new OO.ui.FieldsetLayout({
      label: fieldsetName
    });
    fields = $.map(inputs, function(inputWrapper, index) {
      return new OO.ui.FieldLayout(inputWrapper.input, {
        label: inputWrapper.label,
        align: inputWrapper.labelAlignment,
        help: inputWrapper.helpText
      });
    });
    fieldset.addItems(fields);
    return fieldset;
  };

  SlotAttributesEditor.prototype.startEditing = function() {
    var $content, attrs, bottomButtons, cancelButton, dialog, fieldset, panel, removeButton, saveButton, slotClass;
    slotClass = this.slot.constructor;
    attrs = slotClass.editableAttributes;
    $content = $('<div class="rastEditWindow">');
    if (attrs.view) {
      fieldset = this.fieldsetForAttrs('Вигляд', attrs.view);
      $content.append(fieldset.$element);
    }
    if (attrs.functionality) {
      fieldset = this.fieldsetForAttrs('Функціонал', attrs.functionality);
      $content.append(fieldset.$element);
    }
    saveButton = new OO.ui.ButtonWidget({
      icon: 'check',
      label: 'Зберегти'
    });
    saveButton.on('click', (function(_this) {
      return function() {
        var inputWrapper, k, len1, ref;
        ref = _this.allInputs;
        for (k = 0, len1 = ref.length; k < len1; k++) {
          inputWrapper = ref[k];
          _this.slot[inputWrapper.attribute] = inputWrapper.input[inputWrapper.getValueFunc]();
        }
        _this.slotsManager.onSlotSaved();
        return dialog.close();
      };
    })(this));
    cancelButton = new OO.ui.ButtonWidget({
      icon: 'cancel',
      label: 'Скасувати'
    });
    cancelButton.on('click', function() {
      return dialog.close();
    });
    removeButton = new OO.ui.ButtonWidget({
      icon: 'remove',
      label: 'Вилучити комірку'
    });
    removeButton.on('click', (function(_this) {
      return function() {
        var base;
        if (typeof (base = _this.slotsManager).onDeleteSlot === "function") {
          base.onDeleteSlot(_this.slot.id);
        }
        return dialog.close();
      };
    })(this));
    bottomButtons = new OO.ui.HorizontalLayout({
      items: [saveButton, cancelButton, removeButton],
      classes: ['bottomButtons']
    });
    $content.append(bottomButtons.$element);
    panel = new OO.ui.PanelLayout({
      $: $,
      padded: true,
      expanded: false
    });
    panel.$element.append($content);
    return dialog = rast.UIwindow.show(panel.$element);
  };

  return SlotAttributesEditor;

})();

rast.SlotAttributes = (function() {
  function SlotAttributes(attrsObj) {
    $.extend(this, attrsObj);
  }

  SlotAttributes.prototype.toArray = function() {
    var result;
    result = [];
    if (this.view) {
      result = result.concat(this.view);
    }
    if (this.functionality) {
      result = result.concat(this.functionality);
    }
    return result;
  };

  return SlotAttributes;

})();

rast.Slot = (function() {
  Slot.editableAttributes = new rast.SlotAttributes({});

  Slot.editorClass = rast.SlotAttributesEditor;

  function Slot(options) {
    var attribute, k, len1, ref;
    if (options == null) {
      options = {};
    }
    ref = this.constructor.editableAttributes.toArray();
    for (k = 0, len1 = ref.length; k < len1; k++) {
      attribute = ref[k];
      this[attribute.name] = attribute["default"];
    }
    $.extend(this, options, {
      'class': 'rast.' + this.constructor.name
    });
  }

  Slot.prototype.generateEditHtml = function() {
    var $element;
    $element = this.generateHtml();
    $($element).addClass('editedSlot');
    return $element;
  };

  return Slot;

})();

rast.PlainTextSlot = (function(superClass) {
  extend(PlainTextSlot, superClass);

  function PlainTextSlot() {
    return PlainTextSlot.__super__.constructor.apply(this, arguments);
  }

  PlainTextSlot.caption = 'Простий текст';

  PlainTextSlot.editableAttributes = new rast.SlotAttributes({
    view: [
      {
        name: 'css',
        type: 'text',
        "default": '',
        caption: 'CSS-стилі'
      }, {
        name: 'text',
        type: 'text',
        "default": 'текст',
        caption: 'Текст',
        labelAlignment: 'top'
      }
    ]
  });

  PlainTextSlot.prototype.generateEditHtml = function() {
    var $elem;
    $elem = PlainTextSlot.__super__.generateEditHtml.call(this);
    return $elem.attr('title', this.text);
  };

  PlainTextSlot.prototype.generateHtml = function(styles) {
    var $elem;
    $elem = $('<span>');
    $elem.text(this.text);
    $elem.attr('data-id', this.id);
    $elem.attr('style', styles || this.css);
    if (this.bold) {
      $elem.css('font-weight', 'bold');
    }
    if (this.italic) {
      $elem.css('font-style', 'italic');
    }
    return $elem;
  };

  return PlainTextSlot;

})(rast.Slot);

rast.InsertionSlot = (function(superClass) {
  extend(InsertionSlot, superClass);

  function InsertionSlot() {
    return InsertionSlot.__super__.constructor.apply(this, arguments);
  }

  InsertionSlot.caption = 'Одна вставка';

  InsertionSlot.editableAttributes = new rast.SlotAttributes({
    view: [
      {
        name: 'css',
        type: 'text',
        "default": '',
        caption: 'CSS-стилі'
      }, {
        name: 'caption',
        caption: 'Напис',
        type: 'text',
        "default": 'Нова вставка'
      }, {
        name: 'captionAsHtml',
        caption: 'Сприймати напис, як html-код?',
        type: 'boolean',
        "default": false
      }
    ],
    functionality: [
      {
        name: 'insertion',
        caption: 'Текст вставки',
        type: 'text',
        "default": '$',
        labelAlignment: 'top',
        help: 'Символ долара "$" буде замінено на виділений текст. Перший символ додавання "+" позначає місце каретки після вставлення. \nЯкщо хочете екранувати ці символи, поставте "\\" перед потрібним символом; наприклад "\\$" вставлятиме знак долара.'
      }, {
        name: 'useClickFunc',
        caption: 'Замість вставляння виконати іншу дію?',
        type: 'boolean',
        "default": false
      }, {
        name: 'clickFunc',
        caption: 'Інша дія (при клацанні)',
        type: 'text',
        "default": 'function(){  }',
        labelAlignment: 'top'
      }
    ]
  });

  InsertionSlot.insertFunc = function(insertion) {
    var tags;
    rast.$getTextarea().focus();
    tags = rast.PlainObjectParser.parseInsertion(insertion, '');
    return rast.$getTextarea().insertTag(tags.tagOpen, tags.tagClose);
  };

  InsertionSlot.prototype.toJSON = function() {
    var copy;
    copy = rast.clone(this);
    if (this.clickFunc) {
      copy.clickFunc = this.clickFunc.toString();
    }
    return copy;
  };

  InsertionSlot.prototype.generateEditHtml = function() {
    var $elem;
    $elem = InsertionSlot.__super__.generateEditHtml.call(this);
    $elem.attr('title', (this.useClickFunc && this.clickFunc.toString()) || this.insertion);
    return $elem.append($('<div class="overlay">'));
  };

  InsertionSlot.prototype.generateCommonHtml = function(styles) {
    var $a, $elem, caption;
    if (this.captionAsHtml) {
      $elem = $('<div>');
      $elem.append(this.caption);
      $elem.attr('data-id', this.id);
      if (styles) {
        $elem.attr('style', styles);
      }
      if (this.bold) {
        $elem.css('font-weight', 'bold');
      }
      if (this.italic) {
        $elem.css('font-style', 'italic');
      }
      return $elem;
    } else {
      $a = $('<a>');
      $a.attr('data-id', this.id);
      if (styles) {
        $a.attr('style', styles);
      }
      caption = $('<div/>').text(this.caption).html();
      $a.html(caption);
      if (this.bold) {
        $a.css('font-weight', 'bold');
      }
      if (this.italic) {
        $a.css('font-style', 'italic');
      }
      return $a;
    }
  };

  InsertionSlot.prototype.generateHtml = function(styles) {
    var $elem;
    $elem = this.generateCommonHtml(styles || this.css);
    $elem.click((function(_this) {
      return function(event) {
        event.preventDefault();
        if (_this.useClickFunc) {
          return eval('(' + _this.clickFunc + ')()');
        } else {
          return rast.InsertionSlot.insertFunc(_this.insertion);
        }
      };
    })(this));
    return $elem;
  };

  return InsertionSlot;

})(rast.Slot);

rast.MultipleInsertionsSlot = (function(superClass) {
  extend(MultipleInsertionsSlot, superClass);

  function MultipleInsertionsSlot() {
    return MultipleInsertionsSlot.__super__.constructor.apply(this, arguments);
  }

  MultipleInsertionsSlot.caption = 'Набір вставок';

  MultipleInsertionsSlot.editableAttributes = new rast.SlotAttributes({
    view: [
      {
        name: 'css',
        type: 'text',
        "default": '',
        caption: 'CSS-стилі'
      }
    ],
    functionality: [
      {
        name: 'insertion',
        caption: 'Вставки',
        type: 'text',
        "default": 'вставка_1 ·п вставка_2',
        labelAlignment: 'top',
        help: 'Все, що розділене символами пробілу, вважається окремою коміркою. \nЯкщо комірка закірчується символом "п", вона вважатиметься не вставкою, а простим текстом. \nЯкщо хочете включити пробіл у вставку, пишіть нижнє підкреслення: "_". \nСимвол долара "$" буде замінено на виділений текст. Перший символ додавання "+" позначає місце каретки після вставлення. \nЯкщо хочете екранувати ці символи, поставте "\\" перед потрібним символом; наприклад "\\$" вставлятиме знак долара.'
      }
    ]
  });

  MultipleInsertionsSlot.insertFunc = function(insertion) {
    var tags;
    rast.$getTextarea().focus();
    tags = rast.PlainObjectParser.parseInsertion(insertion, '');
    return rast.$getTextarea().insertTag(tags.tagOpen, tags.tagClose);
  };

  MultipleInsertionsSlot.prototype.generateEditHtml = function() {
    var $elem;
    $elem = MultipleInsertionsSlot.__super__.generateEditHtml.call(this);
    $elem.attr('title', this.insertion);
    return $elem.prepend($('<div class="overlay">'));
  };

  MultipleInsertionsSlot.prototype.generateCommonHtml = function(styles) {
    var $elem, $slot, k, len1, slot, slots;
    slots = rast.PlainObjectParser.slotsFromStr(this.insertion);
    $elem = $('<div>');
    $elem.attr('data-id', this.id);
    if (styles) {
      $elem.attr('style', styles);
    }
    if (this.bold) {
      $elem.css('font-weight', 'bold');
    }
    if (this.italic) {
      $elem.css('font-style', 'italic');
    }
    for (k = 0, len1 = slots.length; k < len1; k++) {
      slot = slots[k];
      $slot = $(slot.generateHtml(styles));
      $elem.append($slot);
    }
    return $elem;
  };

  MultipleInsertionsSlot.prototype.generateHtml = function(styles) {
    var $elem;
    $elem = this.generateCommonHtml(styles || this.css);
    return $elem;
  };

  return MultipleInsertionsSlot;

})(rast.Slot);

rast.HtmlSlot = (function(superClass) {
  extend(HtmlSlot, superClass);

  HtmlSlot.caption = 'Довільний код';

  HtmlSlot.editableAttributes = new rast.SlotAttributes({
    view: [
      {
        name: 'html',
        type: 'text',
        "default": '<span>html</span>',
        caption: 'HTML',
        labelAlignment: 'top'
      }
    ],
    functionality: [
      {
        name: 'onload',
        type: 'text',
        "default": 'function(){  }',
        caption: 'JavaScript, що виконається при ініціалізації',
        labelAlignment: 'top'
      }
    ]
  });

  HtmlSlot.prototype.toJSON = function() {
    var copy;
    copy = rast.clone(this);
    if (this.onload) {
      copy.onload = this.onload.toString();
    }
    return copy;
  };

  function HtmlSlot(options) {
    HtmlSlot.__super__.constructor.call(this, options);
    if (typeof this.onload === 'string') {
      this.onload = eval('(' + this.onload + ')');
    }
    if (typeof this.onload === 'function') {
      editTools.addOnloadFunc(this.onload);
    }
  }

  HtmlSlot.prototype.generateEditHtml = function() {
    var $elem;
    $elem = HtmlSlot.__super__.generateEditHtml.call(this);
    return $elem.attr('title', this.html);
  };

  HtmlSlot.prototype.generateHtml = function() {
    var $elem, $overlay, $wrapper;
    $elem = $(this.html);
    $wrapper = $('<div>');
    $wrapper.attr('data-id', this.id);
    $wrapper.append($elem);
    $overlay = $('<div class="overlay">');
    $wrapper.append($overlay);
    return $wrapper;
  };

  return HtmlSlot;

})(rast.Slot);

rast.PageStorage = (function() {
  function PageStorage() {}

  PageStorage.load = function(pagename, onLoaded, handler) {
    return mw.loader.using('mediawiki.api', function() {
      var api;
      api = new mw.Api;
      return api.get({
        action: 'query',
        prop: 'revisions',
        rvprop: 'content',
        titles: pagename
      }).done(function(data) {
        var pageId, results1;
        results1 = [];
        for (pageId in data.query.pages) {
          if (data.query.pages[pageId].revisions) {
            results1.push(typeof onLoaded === "function" ? onLoaded(data.query.pages[pageId].revisions[0]['*']) : void 0);
          } else {
            results1.push(typeof handler.onSubpageNotFound === "function" ? handler.onSubpageNotFound(pageId) : void 0);
          }
        }
        return results1;
      }).fail(function() {
        return handler.onReadFromSubpageError();
      }).always(function() {
        return handler.onEndReadingSubpage();
      });
    });
  };

  PageStorage.save = function(pagename, string, handler) {
    return mw.loader.using('mediawiki.api', function() {
      var api;
      api = new mw.Api;
      return api.postWithEditToken({
        action: 'edit',
        title: pagename,
        summary: '[[Обговорення користувача:AS/rast.js|serialize]]',
        text: string
      }).done(function() {
        return handler.onSavedToSubpage();
      }).fail(function() {
        return handler.onSaveToSubpageError();
      }).always(function() {
        return handler.onEndSavingToSubpage();
      });
    });
  };

  return PageStorage;

})();

$(function() {
  window.editTools = {
    hotkeys: [],
    onloadFuncs: [],
    mode: 'view',
    addOnloadFunc: (function(_this) {
      return function(func) {
        return editTools.onloadFuncs.push(func);
      };
    })(this),
    fireOnloadFuncs: function() {
      var func, k, len1, ref, results1;
      ref = editTools.onloadFuncs;
      results1 = [];
      for (k = 0, len1 = ref.length; k < len1; k++) {
        func = ref[k];
        results1.push(func());
      }
      return results1;
    },
    checkHotkey: function(e) {
      var obj;
      if (e && e.ctrlKey) {
        obj = editTools.hotkeys[String.fromCharCode(e.which).toUpperCase()];
        if (obj) {
          if (typeof obj === 'object') {
            obj.trigger('click');
          } else {
            obj();
          }
          return false;
        }
      }
      return true;
    },
    extraCSS: '#edittools .etPanel .slots [data-id] { margin: -1px -1px 0px 0px; }\n#edittools .etPanel .slots [data-id]:hover { z-index: 1; text-decoration: none; }\n#edittools .etPanel .preview [data-id] { display: inline; padding: 0px 2px; cursor: pointer; }\n#edittools .etPanel > [data-id] { display: inline; padding: 0px 2px; cursor: pointer; }\n#edittools { min-height: 20px; } \n#edittools .rastMenu.view { position: absolute; left: 0px; } \n#edittools .rastMenu.edit { border-bottom: solid #aaaaaa 1px; padding: 2px 6px; } \n#edittools .slots.ui-sortable { min-height: 4em; border-width: 1px; border-style: dashed; margin: 5px 0px; } \n#edittools .slots.ui-sortable .emptyHint {  } \n#edittools .editedSlot { \n  cursor: pointer; \n  min-width: 1em;\n  min-height: 1em;\n  border: 1px solid grey;\n  margin-left: -1px;\n  position: relative;\n  display: block;\n}\n#edittools .editedSlot .overlay { width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; } \n#edittools .slotClass { cursor: copy; } \n#edittools .panelRemoveButton, #edittools .menuButton { cursor: pointer; }\n#edittools .gear { \n  background-image: url(\'https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Simpleicons_Interface_gear-wheel-in-black.svg/15px-Simpleicons_Interface_gear-wheel-in-black.svg.png\'); \n  height: 15px;;\n  width: 15px; \n  background-repeat: no-repeat;\n  background-size: cover;\n  display: inline-block; } \n#edittools .ui-state-highlight { \n  min-width: 1em; \n  min-height: 1em; \n  display: inline-block;\n} \n#edittools .ui-sortable-helper { min-width: 1em; min-height: 1em; } \n.specialchars-tabs {float: left; background: #E0E0E0; margin-right: 7px; } \n.specialchars-tabs a{ display: block; } \n#edittools { border: solid #aaaaaa 1px; } \n.mw-editTools a{ cursor: pointer; } \n.overflowHidden { overflow: hidden; } \n.specialchars-tabs .asnav-selectedtab{ background: #F0F0F0; } \n#edittools .highlighted { opacity: 0.5; }\n#edittools [data-id]:hover { border-color: red; }\n#edittools .notFoundWarning { padding: 4px; }\n#edittools .newPanelButton { padding: 4px; border-bottom: solid #aaaaaa 1px; }\n#edittools .removeIcon { \n  background-image: url(\'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/Ambox_delete_soft.svg/15px-Ambox_delete_soft.svg.png?uselang=uk\');\n  display: inline-block;\n  width: 15px;\n  height: 15px;\n  margin: 0px 0px 2px 4px;\n  vertical-align: middle;\n}\n#edittools .panelNameLabel { margin-right: 5px; }\n#edittools .panelRemoveButton { margin-left: 20px; }\n#edittools .etPanel > .slots { \n  padding: 6px 1px;\n  border: 1px black dashed; \n  overflow: auto;\n  max-height: 240px;\n}\n.rastEditWindow .bottomButtons { margin-top: 10px; }\n\n#edittools .aboutLink { float: right; }\n}',
    appendExtraCSS: function() {
      mw.util.addCSS(this.extraCSS);
    },
    parentId: '.mw-editTools',
    id: 'edittools',
    cookieName: 'edittool',
    createEditTools: function() {
      var $tabs, event, self;
      $tabs = $('<div></div>').attr('id', this.id);
      event = rast.ieVersion() < 9 ? 'mousedown' : 'click';
      self = this;
      $tabs.on(event, '.asnav-content .etPanel .slots [data-id]', function($e) {
        var id, slot;
        if (editTools.mode === 'edit') {
          id = parseInt($(this).closest('.editedSlot').attr('data-id'));
          slot = editTools.temporarySubsets.slotById(id);
          return editTools.editWindow(slot);
        }
      });
      return $tabs;
    },
    editWindow: function(slot) {
      var editor;
      editor = new slot.constructor.editorClass({
        slot: slot,
        slotsManager: this
      });
      return editor.startEditing();
    },
    onDeleteSlot: function(slotId) {
      var id;
      id = parseInt(slotId);
      this.temporarySubsets.deleteSlot(id);
      return this.refresh();
    },
    edit: function() {
      this.mode = 'edit';
      $('#' + this.id).find('.notFoundWarning').remove();
      return this.refresh();
    },
    view: function() {
      this.mode = 'view';
      return this.refresh();
    },
    reset: function() {
      this.onloadFuncs = [];
      return this.subsets.reset();
    },
    readFromSpecialSyntaxObject: function(obj) {
      if (!obj) {
        return false;
      }
      this.reset();
      this.subsets.readEncodedSubsets(obj);
      this.subsetsUpdated();
      this.refresh();
      return true;
    },
    subsetsUpdated: function() {
      return this.temporarySubsets = rast.clone(this.subsets, false);
    },
    undoChanges: function() {
      return this.temporarySubsets = rast.clone(this.subsets);
    },
    refresh: function() {
      var $tabs, etActiveTab;
      if (!this.created) {
        return;
      }
      $tabs = $('#' + this.id);
      etActiveTab = $tabs.find('.existingTabs .asnav-selectedtab').attr('data-contentid') || mw.cookie.get(editTools.cookieName + 'Selected') || 'etTabContent0';
      this.drawer.$container = $tabs;
      this.drawer.mode = this.mode;
      this.drawer.subsets = this.temporarySubsets;
      this.drawer.message = this.message;
      this.message = null;
      this.drawer.activeTab = etActiveTab;
      this.drawer.draw();
      setTimeout((function(_this) {
        return function() {
          return _this.fireOnloadFuncs();
        };
      })(this), 0);
      return $tabs.on('asNav:select', function(ev, selectedId) {
        return mw.cookie.set(editTools.cookieName + 'Selected', selectedId);
      });
    },
    save: function() {
      this.subsets = this.temporarySubsets;
      this.subsetsUpdated();
      return this.view();
    },
    restoreDefaults: function() {
      return this.readFromSubpage('User:AS/defaults.js');
    },
    init: function() {
      var $placeholder, $tabs, etActiveTab;
      this.subsets = new rast.SubsetsManager;
      this.temporarySubsets = new rast.SubsetsManager;
      $tabs = $('#' + this.id);
      etActiveTab = $tabs.find('.existingTabs .asnav-selectedtab').attr('data-contentid') || mw.cookie.get(editTools.cookieName + 'Selected') || 'etTabContent0';
      this.onSaveClick = (function(_this) {
        return function() {
          return _this.save();
        };
      })(this);
      this.onCancelClick = (function(_this) {
        return function() {
          _this.undoChanges();
          return _this.view();
        };
      })(this);
      this.onResetClick = (function(_this) {
        return function() {
          return _this.restoreDefaults();
        };
      })(this);
      this.onEditClick = (function(_this) {
        return function() {
          return _this.edit();
        };
      })(this);
      this.onTabNameChanged = (function(_this) {
        return function(event) {
          event.data.subsetWrapper.caption = $(event.target).val();
          return _this.refresh();
        };
      })(this);
      this.onAddSubsetClick = (function(_this) {
        return function() {
          var subset;
          subset = _this.temporarySubsets.addSubset('Нова панель', _this.temporarySubsets.subsets.length);
          _this.refresh();
          $tabs = $('#' + _this.id);
          return $tabs.asnavSelect('etTabContent' + subset.id);
        };
      })(this);
      this.onRemoveSubsetClick = (function(_this) {
        return function(subsetWrapper) {
          _this.temporarySubsets.deleteSubset(subsetWrapper);
          return _this.refresh();
        };
      })(this);
      this.onSlotAdded = (function(_this) {
        return function() {
          return _this.refresh();
        };
      })(this);
      this.onPersistClick = (function(_this) {
        return function() {
          _this.save();
          $tabs = $('#' + _this.id);
          $tabs.throbber(true, 'prepend');
          return _this.saveToSubpage();
        };
      })(this);
      this.onSlotRemoved = (function(_this) {
        return function() {
          return _this.refresh();
        };
      })(this);
      this.drawer = new rast.Drawer();
      $.extend(this.drawer, {
        docLink: this.docLink,
        onTabClick: null,
        eventsHandler: this,
        slotClasses: [rast.PlainTextSlot, rast.InsertionSlot, rast.MultipleInsertionsSlot, rast.HtmlSlot]
      });
      $placeholder = $(this.parentId);
      if (!$placeholder.length) {
        return;
      }
      this.appendExtraCSS();
      $placeholder.empty().append(this.createEditTools());
      $('input#wpSummary').attr('style', 'margin-bottom:3px;');
      this.created = true;
      return this.reload();
    },
    reload: function() {
      var $tabs;
      $tabs = $('#' + this.id);
      $tabs.throbber(true, 'prepend');
      return this.readFromSubpage();
    },
    docLink: 'https://uk.wikipedia.org/wiki/%D0%9A%D0%BE%D1%80%D0%B8%D1%81%D1%82%D1%83%D0%B2%D0%B0%D1%87:AS/%D0%9F%D0%9F%D0%A1-2',
    editButtonHtml: function() {
      return this.drawer.$editButtonIcon().prop('outerHTML');
    },
    showMessage: function(html) {
      this.message = html;
      return this.refresh();
    },
    onSubpageNotFound: function() {
      if (!this.readFromSpecialSyntaxObject(window.etSubsets)) {
        return this.showMessage("<div class=\"notFoundWarning\">Це повідомлення від додатка <a href=\"" + this.docLink + "\">Покращеної панелі спецсимволів</a> (Налаштування -> Додатки -> Редагування). Підсторінку із символами не знайдено або не вдалося завантажити. Це нормально, якщо ви ще не зберегли жодну версію. Натисніть зліва від панелі на " + (this.editButtonHtml()) + ", щоб редагувати символи.</div>");
      }
    },
    serialize: function() {
      return JSON.stringify(this.subsets, null, 2);
    },
    subpageStorageName: 'AStools.js',
    saveToSubpage: function() {
      return this.serializeToPage('User:' + mw.config.get('wgUserName') + '/' + this.subpageStorageName);
    },
    trackingPage: 'User:AS/track',
    serializeToPage: function(pagename) {
      var serializedTools;
      serializedTools = "[[" + this.trackingPage + "]]<nowiki>" + (this.serialize()) + "</nowiki>";
      return rast.PageStorage.save(pagename, serializedTools, this);
    },
    subpageName: function() {
      return 'User:' + mw.config.get('wgUserName') + '/' + this.subpageStorageName;
    },
    readFromSubpage: function(pagename) {
      var json;
      this.reset();
      return json = rast.PageStorage.load(pagename || this.subpageName(), (function(_this) {
        return function(pagetext) {
          var pagetextWithoutNowiki, serializedTools;
          pagetextWithoutNowiki = pagetext.replace(/^(\[\[[^\]]+\]\])?<nowiki>/, '').replace(/<\/nowiki>$/, '');
          serializedTools = JSON.parse(pagetextWithoutNowiki);
          _this.subsets.deserialize(serializedTools);
          _this.subsetsUpdated();
          return _this.refresh();
        };
      })(this), this);
    },
    onReadFromSubpageError: function() {
      return this.showMessage("<div class=\"readingSubpageError\">Не вдалося завантажити підсторінку з символами.</div>");
    },
    onEndReadingSubpage: function() {
      var $tabs;
      $tabs = $('#' + this.id);
      return $tabs.throbber(false);
    },
    onSavedToSubpage: function() {
      var pagename;
      pagename = this.subpageName();
      return mw.notify($("<span>Збережено на <a href='" + (mw.util.getUrl(pagename)) + "'>" + pagename + "</a></span>"));
    },
    onSaveToSubpageError: function() {
      var pagename;
      pagename = this.subpageName();
      return mw.notify($("<span>Не вдалося зберегти на <a href='" + (mw.util.getUrl(pagename)) + "'>" + pagename + "</a></span>"));
    },
    onEndSavingToSubpage: function() {
      var $tabs;
      $tabs = $('#' + this.id);
      return $tabs.throbber(false);
    },
    setupOnEditPage: function() {
      if (mw.config.get('wgAction') === 'edit' || mw.config.get('wgAction') === 'submit') {
        return mw.loader.using(['mediawiki.cookie', 'oojs-ui'], function() {
          rast.installJQueryPlugins();
          return editTools.init();
        });
      }
    },
    onSlotSaved: function() {
      return this.refresh();
    }
  };
  rast.PlainObjectParser.processShortcut = editTools.processShortcut;
  rast.PlainObjectParser.addOnloadFunc = editTools.addOnloadFunc;
  return editTools.setupOnEditPage();
});