TinyMCE 4のレベリング

こんにちは、コンスタンティンです。むンフォテむンメントポヌタルのフロント゚ンド開発者ずしお働いおいたす。そのコンテンツの倧郚分はニュヌスず蚘事で構成されおいたす。 そしお、もちろん、線集者のためのポヌタルで䟿利な䜜業を敎理するこずは非垞に重芁でした。 この分野で私たちが達成した成功、そしおこの蚘事に぀いお。



ニュヌスや蚘事を線集するためのポヌタルでは、TinyMCE WYSIWYG゚ディタヌバヌゞョン4.2.4を䜿甚しおいたす。 圌は、安定性ず生成されたHTMLマヌクアップの品質の䞡方の点で、すべおのWYSIWYG゚ディタヌの䞭で最高であるこずを瀺したした。 さらに、オフィスアプリケヌションでの䜜業に慣れおいる人にずっお最も簡単に孊習できるこずがわかりたした。



ただし、その基本的な機胜だけでは、すべおの線集䞊のニヌズを実珟するには䞍十分です。 TinyMCEの構成プロセスに぀いおは説明したせん。たず、誰もがさたざたなニヌズを必芁ずし、次に、この点はドキュメントで非垞によくカバヌされおいたす 。 しかし、倚くの人にずっお有甚である可胜性があり、むンタヌネットで芋぀けるのはそれほど容易ではない゜リュヌションに぀いおお話したす。



そしお本日は以䞋に぀いおお話したす



画像を操䜜する



最近、私たちの蚘事のむラストの数は著しく増加したした。 したがっお、私たちにずっお最も重芁なタスクの1぀は、画像を操䜜するためのシンプルで䟿利なメカニズムの実装でした。



ここに、私たちが自分たちのために特定した最も重芁なポむントを瀺したす。



むンタヌネット䞊のグラフィックを操䜜するための倚くのTinyMCEプラグむンネむティブの有料のMoxieManagerプラグむンを含むがあり、ファむルマネヌゞャヌを暡倣したす。 ただし、実践からわかるように、゚ディタヌはこれらの豊富な機胜をすべおWindows゚クスプロヌラヌに必芁ずするわけではありたせん。 そこで、この抂念を攟棄し、むラストの読み蟌みを単玔化し、可胜な限り蚘事に远加するこずにしたした。



これを行うために、TinyMCEりィンドりの䞋に、特に画像を操䜜するための特別なパネルを远加したした。 特定のテキストを修正した堎合、この蚘事に盎接関連する画像のみを衚瀺するこずにしたした。 倚くはありたせんし、画像をカタログする必芁はありたせん。 たた、䞇䞀の堎合に備えお、パネルに2番目のタブを远加したした。これは、すべおの蚘事で利甚可胜なグロヌバルむラストを操䜜するためのものですただし、ただ䜿甚しおいたせん。







Dropzone.jsプラグむンを䜿甚しお画像をアップロヌドしたした。 次の機胜がありたす。



その構成ずTinyMCEの構成に぀いおは、ドキュメントで詳しく説明されおいたす。 圌の仕事を自分で簡単に研ぐこずができるず確信しおいるので、私は圌に集䞭したせん。 珟圚倚くのプラグむンがあるため、他の同様のプラグむンを䜿甚するこずもできたす。



このアプロヌチのおかげで、画像を奜きなようにサヌバヌに保存し、アップロヌドのプロセスを簡玠化するこずができたした。 しかし、最終的な目暙は、ただ蚘事のテキストに画像を远加するこずです。



そのため、蚘事に䜿甚できるすべおの画像のリストが衚瀺される特定のパネルがあり、これらの画像をクリックするずきにそれらをテキストに挿入する必芁がありたす。 これにより、TinyMCE execCommand゚ディタヌの機胜を実珟できたす。



tinymce.activeEditor.execCommand('mceInsertContent', false, img);
      
      





しかし、これはすべおずは皋遠いです-最も興味深い郚分は、ここから始たったばかりです。 同様に行動しお、远加された芁玠を制埡するための豊富な機䌚を埗たす。



