Директива Google pagedown AngularJS

См. нижнюю часть вопроса для улучшенного решения этой проблемы.

Я уже некоторое время пытаюсь получить директиву для работы с pagedown. Это точно такой же редактор, который используется в stackoverflow. Stackoverflow делает этот код доступным здесь:

https://code.google.com/p/pagedown/

В Интернете есть несколько версий, но ни одна из них не работает должным образом. Мне нужен тот, который будет отображаться со всеми кнопками редактора, как stackoverflow, как при встроенном коде, так и при встроенном как часть ngRepeat.

Я хотел бы, чтобы эта директива работала, когда она встроена в код, а также внутри ng-repeat с использованием Angular версии 1.2.7. Что необходимо, так это то, что при изменении данных модели директиве необходимо обновить представления страницы вниз, чтобы показать новый вопрос и ответы. Когда пользователь изменяет область редактирования страницы, директива должна иметь возможность обновлять модель. Когда пользователь нажимает [сохранить], данные модели должны быть сохранены в базе данных (или, по крайней мере, в другом объекте, чтобы подтвердить, что это сработало).

Директива должна иметь возможность реагировать на изменения в модели, а также сохранять необработанные данные в модели при нажатии клавиши или при нажатии кнопки «Изменить» на панели редактирования. Вот что у меня есть до сих пор. Обратите внимание, что в этой версии нет $wmdInput.on('change', но это начало того, что необходимо.

Самое главное, я хотел бы, чтобы это работало с версией 1.2.7 Angular и jQuery 2.0.3. Обратите внимание, что я обнаружил различия с моим неработающим кодом между версиями 1.2. .2 и 1.2.7. Я думаю, что лучше всего, если какое-либо решение будет работать для последней версии (1.2.7).

Обновить

Теперь я использую эту директиву, которая проще и решает некоторые недавние проблемы, с которыми я столкнулся, когда содержимое не отображалось. Я настоятельно рекомендую использовать эту директиву, которая основана на принятом ответе, а также на нескольких улучшениях: https://github.com/kennyki/angular-pagedown


person Community    schedule 03.01.2014    source источник
comment
Наконец-то я смог воспроизвести ваш баг...   -  person Ilan Frumer    schedule 06.01.2014
comment
@llan - рад это слышать, но не уверен в решении. Я упаковал свой вопрос здесь с максимально возможной наградой. Надеясь найти кого-то, кто любит вызов. Должно быть много людей, которые хотели бы использовать pagedown с AngularJS, поэтому я надеюсь, что кто-то сможет найти решение.   -  person Samantha J T Star    schedule 06.01.2014
comment
Вы должны использовать задержку 300 мс?   -  person Ilan Frumer    schedule 06.01.2014
comment
Нет, я просто добавил, что, как я думал, может быть удар по производительности, который замедлит работу, когда пользователь быстро нажимает клавиши. Кстати, если вы придумываете решение, было бы здорово, если бы вы могли поместить его в плункер, скрипач или что-то еще, чтобы я мог его протестировать, и у нас могло быть что-то общее для работы. Мои навыки плунжера почти равны нулю, поэтому я не уверен, как сделать так, чтобы он показывал боковую панель с несколькими кнопками и областью экрана с окнами редактирования.   -  person Samantha J T Star    schedule 06.01.2014
comment
Было бы разумнее поместить описание, которое вы написали в описании награды, в самом тексте вопроса!   -  person Shahbaz    schedule 07.01.2014


Ответы (4)


Вот рабочая ссылка:

http://cssdeck.com/labs/qebukp9k

ОБНОВИТЬ

  • Я сделал некоторые оптимизации.
  • Я использую форматтеры ngModel.$! нет необходимости в еще $ часы.
  • Я использую $timeout, а затем scope.$apply, чтобы избежать ошибок $digest in progress.

Angular.js и производительность

  • Если вы столкнулись с производительностью, возможно, ваше приложение использует слишком много $watch / $on.
  • По моему опыту, использование сторонних библиотек может привести к неэффективному поведению / утечке памяти, в основном потому, что оно не было реализовано с учетом angular / SPA.
  • Я смог сделать умную интеграцию для некоторых библиотек, но некоторые просто не подходят для мира angular.
  • Если ваше приложение должно отображать более 1000 вопросов, вам, вероятно, следует начать с написания собственного повторителя и предпочесть динамические вставки DOM.
  • Angular.js не будет хорошо работать с тоннами привязок данных, если вы не захотите написать какой-нибудь умный материал более низкого уровня (на самом деле это весело, когда вы знаете, как!).
  • Опять же, предпочитайте пагинацию! Как говорит Миско Хевери: «Вы не можете показать человеку более 2000 фрагментов информации на одной странице. Все, что больше, — это действительно плохой пользовательский интерфейс, и люди все равно не могут это обработать».< /эм>
  • Прочитайте это: Как работает привязка данных в AngularJS?
  • Я более чем счастлив помочь вам, но сначала позвольте мне показать код (свяжитесь со мной)..

Решение:

var app = angular.module('App', []);

app.directive('pagedownAdmin', function ($compile, $timeout) {
    var nextId = 0;
    var converter = Markdown.getSanitizingConverter();
    converter.hooks.chain("preBlockGamut", function (text, rbg) {
        return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) {
            return "<blockquote>" + rbg(inner) + "</blockquote>\n";
        });
    });

    return {
        require: 'ngModel',
        replace: true,
        template: '<div class="pagedown-bootstrap-editor"></div>',
        link: function (scope, iElement, attrs, ngModel) {

            var editorUniqueId;

            if (attrs.id == null) {
                editorUniqueId = nextId++;
            } else {
                editorUniqueId = attrs.id;
            }

            var newElement = $compile(
                '<div>' +
                   '<div class="wmd-panel">' +
                      '<div id="wmd-button-bar-' + editorUniqueId + '"></div>' +
                      '<textarea class="wmd-input" id="wmd-input-' + editorUniqueId + '">' +
                      '</textarea>' +
                   '</div>' +
                   '<div id="wmd-preview-' + editorUniqueId + '" class="pagedown-preview wmd-panel wmd-preview"></div>' +
                '</div>')(scope);

            iElement.html(newElement);

            var help = function () {
                alert("There is no help");
            }

            var editor = new Markdown.Editor(converter, "-" + editorUniqueId, {
                handler: help
            });

            var $wmdInput = iElement.find('#wmd-input-' + editorUniqueId);

            var init = false;

            editor.hooks.chain("onPreviewRefresh", function () {
              var val = $wmdInput.val();
              if (init && val !== ngModel.$modelValue ) {
                $timeout(function(){
                  scope.$apply(function(){
                    ngModel.$setViewValue(val);
                    ngModel.$render();
                  });
                });
              }              
            });

            ngModel.$formatters.push(function(value){
              init = true;
              $wmdInput.val(value);
              editor.refreshPreview();
              return value;
            });

            editor.run();
        }
    }
});
person Ilan Frumer    schedule 06.01.2014
comment
@llan - Ваш код работает для меня, но в моем приложении он очень медленный по сравнению с моим исходным кодом, использующим 1.2.2. Когда я быстро ввожу 5-6 слов, может пройти 3-4 секунды, прежде чем они появятся на экране. Я пытаюсь понять, почему ваш пример работает хорошо, а мой очень медленно. Кстати, вы сказали, что смогли воспроизвести мою проблему. Означает ли это, что вы можете заставить его работать в 1.2.2. а не в 1.2.3? Я заметил, что изменения между этими версиями включают изменение для исправления (ввод): автоматическое заполнение формы поддержки в современном браузере. Мне интересно, связана ли причина, по которой мой код не работает, с этим - person Samantha J T Star; 07.01.2014
comment
@llan - Спасибо за помощь! 2-я версия работает хорошо. Теперь я заметил, что вы только что внесли больше улучшений, поэтому я скоро проверю. Могу я попросить вас добавить однословный комментарий вверху файла, например // 3.1, чтобы я знал, когда вы вносите изменения. Я добавил к вопросу одну небольшую вещь и упомянул, что мои директивы находятся внутри формы. На кнопке отправки формы я проверяю, были ли внесены изменения. Кажется, ваш код не меняет значение $ pristine. Есть ли изменение, которое вы могли бы внести, чтобы оно загрязнило форму, если бы были внесены изменения? - person Samantha J T Star; 07.01.2014
comment
Похоже, моя последняя версия уже устанавливает для $pristine значение false. Я использую ngModel.$render(), поэтому он, вероятно, автоматически устанавливает $pristine. Вы можете проверить это: cssdeck.com/labs/qebukp9k - person Ilan Frumer; 07.01.2014
comment
@llan - Да, настройка original теперь работает хорошо. Я обновил вопрос с сообщением об ошибке, которое я получаю при первой загрузке страницы (когда еще нет данных для директив и когда я предполагаю, что входные данные не определены). У вас есть идеи, что может исправить это. Можете ли вы смоделировать это в своем тесте и получить то же самое? - person Samantha J T Star; 07.01.2014
comment
Фиксированный! Я добавил кнопки заполнения/отмены заполнения, чтобы вы могли видеть, как это работает. - person Ilan Frumer; 07.01.2014
comment
Протестировал это, и пока все выглядит хорошо. Я дам вам знать завтра, если я найду что-нибудь еще. Если нет, то я приму вопрос. Если вы думаете о каких-либо улучшениях производительности, пожалуйста, дайте мне знать. Пользователь, который вводил данные, комментировал, что теперь они все еще намного медленнее, даже если загружено не так много вопросов. Она показывала мне при добавлении слов перед списком, на который курсор иногда перескакивал после списка в окне редактора. Я не думаю, что это ваш код. Возможно, что-то добавлено в более поздних выпусках Angular и некоторые проблемы с синхронизацией страниц. - person Samantha J T Star; 07.01.2014
comment
Я сделал еще одно изменение (один и тот же Markdown.Converter используется во всех директивах). Что касается его медленности, я удалил все привязки $watchers и изолированные области видимости, ваши редакторы должны быть быстрыми, если задействован мой код. - person Ilan Frumer; 07.01.2014
comment
Илан – я проверю ваши последние изменения в течение следующих 24 часов и рассчитываю принять вопрос, если все в порядке. Извините за задержку, но у меня не было времени, чтобы проверить ваше последнее изменение сегодня. Спасибо за вашу помощь. - person Samantha J T Star; 08.01.2014
comment
Эй, я столкнулся с некоторыми проблемами, когда пытался реализовать это и динамически добавлять редакторы на страницу. не могли бы вы взглянуть? stackoverflow.com/questions/21387797/ - person GSto; 27.01.2014

