### # Copyright (C) 2014-2016 Andrey Antukh # Copyright (C) 2014-2016 Jesús Espino Garcia # Copyright (C) 2014-2016 David Barragán Merino # Copyright (C) 2014-2016 Alejandro Alonso # Copyright (C) 2014-2016 Juan Francisco Alcántara # Copyright (C) 2014-2016 Xavi Julian # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # File: modules/components/wysiwyg/wysiwyg.directive.coffee ### taiga = @.taiga bindOnce = @.taiga.bindOnce Medium = ($translate, $confirm, $storage, wysiwygService, animationFrame, tgLoader, wysiwygCodeHightlighterService, wysiwygMentionService, analytics) -> isCodeBlockSelected = (range, elm) -> return !!$(range.endContainer).parentsUntil('.editor', 'code').length removeCodeBlockAndHightlight = (range, elm) -> code = $(range.endContainer).closest('code')[0] pre = code.parentNode p = document.createElement('p') p.innerText = code.innerText pre.parentNode.replaceChild(p, pre) wysiwygCodeHightlighterService.removeCodeLanguageSelectors(elm) addCodeBlockAndHightlight = (range, elm) -> pre = document.createElement('pre') code = document.createElement('code') pre.appendChild(code) code.appendChild(range.extractContents()) range.insertNode(pre) elm.checkContentChanged() wysiwygCodeHightlighterService.addCodeLanguageSelectors(elm) # MediumEditor extension to add CodeButton = MediumEditor.extensions.button.extend({ name: 'code', init: () -> this.button = this.document.createElement('button') this.button.classList.add('medium-editor-action') this.button.innerHTML = 'Code' this.button.title = 'Code' this.on(this.button, 'click', this.handleClick.bind(this)) getButton: () -> return this.button tagNames: ['code'] handleClick: (event) -> range = MediumEditor.selection.getSelectionRange(self.document) if isCodeBlockSelected(range, this.base) removeCodeBlockAndHightlight(range, this.base) else addCodeBlockAndHightlight(range, this.base) }) # bug #
the enter key press doesn't work oldIsBlockContainer = MediumEditor.util.isBlockContainer MediumEditor.util.isBlockContainer = (element) -> if !element return oldIsBlockContainer(element) if element.tagName tagName = element.tagName else tagName = element.parentNode.tagName if tagName.toLowerCase() == 'code' return true return oldIsBlockContainer(element) link = ($scope, $el, $attrs) -> mediumInstance = null editorMedium = $el.find('.medium') editorMarkdown = $el.find('.markdown') isEditOnly = !!$attrs.$attr.editonly notPersist = !!$attrs.$attr.notPersist $scope.required = !!$attrs.$attr.required $scope.editMode = isEditOnly || false $scope.mode = $storage.get('editor-mode', 'html') wysiwygService.loadEmojis() setHtmlMedium = (markdown) -> html = wysiwygService.getHTML(markdown) editorMedium.html(html) $scope.setMode = (mode) -> $storage.set('editor-mode', mode) if mode == 'markdown' updateMarkdownWithCurrentHtml() else setHtmlMedium($scope.markdown) $scope.mode = mode mediumInstance.trigger('editableBlur', {}, editorMedium[0]) $scope.save = () -> if $scope.mode == 'html' updateMarkdownWithCurrentHtml() return if $scope.required && !$scope.markdown.length $scope.saving = true $scope.outdated = false $scope.onSave({text: $scope.markdown, cb: saveEnd}) return $scope.cancel = () -> if !isEditOnly $scope.editMode = false if notPersist clean() else if $scope.mode == 'html' setHtmlMedium($scope.content) $scope.markdown = $scope.content discardLocalStorage() mediumInstance.trigger('blur', {}, editorMedium[0]) $scope.outdated = false $scope.onCancel() return clean = () -> $scope.markdown = '' editorMedium.html('') refreshExtras = () -> animationFrame.add () -> if $scope.mode == 'html' if $scope.editMode wysiwygCodeHightlighterService.addCodeLanguageSelectors(mediumInstance) wysiwygCodeHightlighterService.removeHightlighter(mediumInstance.elements[0]) else wysiwygCodeHightlighterService.addHightlighter(mediumInstance.elements[0]) wysiwygCodeHightlighterService.removeCodeLanguageSelectors(mediumInstance) else wysiwygCodeHightlighterService.removeHightlighter(mediumInstance.elements[0]) wysiwygCodeHightlighterService.removeCodeLanguageSelectors(mediumInstance) saveEnd = () -> $scope.saving = false if !isEditOnly $scope.editMode = false if notPersist clean() discardLocalStorage() mediumInstance.trigger('blur', {}, editorMedium[0]) analytics.trackEvent('develop', 'save wysiwyg', $scope.mode, 1) uploadEnd = (name, url) -> if taiga.isImage(name) mediumInstance.pasteHTML("
") else name = $('
').text(name).html() mediumInstance.pasteHTML("" + name + "
") isOutdated = () -> store = $storage.get($scope.storageKey) if store && store.version && store.version != $scope.version return true return false isDraft = () -> store = $storage.get($scope.storageKey) if store return true return false getCurrentContent = () -> store = $storage.get($scope.storageKey) if store return store.text return $scope.content discardLocalStorage = () -> $storage.remove($scope.storageKey) cancelWithConfirmation = () -> if $scope.content == $scope.markdown $scope.cancel() document.activeElement.blur() document.body.click() return null title = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_TITLE") message = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_MESSAGE") $confirm.ask(title, null, message).then (askResponse) -> $scope.cancel() askResponse.finish() updateMarkdownWithCurrentHtml = () -> $scope.markdown = wysiwygService.getMarkdown(editorMedium.html()) localSave = (markdown) -> if $scope.storageKey store = {} store.version = $scope.version || 0 store.text = markdown $storage.set($scope.storageKey, store) change = () -> if $scope.mode == 'html' updateMarkdownWithCurrentHtml() wysiwygCodeHightlighterService.updateCodeLanguageSelector(mediumInstance) localSave($scope.markdown) $scope.onChange({markdown: $scope.markdown}) throttleChange = _.throttle(change, 200) create = (text, editMode=false) -> if text.length html = wysiwygService.getHTML(text) editorMedium.html(html) mediumInstance = new MediumEditor(editorMedium[0], { targetBlank: true, imageDragging: false, placeholder: { text: $scope.placeholder }, toolbar: { buttons: [ 'bold', 'italic', 'strikethrough', 'anchor', 'image', 'orderedlist', 'unorderedlist', 'h1', 'h2', 'h3', 'quote', 'removeFormat', 'code' ] }, extensions: { code: new CodeButton(), autolist: new AutoList(), mediumMention: new MentionExtension({ getItems: (mention, mentionCb) -> wysiwygMentionService.search(mention).then(mentionCb) }) } }) $scope.changeMarkdown = throttleChange mediumInstance.subscribe 'editableInput', (e) -> $scope.$applyAsync(throttleChange) mediumInstance.subscribe "editableClick", (e) -> e.stopPropagation() if e.target.href window.open(e.target.href) mediumInstance.subscribe 'focus', (event) -> $scope.$applyAsync () -> if !$scope.editMode $scope.editMode = true mediumInstance.subscribe 'editableDrop', (event) -> $scope.onUploadFile({files: event.dataTransfer.files, cb: uploadEnd}) mediumInstance.subscribe 'editableKeydown', (e) -> code = if e.keyCode then e.keyCode else e.which mention = $('.medium-mention') if (code == 40 || code == 38) && mention.length e.stopPropagation() e.preventDefault() return if $scope.editMode && code == 27 e.stopPropagation() $scope.$applyAsync(cancelWithConfirmation) else if code == 27 editorMedium.blur() $scope.editMode = editMode $scope.$applyAsync(refreshExtras) $scope.$watch () -> return $scope.mode + ":" + $scope.editMode , () -> $scope.$applyAsync(refreshExtras) unwatch = $scope.$watch 'content', (content) -> if !_.isUndefined(content) $scope.outdated = isOutdated() if !mediumInstance && isDraft() $scope.editMode = true if $scope.markdown == content return content = getCurrentContent() $scope.markdown = content if mediumInstance mediumInstance.destroy() if tgLoader.open() unwatchLoader = tgLoader.onEnd () -> create(content, $scope.editMode) unwatchLoader() else create(content, $scope.editMode) unwatch() $scope.$on "$destroy", () -> if mediumInstance wysiwygCodeHightlighterService.removeCodeLanguageSelectors(mediumInstance) mediumInstance.destroy() return { templateUrl: "common/components/wysiwyg-toolbar.html", scope: { placeholder: '@', version: '<', storageKey: '<', content: '<', onCancel: '&', onSave: '&', onUploadFile: '&', onChange: '&' }, link: link } angular.module("taigaComponents").directive("tgWysiwyg", [ "$translate", "$tgConfirm", "$tgStorage", "tgWysiwygService", "animationFrame", "tgLoader", "tgWysiwygCodeHightlighterService", "tgWysiwygMentionService", "$tgAnalytics", Medium ])