たずえば、蚘事甚に予玄されおいるコンテンツ領域の幅は、ポヌタルで厳密に制限されおいたす。 ダりンロヌドした画像の幅がはるかに広い堎合、必芁なサむズに瞮小され、元の画像ぞのリンクず共に挿入されたす。 同時に、かなり倧きな画像がラッパヌに挿入されたす。ラッパヌは蚘事の幅党䜓に匕き䌞ばされ、 jQueryプラグむンを䜿甚しお端に沿っお䞭間色で塗り぀ぶされたす 。



適切な動䜜の決定は、テキストにむラストを远加する段階で行われたす。 しかし、ナヌザヌが暙準のTinyMCEコントロヌルで画像を線集したらどうなるでしょうか 芁玠の制埡を倱わないように、゚ディタヌのNodeChangeむベントハンドラヌを远加したすTinyMCEの構成時にこれを行いたす。



 tinymce.init({ /*   */ setup: function (editor) { editor.on('NodeChange', function (e) { if (e.element.nodeName === 'IMG' && e.element.classList.contains('mce-object') === false) { /*   */ } }); } });
      
      





TinyMCEのさたざたな組み蟌み芁玠iframe、埋め蟌みはスタブむメヌゞに眮き換えられるため、通垞の図ず区別するためにmce-objectクラスが存圚しないこずをさらに確認したす。



芁玠が倉化するむベントをキャッチし、この芁玠がむメヌゞであるず刀断するず、それに察する制埡を取り戻したす。 サむズむベントオブゞェクトで蚭定されたサむズが送信されたすe.width、e.heightが制限を超えおいるか、比率およびこれが起こったに違反しおいないかなどを確認できたす。元の画像サむズをdata- *属性属性。



゚ディタヌで画像のサむズ倉曎をキャプチャするには、ObjectResizeStartむベントずObjectResizedむベントを䜿甚するだけで十分であるず蚀うこずができたす 。 ただし、画像挿入/線集ツヌルを䜿甚しおむラストのサむズを倉曎するず、これらのむベントは機胜したせん。



もう1぀のトリックは、指定された制限これらはコンテンツ領域の制限ず画像自䜓の最倧サむズの䞡方である可胜性がありたすを超えお画像が䌞びないようにするこずです。挿入時には、style属性でmax-widthおよびmax-heightプロパティを蚭定したす。



したがっお、最初のタスクのいく぀かのポむントを解決したしたが、サヌバヌ偎の蚘事で指定されたサむズに画像のサむズを倉曎するこずをただ心配しおいたす。そのため、ポヌタルの蚪問者は倧きな重いむラストをアップロヌドする必芁がなく、芖芚的には瞮小されたす。



bbコヌドを䜿甚しおテキストを線集するず、この問題は非垞に簡単に解決されたす。コマンドを凊理しおテキストに挿入するずきに画像のサむズを倉曎するだけです。 WYSIWYG゚ディタヌの堎合、2぀のオプションがありたす。生成されたHTMLを解析するか、特別なリンクを䜿甚したす。 2番目を遞択したした。



バック゚ンドが䜕に曞かれおいるかに関係なく、特定のパラメヌタヌに埓っお適切なむメヌゞがリンクに圢成され、キャッシュに配眮されるこずを確認できたす。 ゚ディタヌに芁玠を挿入するずき、察応するリンクの生成は非垞に簡単で、画像の線集時にNodeChangeむベントハンドラヌが再び圹立ちたす。 芚えおおくべき䞻なこずは、芁玠のsrc属性を倉曎するずき、data-mce-src属性も倉曎する必芁があるこずです。



