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

  • 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.rast = {
  clone: function(object) {
    return $.extend(true, {}, object);
  },
  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;
  },
  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');
  },
  searchAndReplace: {
    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, #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.util.escapeRegExp(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();
      }
    }
  }
};

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() {
    if (this.mode === 'edit') {
      return this.drawEditMode();
    } else if (this.mode === 'view') {
      return this.generateHtml(this.$panel, this.subsetWrapper.slots, 'generateHtml');
    }
  };

  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 $descLabel, $preview, $previewContent, $slots, layout, nameInput, nameLabel, removeButton;
    nameLabel = new OO.ui.LabelWidget({
      label: 'Назва панелі:'
    });
    nameInput = new OO.ui.TextInputWidget({
      value: this.subsetWrapper.caption
    });
    nameInput.$element.on('keydown', function(event) {
      if (event.keyCode === 13) {
        event.preventDefault();
        return false;
      }
    });
    nameInput.$element.change({
      subsetWrapper: this.subsetWrapper
    }, this.eventsHandler.onTabNameChanged);
    removeButton = new OO.ui.ButtonWidget({
      label: 'Вилучити цю панель',
      flags: 'destructive'
    });
    removeButton.on('click', (function(_this) {
      return function() {
        return _this.eventsHandler.onRemoveSubsetClick(_this.subsetWrapper);
      };
    })(this));
    layout = new OO.ui.HorizontalLayout({
      items: [nameLabel, nameInput, removeButton]
    });
    this.$panel.append(layout.$element);
    $descLabel = $('<span>');
    $descLabel.text('Ви можете перетягувати комірки, щоб змінити їхній порядок. Клацніть по комірці, щоб редагувати її. Щоб додати комірку, перетягніть нижче один з цих видів:');
    this.$panel.append($descLabel);
    this.drawSlotClasses(this.$panel);
    $slots = $('<div class="slots">');
    this.$panel.append($slots);
    this.sortableSlots($slots);
    if (!this.subsetWrapper.slots.length) {
      $slots.append('<span>Щоб додати комірку, сюди перетягніть потрібний вид.</span>');
    } else {
      this.generateHtml($slots, this.subsetWrapper.slots, 'generateEditHtml');
    }
    $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);
  };

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

  PanelDrawer.prototype.slotClasses = function() {
    return [rast.PlainTextSlot, rast.InsertionSlot, rast.MultipleInsertionsSlot, rast.HtmlSlot];
  };

  return PanelDrawer;

})();

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

  Drawer.prototype.$editButton = function() {
    var icon;
    icon = new OO.ui.IconWidget({
      icon: 'settings',
      title: 'Редагувати символи',
      classes: ['gear']
    });
    return icon.$element;
  };

  Drawer.prototype.drawMenu = function() {
    var $aboutLink, $editButton, $menu, cancelButton, 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') {
      persistButton = new OO.ui.ButtonWidget({
        label: ' Зберегти на постійно',
        title: 'Символи буде збережено на підсторінку у Вашому просторі користувача.',
        icon: 'checkAll'
      });
      persistButton.on('click', this.eventsHandler.onPersistClick);
      saveButton = new OO.ui.ButtonWidget({
        label: ' Зберегти тимчасово',
        title: 'Зміни збережуться тільки на час редагування сторінки і втратяться після закриття або перевантаження сторінки.',
        icon: 'check'
      });
      saveButton.on('click', this.eventsHandler.onSaveClick);
      cancelButton = new OO.ui.ButtonWidget({
        label: ' Скасувати',
        title: 'Всі зміни цієї сесії редагування будуть відкинуті.',
        icon: 'cancel'
      });
      cancelButton.on('click', this.eventsHandler.onCancelClick);
      resetButton = new OO.ui.ButtonWidget({
        label: ' Відновити звичаєві',
        title: 'Буде відновлено набір символів за промовчанням.',
        icon: 'reload'
      });
      resetButton.on('click', this.eventsHandler.onResetClick);
      $aboutLink = $("<a class=\"aboutLink\" target=\"_blank\" href=\"" + this.docLink + "\">про додаток</a>");
      $menu.append(persistButton.$element, saveButton.$element, cancelButton.$element, resetButton.$element, $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);
    }
    return this.$container.append($outline);
  };

  Drawer.prototype.draw = function() {
    this.$container.empty();
    this.drawMenu();
    this.drawMessage();
    this.drawNavigation();
    return this.drawPanels();
  };

  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) {
    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);
        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) {
    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;
      }
    }
    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.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 = [];
    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' ? (fieldOptions = {
        value: value
      }, {
        getValue: 'getValue',
        OOobject: new OO.ui.TextInputWidget(fieldOptions)
      }) : type === 'text' ? (fieldOptions = {
        value: value,
        rows: 3,
        autosize: true
      }, {
        getValue: 'getValue',
        OOobject: new OO.ui.MultilineTextInputWidget(fieldOptions)
      }) : type === 'boolean' ? {
        getValue: 'getValue',
        OOobject: new OO.ui.ToggleSwitchWidget({
          value: value
        })
      } : type === 'code' ? (fieldOptions = {
        value: value,
        rows: 3,
        autosize: true,
        classes: ['monospace']
      }, {
        getValue: 'getValue',
        OOobject: new OO.ui.MultilineTextInputWidget(fieldOptions)
      }) : 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.generateCommonHtml();
    $($element).addClass('editedSlot');
    return $element;
  };

  Slot.prototype.generateHtml = function() {
    return this.generateCommonHtml();
  };

  Slot.prototype.toJSON = function() {
    var defaults, res, sanitized;
    defaults = new this.constructor;
    defaults = defaults.sanitizedAttributes();
    sanitized = this.sanitizedAttributes();
    res = {};
    Object.keys(sanitized).forEach((function(_this) {
      return function(key) {
        if ((sanitized[key] !== defaults[key]) && defaults.hasOwnProperty(key)) {
          return res[key] = sanitized[key];
        }
      };
    })(this));
    res['class'] = this['class'];
    delete res['id'];
    return res;
  };

  Slot.prototype.sanitizedAttributes = function() {
    return rast.clone(this);
  };

  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: 'code',
        "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.generateCommonHtml = function(styles) {
    var $elem;
    $elem = $('<span>');
    $elem.text(this.text);
    $elem.attr('data-id', this.id);
    $elem.attr('style', styles || this.css);
    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: 'code',
        "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: 'code',
        "default": '',
        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.sanitizedAttributes = function() {
    var copy;
    copy = rast.clone(this);
    copy.clickFunc = $.trim(this.clickFunc);
    return copy;
  };

  InsertionSlot.prototype.generateEditHtml = function() {
    var $elem;
    $elem = InsertionSlot.__super__.generateEditHtml.call(this);
    $elem.attr('title', (this.useClickFunc && this.clickFunc) || 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 && styles.length) {
        $elem.attr('style', styles);
      }
      return $elem;
    } else {
      $a = $('<a>');
      $a.attr('data-id', this.id);
      if (styles && styles.length) {
        $a.attr('style', styles);
      }
      caption = $('<div/>').text(this.caption).html();
      $a.html(caption);
      return $a;
    }
  };

  InsertionSlot.prototype.generateHtml = function(styles) {
    var $elem;
    $elem = this.generateCommonHtml(styles || this.css);
    return $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 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: 'code',
        "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 && styles.length) {
      $elem.attr('style', styles);
    }
    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) {
    return this.generateCommonHtml(styles || this.css);
  };

  return MultipleInsertionsSlot;

})(rast.Slot);

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

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

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

  HtmlSlot.prototype.sanitizedAttributes = function() {
    var copy;
    copy = rast.clone(this);
    copy.onload = $.trim(this.onload);
    return copy;
  };

  function HtmlSlot(options) {
    HtmlSlot.__super__.constructor.call(this, options);
    this.onload = $.trim(this.onload);
    if (this.onload.length) {
      editTools.addOnloadFunc((function(_this) {
        return function() {
          try {
            return eval(_this.onload);
          } catch (_error) {}
        };
      })(this));
    }
  }

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

  HtmlSlot.prototype.generateCommonHtml = 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) {
    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, summary) {
    var api;
    api = new mw.Api;
    return api.postWithEditToken({
      action: 'edit',
      title: pagename,
      summary: summary,
      text: string
    }).done(function() {
      return handler.onSavedToSubpage(pagename);
    }).fail(function() {
      return handler.onSaveToSubpageError(pagename);
    }).always(function() {
      return handler.onEndSavingToSubpage();
    });
  };

  return PageStorage;

})();