Вы можете изменить это:

scope.$watch(attrs.ngModel, function () {
var val = scope.$eval(attrs.ngModel);

За это:

scope.$watch(attrs.ngModel, function(newValue, oldValue) {
  var val = newValue;
});

Дополнительно можно попробовать прокомментировать этот код:

if (val !== undefined) {
    $wmdInput.val(val);
    ...    

}

Я думаю, что это может быть связано со странным поведением.

person Dalorzo    schedule 03.01.2014
comment
Я полагаю, что это было в одной из других директив pagedown. Сейчас попробую и отчитаюсь. Спасибо - person Samantha J T Star; 03.01.2014
comment
Я сделал изменение, как вы предложили, но оно все еще не работает. Результат такой же. - person Samantha J T Star; 03.01.2014
comment
Я поместил это в заявку как еще один постер, прежде чем сказал, что это должно быть внутри $apply. Может быть, кто-то еще может прокомментировать это. Спасибо - person Samantha J T Star; 03.01.2014
comment
Не могли бы вы прокомментировать этот код и повторить попытку if (val !== undefined) { $wmdInput.val(val); редактор.refreshPreview(); } - person Dalorzo; 03.01.2014
comment
С этим закомментированным все работает. НО он не заполняется начальным значением modal.data.text. Когда окно открывается, оно пустое. Когда я добавляю буквы, они добавляются правильно. Обратите внимание, что порядок заключается в том, что директива изменяет DOM, а затем мой HTTP-вызов возвращается для заполнения данных. Я попытался установить дату с помощью этого в моем контроллере: $(#wmd-input-question-text).val ($scope.modal.data.text); Однако я думаю, что это не рекомендуемый способ сделать это, и это очень грязно, так как мне придется делать это изменение везде. - person Samantha J T Star; 03.01.2014
comment
Я бы порекомендовал вам подумать о проверке модели, а не о ее контроле. Если это поможет, для инициализации значений также можно использовать директиву ngInit. - person Dalorzo; 03.01.2014
comment
Я не могу использовать ngInit, поскольку значение, которое входит в директиву, появляется после запуска директивы. Затем, может быть, через час я выберу другой вопрос, и modal.data.text снова изменится. Вот почему я должен использовать $watch. Как вы упомянули, да, я, конечно, хочу сделать это через модель. Я не хочу устанавливать значение с помощью jQuery. Я просто упомянул об этом как о плохом исправлении, которое я не хочу использовать. Спасибо - person Samantha J T Star; 03.01.2014

Это может быть не ответ, но вся проблема возникает, когда вы начинаете использовать Markdown.Editor, который не дает вам много преимуществ.

Конечно, вам нужно использовать его для начинающих редакторов уценки, но при использовании уценки они все равно уже не новички (могу ошибаться).

Что я подошел к этой проблеме, так это сделать полностью рабочую версию без использования редактора.

Он также имеет предварительный просмотр.

Это также очень просто.

https://github.com/allenhwkim/wiki

---- изменить ----
удалить
---- изменить ----
удалить
---- изменить ----

Чтобы предоставить полностью работающий редактор, после нескольких часов проб и вопросов, следующее самое простое, что я могу получить. Это требует ни $watch, ни $formatters. Он просто оборачивает данный элемент со всеми атрибутами, заданными текстовой областью.

http://plnkr.co/edit/jeZ5EdLwOfwo6HzcTAOR?p=preview

app.directive('pagedownEditor', function($compile, $timeout) {
  var num=0;
  return {
    priority: 1001, //higher than ng-repeat, 1000
    link: function(scope, el, attrs) {
      var uniqNum = scope.$index || num++;
      var wmdPanel = document.createElement('div');
      wmdPanel.className = "wmd-panel";
      var wmdButtonBar = document.createElement('div');
      wmdButtonBar.id = 'wmd-button-bar-'+uniqNum;
      wmdPanel.appendChild(wmdButtonBar);
      el.wrap(wmdPanel); // el is ng-repeat comment, it takes tim

      var converter = Markdown.getSanitizingConverter();
      var editor = new Markdown.Editor(converter, "-"+uniqNum);
      $timeout(function() {
        wmdPanel.querySelector('textarea').id = 'wmd-input-'+uniqNum;
        wmdPanel.querySelector('textarea').className += ' wmd-input';
        wmdPanel.insertAdjacentHTML('afterend', '<div id="wmd-preview-'+uniqNum+'" '
          +'class="pagedown-preview wmd-panel wmd-preview">');
        editor.run()
      }, 50);
    }
  };
person allenhwkim    schedule 06.01.2014
comment
Спасибо за ссылку, но мне действительно нужно решение с редактором, таким как stackoverflow, поскольку пользователям нравится иметь возможность нажимать кнопки для кода и т. д. - person Samantha J T Star; 06.01.2014
comment
Это можно упростить, используя element.wrap(), который удаляет все коды, связанные с ng-моделью. - person allenhwkim; 08.01.2014

Демонстрация: http://plnkr.co/edit/FyywJS?p=preview

Резюме

  1. Я удалил keyup и добавил хук на onPreviewRefresh, чтобы убедиться, что нажатие на панель инструментов правильно обновит ng-model.

  2. Функции на $rootScope продемонстрируют возможность обновления ng-model вне pagedown.

  3. Функция сохранения полностью зависит от вашего выбора, поскольку теперь вы можете получить доступ к ng-model из любого места.

person Daiwei    schedule 06.01.2014
comment
У меня были проблемы с попыткой заставить ваш код работать в моем приложении. Вы использовали templateUrl и, возможно, это часть моей проблемы. Я добавил шаблон на свою страницу, но получаю различные ошибки, а кнопки редактирования не отображаются. Можете ли вы попробовать изменить свой код, чтобы он был максимально похож на мой оригинал, чтобы его было легко подключить и попробовать. Также в вашем примере plnkr вы можете попробовать добавить более одного экземпляра pagedown внутри ng-repeat. Вот так, как у меня. Большое спасибо за вашу помощь. - person Samantha J T Star; 07.01.2014
comment
@SamanthaJ Спасибо за информацию. Я обновил свой код и удалил templateUrl. Также добавлено ng-repeat в демоверсию. Но если вы не видите значки кнопок панели редактора, вам нужно проверить свой css на наличие .wmd-button > span { background-image: url(../../wmd-buttons.png); }, поскольку значки кнопок панели инструментов не контролируются JS. Дайте мне знать о дальнейшем развитии вашей проблемы. - person Daiwei; 07.01.2014
comment
@SamanthaJ Я включил <link rel="stylesheet" href="http://pagedown.googlecode.com/hg/demo/browser/demo.css">, чтобы убедиться, что значки отображаются. - person Daiwei; 07.01.2014