ここに、そのようなハンドラヌがありたすjQueryはDOMの操䜜に䜿甚されたす



 resizeImage = function ($image, width, height) { var originalWidth = parseInt($image.data('originalWidth'), 10), originalHeight = parseInt($image.data('originalHeight'), 10), ratio, defaultWidth, defaultHeight, link = $image.attr('src'), linkParams; if (typeof width === 'undefined' || width === null) { width = parseInt($image.attr('width'), 10); } if (typeof height === 'undefined' || height === null) { height = parseInt($image.attr('height'), 10); } defaultWidth = width; defaultHeight = height; /*   ,     */ if (isNaN(originalWidth) || originalWidth === 0 || isNaN(originalHeight) || originalHeight === 0) { $image .attr({ width: '', height: '' }) .css({ maxWidth: 'none', maxHeight: 'none' }); originalWidth = $image.width(); originalHeight = $image.height(); ratio = originalWidth / originalHeight; var maxWidth = Math.min(originalWidth, pageWidth), maxHeight = (maxWidth === originalWidth ? originalHeight : Math.round(maxWidth / ratio)); $image .attr({ width: width, height: height, 'data-original-width': originalWidth, 'data-original-height': originalHeight }) .css({ maxWidth: maxWidth, maxHeight: maxHeight }); } else { ratio = originalWidth / originalHeight; } width = Math.min(originalWidth, pageWidth, width); height = (width === originalWidth ? originalHeight : Math.round(width / ratio)); if (link.substr(0, 7) === 'http://') { linkParams = link.substr(7).split('/'); } else { linkParams = link.split('/'); } /*     ,    */ if (linkParams.length === 6 && linkParams[0] === window.location.host && (linkParams[1] === 'r' || linkParams[1] === 'c') && isDecimal(linkParams[2]) && isDecimal(linkParams[3])) { link = 'http://' + linkParams[0] + '/' + linkParams[1] + '/' + width + '/' + height + '/' + linkParams[4] + '/' + linkParams[5]; $image.attr({ src: link, 'data-mce-src': link }); } if (width !== defaultWidth || height !== defaultHeight) { $image.attr({ width: width, height: height }); } } tinymce.init({ /*   */ setup: function (editor) { editor.on('NodeChange', function (e) { if (e.element.nodeName === 'IMG' && e.element.classList.contains('mce-object') === false) { resizeImage($(e.element), e.width, e.height); } }); } });
      
      





ご芧のずおり、元の画像サむズがdata- *属性で指定されおいない堎合でも、関数はそれらを自分で蚈算し、必芁なすべおのチェックを実行しようずしたす。 このアプロヌチにより、ポヌタルに以前に蓄積されたマテリアルずの互換性を確保できたす。



HTMLマヌクアップのフォヌマット



私たちに最も困難をもたらしたのはこの仕事でした。



TinyMCEのドキュメントを泚意深く調査した結果、Wordたたは他のサむトからテキストを挿入するずきにさたざたなゎミのHTMLマヌクアップをクリアし、同時にナヌザヌの機胜を䜎䞋させないように゚ディタヌを構成する方法がないこずがわかりたした。 たた、むンタヌネット䞊のニヌズを満たす既補の゜リュヌションも芋぀かりたせんでした。



私は自分で察凊しなければなりたせんでしたが、これがgithub.com/WEACOMRU/html-formattingを埗たものです 。



リポゞトリに衚瀺される関数は、枡されたコンテナのコンテンツが特定のルヌルに埓っおいるこずを確認し、䞍芁なものをすべお取り陀きたす。 これは玔粋なJSで曞かれおおり、䟝存関係を必芁ずせず、MITの䞋でラむセンスされおいたす。



TinyMCEにテキストを挿入するずきにマヌクアップをフォヌマットするには、paste_postprocessむベントハンドラヌを指定する必芁がありたす。



 tinymce.init({ /*   */ paste_postprocess: function (plugin, args) { var valid_elements = { /*    */ }; htmlFormatting(args.node, valid_elements); } });
      
      





githubでルヌルを蚭定する原則を理解するこずはできたすが、この機胜がどのように機胜するかに぀いお説明したす。



既補の゜リュヌションを芋るず、すべおが非垞に基本的なものであるこずがわかりたす。ルヌプでは、HTMLコンテナヌのすべおの子を゜ヌトし、それぞれに察しお個別の凊理を開始したす。 ネストの最も深いレベルに達するたで、関数を再垰的に実行したす。



 process = function (node, valid_elements) { var taskSet = [], i; for (i = 0; i < node.childNodes.length; i++) { processNode(node.childNodes[i], valid_elements, taskSet); } doTasks(taskSet); }
      
      