$(function() {
  window.editTools = {
    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;
    },
    extraCSS: '#edittools .etPanel { margin-top: 5px; }\n#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 > [data-id], #edittools .etPanel .preview [data-id] { display: inline; padding: 0px 2px; }\n#edittools .etPanel > a[data-id], #edittools .etPanel .preview a[data-id] { cursor: pointer; }\n#edittools { min-height: 20px; }\n#edittools .rastMenu.view { position: absolute; left: -5px; }\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; background-color: white; }\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 .slotClasses { text-align: center; }\n#edittools .slotClass { cursor: copy; padding: 3px 5px; border: 1px solid grey; margin-left: 5px; }\n#edittools .panelRemoveButton, #edittools .menuButton { cursor: pointer; }\n#edittools .gear {\n  min-height: 15px;\n  min-width: 15px;\n  cursor: pointer;\n}\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; padding-left: 3px; }\n#edittools { border: solid #aaaaaa 1px; }\n.mw-editTools a{ cursor: pointer; }\n.overflowHidden { overflow: hidden; }\n.specialchars-tabs .asnav-selectedtab { background: #eaecf0; border-left: 2px solid #aaaaaa; }\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.monospace { font-family: "Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro", monospace; }\n}',
    appendExtraCSS: function() {
      mw.util.addCSS(this.extraCSS);
    },
    parentId: '.mw-editTools',
    id: 'edittools',
    cookieName: 'edittool',
    createEditTools: function() {
      var $tabs, self;
      $tabs = $('<div></div>').attr('id', this.id);
      self = this;
      $tabs.on('click', '.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.resetTemporarySubsets();
      this.refresh();
      return true;
    },
    resetTemporarySubsets: 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.resetTemporarySubsets();
      return this.view();
    },
    restoreDefaults: function() {
      return this.readFromSubpage('User:AS/defaults.js');
    },
    init: function() {
      var $placeholder, $tabs, etActiveTab;
      $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.resetTemporarySubsets();
          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
      });
      $placeholder = $(this.parentId);
      if (!$placeholder.length) {
        return;
      }
      this.appendExtraCSS();
      $placeholder.empty().append(this.createEditTools());
      $('input#wpSummary').attr('style', 'margin-bottom: 3px;');
      this.created = true;
      this.temporarySubsets = new rast.SubsetsManager;
      return this.reload();
    },
    reload: function() {
      var $tabs;
      $tabs = $('#' + this.id);
      $tabs.throbber(true, 'prepend');
      return this.readFromSubpage(this.subpage(), (function(_this) {
        return function() {
          _this.subsets = rast.clone(_this.temporarySubsets);
          return _this.refresh();
        };
      })(this));
    },
    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.$editButton().prop('outerHTML');
    },
    showMessage: function(html) {
      this.message = html;
      return this.refresh();
    },
    onSubpageNotFound: function() {
      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(this.subpage(), '[[Обговорення користувача:AS/rast.js|serialize]]');
    },
    subpage: function() {
      return '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, doneFunc) {
      var json;
      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.temporarySubsets.reset();
          _this.temporarySubsets.deserialize(serializedTools);
          _this.refresh();
          if (doneFunc != null) {
            return doneFunc();
          }
        };
      })(this), this);
    },
    onReadFromSubpageError: function() {
      return this.showMessage("<div class=\"readingSubpageError\">Не вдалося завантажити підсторінку з символами.</div>");
    },
    onEndReadingSubpage: function() {
      var $tabs;
      $tabs = $('#' + this.id);
      return $tabs.throbber(false);
    },
    onSavedToSubpage: function(pagename) {
      return mw.notify($("<span>Збережено на <a href='" + (mw.util.getUrl(pagename)) + "'>" + pagename + "</a></span>"));
    },
    onSaveToSubpageError: function(pagename) {
      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') {
        rast.installJQueryPlugins();
        return editTools.init();
      }
    },
    onSlotSaved: function() {
      return this.refresh();
    }
  };
  rast.PlainObjectParser.addOnloadFunc = editTools.addOnloadFunc;
  return $(function() {
    return mw.loader.using(['mediawiki.cookie', 'oojs-ui', 'mediawiki.api'], function() {
      return editTools.setupOnEditPage();
    });
  });
});