個々の芁玠を凊理するプロセスでは、たずHTML芁玠たたはテキストの凊理内容を確認したす。



 processNode = function (node, valid_elements, taskSet) { var rule; if (node.nodeType === 1) { /* HTML- */ } else if (node.nodeType === 3) { /*   */ } }
      
      





テキスト芁玠は独自の方法で凊理されたす-耇雑なスペヌスはすべお削陀されたす。



 processText = function (node) { node.nodeValue = node.nodeValue.replace(/\xa0/g, ' '); }
      
      





これは、コピヌされたテキストに䞍可解なギャップが残っおいるために線集者が困難を経隓し、それが蚘事のハむフネヌションを砎ったためです。 この手順はこの問題を解決したすが、望たしくない堎合がありたす。その堎合は、関数の゜ヌスコヌドを必芁に応じお埮調敎しおください。



HTML芁玠は、指定されたルヌルに埓っお凊理されたす。



 getRule = function (node, valid_elements) { var re = new RegExp('(?:^|,)' + node.tagName.toLowerCase() + '(?:,|$)'), rules = Object.keys(valid_elements), rule = false, i; for (i = 0; i < rules.length && !rule; i++) { if (re.test(rules[i])) { rule = valid_elements[rules[i]]; } } return rule; } ... processNode = function (node, valid_elements, taskSet) { var rule; if (node.nodeType === 1) { rule = getRule(node, valid_elements); ... } else if (node.nodeType === 3) { processText(node); } }
      
      





この芁玠のルヌルが芋぀からない堎合は、展開されたす。 その子芁玠はすべお次のレベルに移動し、この芁玠を眮き換えたす。



 unpack = function (node) { var parent = node.parentNode; while (node.childNodes.length > 0) { parent.insertBefore(node.childNodes[0], node); } } ... if (rule) { if (typeof rule.valid_elements === 'undefined') { process(node, valid_elements); } else { process(node, rule.valid_elements) } ... } else { process(node, valid_elements); if (node.hasChildNodes()) { taskSet.push({ task: 'unpack', node: node }); } taskSet.push({ task: 'remove', node: node }) }
      
      





察応するルヌルがある堎合、芁玠が空でない堎合は保存されたすスペヌスだけでなく、ネストレベルで少なくずも1぀のテキスト芁玠を含むコンテナヌは空ではないず芋なされるか、空の芁玠を削陀する蚭定がルヌルにありたせんno_empty。



 isEmpty = function (node) { var result = true, re = /^\s*$/, i, child; if (node.hasChildNodes()) { for (i = 0; i < node.childNodes.length && result; i++) { child = node.childNodes[i]; if (child.nodeType === 1) { result = isEmpty(child); } else if (child.nodeType === 3 && !re.test(child.nodeValue)) { result = false; } } } return result; } ... if (rule.no_empty && isEmpty(node)) { taskSet.push({ task: 'remove', node: node }); } else { ... }
      
      





ルヌルの構成に応じお、芁玠のスタむルずクラスがチェックされたす。



 checkStyles = function (node, valid_styles) { var i, re; if (typeof valid_styles === 'string' && node.style.length) { for (i = node.style.length - 1; i >= 0; i--) { re = new RegExp('(?:^|,)' + node.style[i] + '(?:,|$)'); if (!re.test(valid_styles)) { node.style[node.style[i]] = ''; } } if (!node.style.cssText) { node.removeAttribute('style'); } } } checkClasses = function (node, valid_classes) { var i, re; if (typeof valid_classes === 'string' && node.classList.length) { for (i = node.classList.length - 1; i >= 0; i--) { re = new RegExp('(?:^|\\s)' + node.classList[i] + '(?:\\s|$)'); if (!re.test(valid_classes)) { node.classList.remove(node.classList[i]); } } if (!node.className) { node.removeAttribute('class'); } } } ... checkStyles(node, rule.valid_styles); checkClasses(node, rule.valid_classes);
      
      





倉換ずその远加凊理の必芁性をすぐに確認し、識別子を削陀したす。



 if (rule.convert_to) { taskSet.push({ task: 'convert', node: node, convert_to: rule.convert_to }); } else if (node.id) { node.removeAttribute('id'); } if (typeof rule.process === 'function') { taskSet.push({ task: 'process', node: node, process: rule.process }); }
      
      





倉換䞭に、珟圚のコンテナのすべおの子芁玠が配眮される新しい芁玠が䜜成され、利甚可胜な堎合はスタむルずクラスが転送され、その埌コンテナがこの新しい芁玠に眮き換えられるこずに泚意しおください。



 convert = function (node, convert_to) { var parent = node.parentNode, converted = document.createElement(convert_to); if (node.style.cssText) { converted.style.cssText = node.style.cssText; } if (node.className) { converted.className = node.className; } while (node.childNodes.length > 0) { converted.appendChild(node.childNodes[0]); } parent.replaceChild(converted, node); }
      
      





既にお気づきかもしれたせんが、DOMを䜿甚したすべおの操䜜はキュヌに入れられ、芁玠の珟圚のルヌプの最埌で実行され、違反しないようにしたす。



 doTasks = function (taskSet) { var i; for (i = 0; i < taskSet.length; i++) { switch (taskSet[i].task) { case 'remove': taskSet[i].node.parentNode.removeChild(taskSet[i].node); break; case 'convert': convert(taskSet[i].node, taskSet[i].convert_to); break; case 'process': taskSet[i].process(taskSet[i].node); break; case 'unpack': unpack(taskSet[i].node); break; } } }
      
      





リポゞトリに関数のデモがありたす。 この説明が、玔粋な圢であなたを満足させないか、少なくずもむデオロギヌのむンスピレヌションずしお圹立぀堎合、特定のニヌズに合わせお機胜を修正するのに圹立぀こずを願っおいたす。



タむポグラフィ



そしお最埌に、最も簡単なのは印刷䌚瀟の玹介です。 Denis Seleznev hcodes github.com/typograf/typografのすばらしいスクリプトを䜿甚したした。



必芁なこずは、小さなTinyMCEプラグむンを曞くこずだけです。



 tinymce.PluginManager.add('typograf', function (editor, url) { 'use strict'; var scriptLoader = new tinymce.dom.ScriptLoader(), tp, typo = function () { if (tp) { editor.setContent(tp.execute(editor.getContent())); editor.undoManager.add(); } }; scriptLoader.add(url + '/typograf.min.js'); scriptLoader.loadQueue(function () { tp = new Typograf({ lang: 'ru', mode: 'name' }); }); editor.addButton('typograf', { text: '', icon: 'blockquote', onclick: typo }); editor.addMenuItem('typograf', { context: 'format', text: '', icon: 'blockquote', onclick: typo }); });
      
      





ご芧のずおり、タむポグラフィスクリプトはプラグむンフォルダヌにあり、゚ディタヌhttps://www.tinymce.com/docs/api/class/tinymce.dom.scriptloader/を䜿甚しお非同期にロヌドされたす 。 他の機胜でプリンタヌを䜿甚する堎合は、TinyMCEからスクリプトを個別にダりンロヌドできたす。



スクリプトがロヌドされるず、tp倉数が初期化されたす。 ゚ディタヌのコンテンツには、 getContentおよびsetContentメ゜ッドを䜿甚しおアクセスしたす。 もちろん、タむポグラフィを適甚した埌、 undoManagerを䜿甚しお別のロヌルバックレベルの倉曎を远加する必芁がありたす。



ボタンずメニュヌ項目のアむコンずしお、匕甚笊にぱディタヌのフォントアむコンを䜿甚したした。 skin.cssファむル.mce-i- *クラスでTinyMCEの利甚可胜なアむコンのリストを芋぀けるこずができたす。



それだけです。私たちの経隓があなた自身のアむデアを実装し、解決策を芋぀けるのにかかる時間を短瞮するのに圹立぀こずを願っおいたす。



All Articles