');\n this.$placeholder.on('click', () => {\n this.context.invoke('focus');\n }).html(this.options.placeholder).prependTo(this.$editingArea);\n\n this.update();\n }\n\n destroy() {\n this.$placeholder.remove();\n }\n\n update() {\n const isShow = !this.context.invoke('codeview.isActivated') && this.context.invoke('editor.isEmpty');\n this.$placeholder.toggle(isShow);\n }\n}\n","import $ from 'jquery';\nimport func from '../core/func';\nimport lists from '../core/lists';\nimport env from '../core/env';\n\nexport default class Buttons {\n constructor(context) {\n this.ui = $.summernote.ui;\n this.context = context;\n this.$toolbar = context.layoutInfo.toolbar;\n this.options = context.options;\n this.lang = this.options.langInfo;\n this.invertedKeyMap = func.invertObject(\n this.options.keyMap[env.isMac ? 'mac' : 'pc']\n );\n }\n\n representShortcut(editorMethod) {\n let shortcut = this.invertedKeyMap[editorMethod];\n if (!this.options.shortcuts || !shortcut) {\n return '';\n }\n\n if (env.isMac) {\n shortcut = shortcut.replace('CMD', '⌘').replace('SHIFT', '⇧');\n }\n\n shortcut = shortcut.replace('BACKSLASH', '\\\\')\n .replace('SLASH', '/')\n .replace('LEFTBRACKET', '[')\n .replace('RIGHTBRACKET', ']');\n\n return ' (' + shortcut + ')';\n }\n\n button(o) {\n if (!this.options.tooltip && o.tooltip) {\n delete o.tooltip;\n }\n o.container = this.options.container;\n return this.ui.button(o);\n }\n\n initialize() {\n this.addToolbarButtons();\n this.addImagePopoverButtons();\n this.addLinkPopoverButtons();\n this.addTablePopoverButtons();\n this.fontInstalledMap = {};\n }\n\n destroy() {\n delete this.fontInstalledMap;\n }\n\n isFontInstalled(name) {\n if (!Object.prototype.hasOwnProperty.call(this.fontInstalledMap, name)) {\n this.fontInstalledMap[name] = env.isFontInstalled(name) ||\n lists.contains(this.options.fontNamesIgnoreCheck, name);\n }\n return this.fontInstalledMap[name];\n }\n\n isFontDeservedToAdd(name) {\n name = name.toLowerCase();\n return (name !== '' && this.isFontInstalled(name) && env.genericFontFamilies.indexOf(name) === -1);\n }\n\n colorPalette(className, tooltip, backColor, foreColor) {\n return this.ui.buttonGroup({\n className: 'note-color ' + className,\n children: [\n this.button({\n className: 'note-current-color-button',\n contents: this.ui.icon(this.options.icons.font + ' note-recent-color'),\n tooltip: tooltip,\n click: (e) => {\n const $button = $(e.currentTarget);\n if (backColor && foreColor) {\n this.context.invoke('editor.color', {\n backColor: $button.attr('data-backColor'),\n foreColor: $button.attr('data-foreColor'),\n });\n } else if (backColor) {\n this.context.invoke('editor.color', {\n backColor: $button.attr('data-backColor'),\n });\n } else if (foreColor) {\n this.context.invoke('editor.color', {\n foreColor: $button.attr('data-foreColor'),\n });\n }\n },\n callback: ($button) => {\n const $recentColor = $button.find('.note-recent-color');\n if (backColor) {\n $recentColor.css('background-color', this.options.colorButton.backColor);\n $button.attr('data-backColor', this.options.colorButton.backColor);\n }\n if (foreColor) {\n $recentColor.css('color', this.options.colorButton.foreColor);\n $button.attr('data-foreColor', this.options.colorButton.foreColor);\n } else {\n $recentColor.css('color', 'transparent');\n }\n },\n }),\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents('', this.options),\n tooltip: this.lang.color.more,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdown({\n items: (backColor ? [\n '
',\n '
' + this.lang.color.background + '
',\n '
',\n '',\n '
',\n '
',\n '
',\n '',\n '',\n '
',\n '
',\n '
',\n ].join('') : '') +\n (foreColor ? [\n '
',\n '
' + this.lang.color.foreground + '
',\n '
',\n '',\n '
',\n '
',\n '
',\n '',\n '',\n '
', // Fix missing Div, Commented to find easily if it's wrong\n '
',\n '
',\n ].join('') : ''),\n callback: ($dropdown) => {\n $dropdown.find('.note-holder').each((idx, item) => {\n const $holder = $(item);\n $holder.append(this.ui.palette({\n colors: this.options.colors,\n colorsName: this.options.colorsName,\n eventName: $holder.data('event'),\n container: this.options.container,\n tooltip: this.options.tooltip,\n }).render());\n });\n /* TODO: do we have to record recent custom colors within cookies? */\n var customColors = [\n ['#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF'],\n ];\n $dropdown.find('.note-holder-custom').each((idx, item) => {\n const $holder = $(item);\n $holder.append(this.ui.palette({\n colors: customColors,\n colorsName: customColors,\n eventName: $holder.data('event'),\n container: this.options.container,\n tooltip: this.options.tooltip,\n }).render());\n });\n $dropdown.find('input[type=color]').each((idx, item) => {\n $(item).change(function() {\n const $chip = $dropdown.find('#' + $(this).data('event')).find('.note-color-btn').first();\n const color = this.value.toUpperCase();\n $chip.css('background-color', color)\n .attr('aria-label', color)\n .attr('data-value', color)\n .attr('data-original-title', color);\n $chip.click();\n });\n });\n },\n click: (event) => {\n event.stopPropagation();\n\n const $parent = $('.' + className).find('.note-dropdown-menu');\n const $button = $(event.target);\n const eventName = $button.data('event');\n const value = $button.attr('data-value');\n\n if (eventName === 'openPalette') {\n const $picker = $parent.find('#' + value);\n const $palette = $($parent.find('#' + $picker.data('event')).find('.note-color-row')[0]);\n\n // Shift palette chips\n const $chip = $palette.find('.note-color-btn').last().detach();\n\n // Set chip attributes\n const color = $picker.val();\n $chip.css('background-color', color)\n .attr('aria-label', color)\n .attr('data-value', color)\n .attr('data-original-title', color);\n $palette.prepend($chip);\n $picker.click();\n } else {\n if (lists.contains(['backColor', 'foreColor'], eventName)) {\n const key = eventName === 'backColor' ? 'background-color' : 'color';\n const $color = $button.closest('.note-color').find('.note-recent-color');\n const $currentButton = $button.closest('.note-color').find('.note-current-color-button');\n\n $color.css(key, value);\n $currentButton.attr('data-' + eventName, value);\n }\n this.context.invoke('editor.' + eventName, value);\n }\n },\n }),\n ],\n }).render();\n }\n\n addToolbarButtons() {\n this.context.memo('button.style', () => {\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents(\n this.ui.icon(this.options.icons.magic), this.options\n ),\n tooltip: this.lang.style.style,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdown({\n className: 'dropdown-style',\n items: this.options.styleTags,\n title: this.lang.style.style,\n template: (item) => {\n // TBD: need to be simplified\n if (typeof item === 'string') {\n item = {\n tag: item,\n title: (Object.prototype.hasOwnProperty.call(this.lang.style, item) ? this.lang.style[item] : item),\n };\n }\n\n const tag = item.tag;\n const title = item.title;\n const style = item.style ? ' style=\"' + item.style + '\" ' : '';\n const className = item.className ? ' class=\"' + item.className + '\"' : '';\n\n return '<' + tag + style + className + '>' + title + '' + tag + '>';\n },\n click: this.context.createInvokeHandler('editor.formatBlock'),\n }),\n ]).render();\n });\n\n for (let styleIdx = 0, styleLen = this.options.styleTags.length; styleIdx < styleLen; styleIdx++) {\n const item = this.options.styleTags[styleIdx];\n\n this.context.memo('button.style.' + item, () => {\n return this.button({\n className: 'note-btn-style-' + item,\n contents: '
' + item.toUpperCase() + '
',\n tooltip: this.lang.style[item],\n click: this.context.createInvokeHandler('editor.formatBlock'),\n }).render();\n });\n }\n\n this.context.memo('button.bold', () => {\n return this.button({\n className: 'note-btn-bold',\n contents: this.ui.icon(this.options.icons.bold),\n tooltip: this.lang.font.bold + this.representShortcut('bold'),\n click: this.context.createInvokeHandlerAndUpdateState('editor.bold'),\n }).render();\n });\n\n this.context.memo('button.italic', () => {\n return this.button({\n className: 'note-btn-italic',\n contents: this.ui.icon(this.options.icons.italic),\n tooltip: this.lang.font.italic + this.representShortcut('italic'),\n click: this.context.createInvokeHandlerAndUpdateState('editor.italic'),\n }).render();\n });\n\n this.context.memo('button.underline', () => {\n return this.button({\n className: 'note-btn-underline',\n contents: this.ui.icon(this.options.icons.underline),\n tooltip: this.lang.font.underline + this.representShortcut('underline'),\n click: this.context.createInvokeHandlerAndUpdateState('editor.underline'),\n }).render();\n });\n\n this.context.memo('button.clear', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.eraser),\n tooltip: this.lang.font.clear + this.representShortcut('removeFormat'),\n click: this.context.createInvokeHandler('editor.removeFormat'),\n }).render();\n });\n\n this.context.memo('button.strikethrough', () => {\n return this.button({\n className: 'note-btn-strikethrough',\n contents: this.ui.icon(this.options.icons.strikethrough),\n tooltip: this.lang.font.strikethrough + this.representShortcut('strikethrough'),\n click: this.context.createInvokeHandlerAndUpdateState('editor.strikethrough'),\n }).render();\n });\n\n this.context.memo('button.superscript', () => {\n return this.button({\n className: 'note-btn-superscript',\n contents: this.ui.icon(this.options.icons.superscript),\n tooltip: this.lang.font.superscript,\n click: this.context.createInvokeHandlerAndUpdateState('editor.superscript'),\n }).render();\n });\n\n this.context.memo('button.subscript', () => {\n return this.button({\n className: 'note-btn-subscript',\n contents: this.ui.icon(this.options.icons.subscript),\n tooltip: this.lang.font.subscript,\n click: this.context.createInvokeHandlerAndUpdateState('editor.subscript'),\n }).render();\n });\n\n this.context.memo('button.fontname', () => {\n const styleInfo = this.context.invoke('editor.currentStyle');\n\n if (this.options.addDefaultFonts) {\n // Add 'default' fonts into the fontnames array if not exist\n $.each(styleInfo['font-family'].split(','), (idx, fontname) => {\n fontname = fontname.trim().replace(/['\"]+/g, '');\n if (this.isFontDeservedToAdd(fontname)) {\n if (this.options.fontNames.indexOf(fontname) === -1) {\n this.options.fontNames.push(fontname);\n }\n }\n });\n }\n\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents(\n '
', this.options\n ),\n tooltip: this.lang.font.name,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdownCheck({\n className: 'dropdown-fontname',\n checkClassName: this.options.icons.menuCheck,\n items: this.options.fontNames.filter(this.isFontInstalled.bind(this)),\n title: this.lang.font.name,\n template: (item) => {\n return '
' + item + '';\n },\n click: this.context.createInvokeHandlerAndUpdateState('editor.fontName'),\n }),\n ]).render();\n });\n\n this.context.memo('button.fontsize', () => {\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents('
', this.options),\n tooltip: this.lang.font.size,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdownCheck({\n className: 'dropdown-fontsize',\n checkClassName: this.options.icons.menuCheck,\n items: this.options.fontSizes,\n title: this.lang.font.size,\n click: this.context.createInvokeHandlerAndUpdateState('editor.fontSize'),\n }),\n ]).render();\n });\n\n this.context.memo('button.fontsizeunit', () => {\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents('
', this.options),\n tooltip: this.lang.font.sizeunit,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdownCheck({\n className: 'dropdown-fontsizeunit',\n checkClassName: this.options.icons.menuCheck,\n items: this.options.fontSizeUnits,\n title: this.lang.font.sizeunit,\n click: this.context.createInvokeHandlerAndUpdateState('editor.fontSizeUnit'),\n }),\n ]).render();\n });\n\n this.context.memo('button.color', () => {\n return this.colorPalette('note-color-all', this.lang.color.recent, true, true);\n });\n\n this.context.memo('button.forecolor', () => {\n return this.colorPalette('note-color-fore', this.lang.color.foreground, false, true);\n });\n\n this.context.memo('button.backcolor', () => {\n return this.colorPalette('note-color-back', this.lang.color.background, true, false);\n });\n\n this.context.memo('button.ul', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.unorderedlist),\n tooltip: this.lang.lists.unordered + this.representShortcut('insertUnorderedList'),\n click: this.context.createInvokeHandler('editor.insertUnorderedList'),\n }).render();\n });\n\n this.context.memo('button.ol', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.orderedlist),\n tooltip: this.lang.lists.ordered + this.representShortcut('insertOrderedList'),\n click: this.context.createInvokeHandler('editor.insertOrderedList'),\n }).render();\n });\n\n const justifyLeft = this.button({\n contents: this.ui.icon(this.options.icons.alignLeft),\n tooltip: this.lang.paragraph.left + this.representShortcut('justifyLeft'),\n click: this.context.createInvokeHandler('editor.justifyLeft'),\n });\n\n const justifyCenter = this.button({\n contents: this.ui.icon(this.options.icons.alignCenter),\n tooltip: this.lang.paragraph.center + this.representShortcut('justifyCenter'),\n click: this.context.createInvokeHandler('editor.justifyCenter'),\n });\n\n const justifyRight = this.button({\n contents: this.ui.icon(this.options.icons.alignRight),\n tooltip: this.lang.paragraph.right + this.representShortcut('justifyRight'),\n click: this.context.createInvokeHandler('editor.justifyRight'),\n });\n\n const justifyFull = this.button({\n contents: this.ui.icon(this.options.icons.alignJustify),\n tooltip: this.lang.paragraph.justify + this.representShortcut('justifyFull'),\n click: this.context.createInvokeHandler('editor.justifyFull'),\n });\n\n const outdent = this.button({\n contents: this.ui.icon(this.options.icons.outdent),\n tooltip: this.lang.paragraph.outdent + this.representShortcut('outdent'),\n click: this.context.createInvokeHandler('editor.outdent'),\n });\n\n const indent = this.button({\n contents: this.ui.icon(this.options.icons.indent),\n tooltip: this.lang.paragraph.indent + this.representShortcut('indent'),\n click: this.context.createInvokeHandler('editor.indent'),\n });\n\n this.context.memo('button.justifyLeft', func.invoke(justifyLeft, 'render'));\n this.context.memo('button.justifyCenter', func.invoke(justifyCenter, 'render'));\n this.context.memo('button.justifyRight', func.invoke(justifyRight, 'render'));\n this.context.memo('button.justifyFull', func.invoke(justifyFull, 'render'));\n this.context.memo('button.outdent', func.invoke(outdent, 'render'));\n this.context.memo('button.indent', func.invoke(indent, 'render'));\n\n this.context.memo('button.paragraph', () => {\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents(this.ui.icon(this.options.icons.alignLeft), this.options),\n tooltip: this.lang.paragraph.paragraph,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdown([\n this.ui.buttonGroup({\n className: 'note-align',\n children: [justifyLeft, justifyCenter, justifyRight, justifyFull],\n }),\n this.ui.buttonGroup({\n className: 'note-list',\n children: [outdent, indent],\n }),\n ]),\n ]).render();\n });\n\n this.context.memo('button.height', () => {\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents(this.ui.icon(this.options.icons.textHeight), this.options),\n tooltip: this.lang.font.height,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdownCheck({\n items: this.options.lineHeights,\n checkClassName: this.options.icons.menuCheck,\n className: 'dropdown-line-height',\n title: this.lang.font.height,\n click: this.context.createInvokeHandler('editor.lineHeight'),\n }),\n ]).render();\n });\n\n this.context.memo('button.table', () => {\n return this.ui.buttonGroup([\n this.button({\n className: 'dropdown-toggle',\n contents: this.ui.dropdownButtonContents(this.ui.icon(this.options.icons.table), this.options),\n tooltip: this.lang.table.table,\n data: {\n toggle: 'dropdown',\n },\n }),\n this.ui.dropdown({\n title: this.lang.table.table,\n className: 'note-table',\n items: [\n '
',\n '
1 x 1
',\n ].join(''),\n }),\n ], {\n callback: ($node) => {\n const $catcher = $node.find('.note-dimension-picker-mousecatcher');\n $catcher.css({\n width: this.options.insertTableMaxSize.col + 'em',\n height: this.options.insertTableMaxSize.row + 'em',\n }).mousedown(this.context.createInvokeHandler('editor.insertTable'))\n .on('mousemove', this.tableMoveHandler.bind(this));\n },\n }).render();\n });\n\n this.context.memo('button.link', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.link),\n tooltip: this.lang.link.link + this.representShortcut('linkDialog.show'),\n click: this.context.createInvokeHandler('linkDialog.show'),\n }).render();\n });\n\n this.context.memo('button.picture', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.picture),\n tooltip: this.lang.image.image,\n click: this.context.createInvokeHandler('imageDialog.show'),\n }).render();\n });\n\n this.context.memo('button.video', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.video),\n tooltip: this.lang.video.video,\n click: this.context.createInvokeHandler('videoDialog.show'),\n }).render();\n });\n\n this.context.memo('button.hr', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.minus),\n tooltip: this.lang.hr.insert + this.representShortcut('insertHorizontalRule'),\n click: this.context.createInvokeHandler('editor.insertHorizontalRule'),\n }).render();\n });\n\n this.context.memo('button.fullscreen', () => {\n return this.button({\n className: 'btn-fullscreen note-codeview-keep',\n contents: this.ui.icon(this.options.icons.arrowsAlt),\n tooltip: this.lang.options.fullscreen,\n click: this.context.createInvokeHandler('fullscreen.toggle'),\n }).render();\n });\n\n this.context.memo('button.codeview', () => {\n return this.button({\n className: 'btn-codeview note-codeview-keep',\n contents: this.ui.icon(this.options.icons.code),\n tooltip: this.lang.options.codeview,\n click: this.context.createInvokeHandler('codeview.toggle'),\n }).render();\n });\n\n this.context.memo('button.redo', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.redo),\n tooltip: this.lang.history.redo + this.representShortcut('redo'),\n click: this.context.createInvokeHandler('editor.redo'),\n }).render();\n });\n\n this.context.memo('button.undo', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.undo),\n tooltip: this.lang.history.undo + this.representShortcut('undo'),\n click: this.context.createInvokeHandler('editor.undo'),\n }).render();\n });\n\n this.context.memo('button.help', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.question),\n tooltip: this.lang.options.help,\n click: this.context.createInvokeHandler('helpDialog.show'),\n }).render();\n });\n }\n\n /**\n * image: [\n * ['imageResize', ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone']],\n * ['float', ['floatLeft', 'floatRight', 'floatNone']],\n * ['remove', ['removeMedia']],\n * ],\n */\n addImagePopoverButtons() {\n // Image Size Buttons\n this.context.memo('button.resizeFull', () => {\n return this.button({\n contents: '
100%',\n tooltip: this.lang.image.resizeFull,\n click: this.context.createInvokeHandler('editor.resize', '1'),\n }).render();\n });\n this.context.memo('button.resizeHalf', () => {\n return this.button({\n contents: '
50%',\n tooltip: this.lang.image.resizeHalf,\n click: this.context.createInvokeHandler('editor.resize', '0.5'),\n }).render();\n });\n this.context.memo('button.resizeQuarter', () => {\n return this.button({\n contents: '
25%',\n tooltip: this.lang.image.resizeQuarter,\n click: this.context.createInvokeHandler('editor.resize', '0.25'),\n }).render();\n });\n this.context.memo('button.resizeNone', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.rollback),\n tooltip: this.lang.image.resizeNone,\n click: this.context.createInvokeHandler('editor.resize', '0'),\n }).render();\n });\n\n // Float Buttons\n this.context.memo('button.floatLeft', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.floatLeft),\n tooltip: this.lang.image.floatLeft,\n click: this.context.createInvokeHandler('editor.floatMe', 'left'),\n }).render();\n });\n\n this.context.memo('button.floatRight', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.floatRight),\n tooltip: this.lang.image.floatRight,\n click: this.context.createInvokeHandler('editor.floatMe', 'right'),\n }).render();\n });\n\n this.context.memo('button.floatNone', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.rollback),\n tooltip: this.lang.image.floatNone,\n click: this.context.createInvokeHandler('editor.floatMe', 'none'),\n }).render();\n });\n\n // Remove Buttons\n this.context.memo('button.removeMedia', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.trash),\n tooltip: this.lang.image.remove,\n click: this.context.createInvokeHandler('editor.removeMedia'),\n }).render();\n });\n }\n\n addLinkPopoverButtons() {\n this.context.memo('button.linkDialogShow', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.link),\n tooltip: this.lang.link.edit,\n click: this.context.createInvokeHandler('linkDialog.show'),\n }).render();\n });\n\n this.context.memo('button.unlink', () => {\n return this.button({\n contents: this.ui.icon(this.options.icons.unlink),\n tooltip: this.lang.link.unlink,\n click: this.context.createInvokeHandler('editor.unlink'),\n }).render();\n });\n }\n\n /**\n * table : [\n * ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],\n * ['delete', ['deleteRow', 'deleteCol', 'deleteTable']]\n * ],\n */\n addTablePopoverButtons() {\n this.context.memo('button.addRowUp', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.rowAbove),\n tooltip: this.lang.table.addRowAbove,\n click: this.context.createInvokeHandler('editor.addRow', 'top'),\n }).render();\n });\n this.context.memo('button.addRowDown', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.rowBelow),\n tooltip: this.lang.table.addRowBelow,\n click: this.context.createInvokeHandler('editor.addRow', 'bottom'),\n }).render();\n });\n this.context.memo('button.addColLeft', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.colBefore),\n tooltip: this.lang.table.addColLeft,\n click: this.context.createInvokeHandler('editor.addCol', 'left'),\n }).render();\n });\n this.context.memo('button.addColRight', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.colAfter),\n tooltip: this.lang.table.addColRight,\n click: this.context.createInvokeHandler('editor.addCol', 'right'),\n }).render();\n });\n this.context.memo('button.deleteRow', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.rowRemove),\n tooltip: this.lang.table.delRow,\n click: this.context.createInvokeHandler('editor.deleteRow'),\n }).render();\n });\n this.context.memo('button.deleteCol', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.colRemove),\n tooltip: this.lang.table.delCol,\n click: this.context.createInvokeHandler('editor.deleteCol'),\n }).render();\n });\n this.context.memo('button.deleteTable', () => {\n return this.button({\n className: 'btn-md',\n contents: this.ui.icon(this.options.icons.trash),\n tooltip: this.lang.table.delTable,\n click: this.context.createInvokeHandler('editor.deleteTable'),\n }).render();\n });\n }\n\n build($container, groups) {\n for (let groupIdx = 0, groupLen = groups.length; groupIdx < groupLen; groupIdx++) {\n const group = groups[groupIdx];\n const groupName = Array.isArray(group) ? group[0] : group;\n const buttons = Array.isArray(group) ? ((group.length === 1) ? [group[0]] : group[1]) : [group];\n\n const $group = this.ui.buttonGroup({\n className: 'note-' + groupName,\n }).render();\n\n for (let idx = 0, len = buttons.length; idx < len; idx++) {\n const btn = this.context.memo('button.' + buttons[idx]);\n if (btn) {\n $group.append(typeof btn === 'function' ? btn(this.context) : btn);\n }\n }\n $group.appendTo($container);\n }\n }\n\n /**\n * @param {jQuery} [$container]\n */\n updateCurrentStyle($container) {\n const $cont = $container || this.$toolbar;\n\n const styleInfo = this.context.invoke('editor.currentStyle');\n this.updateBtnStates($cont, {\n '.note-btn-bold': () => {\n return styleInfo['font-bold'] === 'bold';\n },\n '.note-btn-italic': () => {\n return styleInfo['font-italic'] === 'italic';\n },\n '.note-btn-underline': () => {\n return styleInfo['font-underline'] === 'underline';\n },\n '.note-btn-subscript': () => {\n return styleInfo['font-subscript'] === 'subscript';\n },\n '.note-btn-superscript': () => {\n return styleInfo['font-superscript'] === 'superscript';\n },\n '.note-btn-strikethrough': () => {\n return styleInfo['font-strikethrough'] === 'strikethrough';\n },\n });\n\n if (styleInfo['font-family']) {\n const fontNames = styleInfo['font-family'].split(',').map((name) => {\n return name.replace(/[\\'\\\"]/g, '')\n .replace(/\\s+$/, '')\n .replace(/^\\s+/, '');\n });\n const fontName = lists.find(fontNames, this.isFontInstalled.bind(this));\n\n $cont.find('.dropdown-fontname a').each((idx, item) => {\n const $item = $(item);\n // always compare string to avoid creating another func.\n const isChecked = ($item.data('value') + '') === (fontName + '');\n $item.toggleClass('checked', isChecked);\n });\n $cont.find('.note-current-fontname').text(fontName).css('font-family', fontName);\n }\n\n if (styleInfo['font-size']) {\n const fontSize = styleInfo['font-size'];\n $cont.find('.dropdown-fontsize a').each((idx, item) => {\n const $item = $(item);\n // always compare with string to avoid creating another func.\n const isChecked = ($item.data('value') + '') === (fontSize + '');\n $item.toggleClass('checked', isChecked);\n });\n $cont.find('.note-current-fontsize').text(fontSize);\n\n const fontSizeUnit = styleInfo['font-size-unit'];\n $cont.find('.dropdown-fontsizeunit a').each((idx, item) => {\n const $item = $(item);\n const isChecked = ($item.data('value') + '') === (fontSizeUnit + '');\n $item.toggleClass('checked', isChecked);\n });\n $cont.find('.note-current-fontsizeunit').text(fontSizeUnit);\n }\n\n if (styleInfo['line-height']) {\n const lineHeight = styleInfo['line-height'];\n $cont.find('.dropdown-line-height li a').each((idx, item) => {\n // always compare with string to avoid creating another func.\n const isChecked = ($(item).data('value') + '') === (lineHeight + '');\n this.className = isChecked ? 'checked' : '';\n });\n }\n }\n\n updateBtnStates($container, infos) {\n $.each(infos, (selector, pred) => {\n this.ui.toggleBtnActive($container.find(selector), pred());\n });\n }\n\n tableMoveHandler(event) {\n const PX_PER_EM = 18;\n const $picker = $(event.target.parentNode); // target is mousecatcher\n const $dimensionDisplay = $picker.next();\n const $catcher = $picker.find('.note-dimension-picker-mousecatcher');\n const $highlighted = $picker.find('.note-dimension-picker-highlighted');\n const $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');\n\n let posOffset;\n // HTML5 with jQuery - e.offsetX is undefined in Firefox\n if (event.offsetX === undefined) {\n const posCatcher = $(event.target).offset();\n posOffset = {\n x: event.pageX - posCatcher.left,\n y: event.pageY - posCatcher.top,\n };\n } else {\n posOffset = {\n x: event.offsetX,\n y: event.offsetY,\n };\n }\n\n const dim = {\n c: Math.ceil(posOffset.x / PX_PER_EM) || 1,\n r: Math.ceil(posOffset.y / PX_PER_EM) || 1,\n };\n\n $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });\n $catcher.data('value', dim.c + 'x' + dim.r);\n\n if (dim.c > 3 && dim.c < this.options.insertTableMaxSize.col) {\n $unhighlighted.css({ width: dim.c + 1 + 'em' });\n }\n\n if (dim.r > 3 && dim.r < this.options.insertTableMaxSize.row) {\n $unhighlighted.css({ height: dim.r + 1 + 'em' });\n }\n\n $dimensionDisplay.html(dim.c + ' x ' + dim.r);\n }\n}\n","import $ from 'jquery';\nexport default class Toolbar {\n constructor(context) {\n this.context = context;\n\n this.$window = $(window);\n this.$document = $(document);\n\n this.ui = $.summernote.ui;\n this.$note = context.layoutInfo.note;\n this.$editor = context.layoutInfo.editor;\n this.$toolbar = context.layoutInfo.toolbar;\n this.$editable = context.layoutInfo.editable;\n this.$statusbar = context.layoutInfo.statusbar;\n this.options = context.options;\n\n this.isFollowing = false;\n this.followScroll = this.followScroll.bind(this);\n }\n\n shouldInitialize() {\n return !this.options.airMode;\n }\n\n initialize() {\n this.options.toolbar = this.options.toolbar || [];\n\n if (!this.options.toolbar.length) {\n this.$toolbar.hide();\n } else {\n this.context.invoke('buttons.build', this.$toolbar, this.options.toolbar);\n }\n\n if (this.options.toolbarContainer) {\n this.$toolbar.appendTo(this.options.toolbarContainer);\n }\n\n this.changeContainer(false);\n\n this.$note.on('summernote.keyup summernote.mouseup summernote.change', () => {\n this.context.invoke('buttons.updateCurrentStyle');\n });\n\n this.context.invoke('buttons.updateCurrentStyle');\n if (this.options.followingToolbar) {\n this.$window.on('scroll resize', this.followScroll);\n }\n }\n\n destroy() {\n this.$toolbar.children().remove();\n\n if (this.options.followingToolbar) {\n this.$window.off('scroll resize', this.followScroll);\n }\n }\n\n followScroll() {\n if (this.$editor.hasClass('fullscreen')) {\n return false;\n }\n\n const editorHeight = this.$editor.outerHeight();\n const editorWidth = this.$editor.width();\n const toolbarHeight = this.$toolbar.height();\n const statusbarHeight = this.$statusbar.height();\n\n // check if the web app is currently using another static bar\n let otherBarHeight = 0;\n if (this.options.otherStaticBar) {\n otherBarHeight = $(this.options.otherStaticBar).outerHeight();\n }\n\n const currentOffset = this.$document.scrollTop();\n const editorOffsetTop = this.$editor.offset().top;\n const editorOffsetBottom = editorOffsetTop + editorHeight;\n const activateOffset = editorOffsetTop - otherBarHeight;\n const deactivateOffsetBottom = editorOffsetBottom - otherBarHeight - toolbarHeight - statusbarHeight;\n\n if (!this.isFollowing &&\n (currentOffset > activateOffset) && (currentOffset < deactivateOffsetBottom - toolbarHeight)) {\n this.isFollowing = true;\n this.$editable.css({\n marginTop: this.$toolbar.outerHeight(),\n });\n this.$toolbar.css({\n position: 'fixed',\n top: otherBarHeight,\n width: editorWidth,\n zIndex: 1000,\n });\n } else if (this.isFollowing &&\n ((currentOffset < activateOffset) || (currentOffset > deactivateOffsetBottom))) {\n this.isFollowing = false;\n this.$toolbar.css({\n position: 'relative',\n top: 0,\n width: '100%',\n zIndex: 'auto',\n });\n this.$editable.css({\n marginTop: '',\n });\n }\n }\n\n changeContainer(isFullscreen) {\n if (isFullscreen) {\n this.$toolbar.prependTo(this.$editor);\n } else {\n if (this.options.toolbarContainer) {\n this.$toolbar.appendTo(this.options.toolbarContainer);\n }\n }\n if (this.options.followingToolbar) {\n this.followScroll();\n }\n }\n\n updateFullscreen(isFullscreen) {\n this.ui.toggleBtnActive(this.$toolbar.find('.btn-fullscreen'), isFullscreen);\n\n this.changeContainer(isFullscreen);\n }\n\n updateCodeview(isCodeview) {\n this.ui.toggleBtnActive(this.$toolbar.find('.btn-codeview'), isCodeview);\n if (isCodeview) {\n this.deactivate();\n } else {\n this.activate();\n }\n }\n\n activate(isIncludeCodeview) {\n let $btn = this.$toolbar.find('button');\n if (!isIncludeCodeview) {\n $btn = $btn.not('.note-codeview-keep');\n }\n this.ui.toggleBtn($btn, true);\n }\n\n deactivate(isIncludeCodeview) {\n let $btn = this.$toolbar.find('button');\n if (!isIncludeCodeview) {\n $btn = $btn.not('.note-codeview-keep');\n }\n this.ui.toggleBtn($btn, false);\n }\n}\n","import $ from 'jquery';\nimport env from '../core/env';\nimport key from '../core/key';\nimport func from '../core/func';\n\nexport default class LinkDialog {\n constructor(context) {\n this.context = context;\n\n this.ui = $.summernote.ui;\n this.$body = $(document.body);\n this.$editor = context.layoutInfo.editor;\n this.options = context.options;\n this.lang = this.options.langInfo;\n\n context.memo('help.linkDialog.show', this.options.langInfo.help['linkDialog.show']);\n }\n\n initialize() {\n const $container = this.options.dialogsInBody ? this.$body : this.options.container;\n const body = [\n '
',\n ``,\n ``,\n '
',\n '
',\n ``,\n ``,\n '
',\n !this.options.disableLinkTarget\n ? $('
').append(this.ui.checkbox({\n className: 'sn-checkbox-open-in-new-window',\n text: this.lang.link.openInNewWindow,\n checked: true,\n }).render()).html()\n : '',\n $('
').append(this.ui.checkbox({\n className: 'sn-checkbox-use-protocol',\n text: this.lang.link.useProtocol,\n checked: true,\n }).render()).html(),\n ].join('');\n\n const buttonClass = 'btn btn-primary note-btn note-btn-primary note-link-btn';\n const footer = `
`;\n\n this.$dialog = this.ui.dialog({\n className: 'link-dialog',\n title: this.lang.link.insert,\n fade: this.options.dialogsFade,\n body: body,\n footer: footer,\n }).render().appendTo($container);\n }\n\n destroy() {\n this.ui.hideDialog(this.$dialog);\n this.$dialog.remove();\n }\n\n bindEnterKey($input, $btn) {\n $input.on('keypress', (event) => {\n if (event.keyCode === key.code.ENTER) {\n event.preventDefault();\n $btn.trigger('click');\n }\n });\n }\n\n /**\n * toggle update button\n */\n toggleLinkBtn($linkBtn, $linkText, $linkUrl) {\n this.ui.toggleBtn($linkBtn, $linkText.val() && $linkUrl.val());\n }\n\n /**\n * Show link dialog and set event handlers on dialog controls.\n *\n * @param {Object} linkInfo\n * @return {Promise}\n */\n showLinkDialog(linkInfo) {\n return $.Deferred((deferred) => {\n const $linkText = this.$dialog.find('.note-link-text');\n const $linkUrl = this.$dialog.find('.note-link-url');\n const $linkBtn = this.$dialog.find('.note-link-btn');\n const $openInNewWindow = this.$dialog\n .find('.sn-checkbox-open-in-new-window input[type=checkbox]');\n const $useProtocol = this.$dialog\n .find('.sn-checkbox-use-protocol input[type=checkbox]');\n\n this.ui.onDialogShown(this.$dialog, () => {\n this.context.triggerEvent('dialog.shown');\n\n // If no url was given and given text is valid URL then copy that into URL Field\n if (!linkInfo.url && func.isValidUrl(linkInfo.text)) {\n linkInfo.url = linkInfo.text;\n }\n\n $linkText.on('input paste propertychange', () => {\n // If linktext was modified by input events,\n // cloning text from linkUrl will be stopped.\n linkInfo.text = $linkText.val();\n this.toggleLinkBtn($linkBtn, $linkText, $linkUrl);\n }).val(linkInfo.text);\n\n $linkUrl.on('input paste propertychange', () => {\n // Display same text on `Text to display` as default\n // when linktext has no text\n if (!linkInfo.text) {\n $linkText.val($linkUrl.val());\n }\n this.toggleLinkBtn($linkBtn, $linkText, $linkUrl);\n }).val(linkInfo.url);\n\n if (!env.isSupportTouch) {\n $linkUrl.trigger('focus');\n }\n\n this.toggleLinkBtn($linkBtn, $linkText, $linkUrl);\n this.bindEnterKey($linkUrl, $linkBtn);\n this.bindEnterKey($linkText, $linkBtn);\n\n const isNewWindowChecked = linkInfo.isNewWindow !== undefined\n ? linkInfo.isNewWindow : this.context.options.linkTargetBlank;\n\n $openInNewWindow.prop('checked', isNewWindowChecked);\n\n const useProtocolChecked = linkInfo.url\n ? false : this.context.options.useProtocol;\n\n $useProtocol.prop('checked', useProtocolChecked);\n\n $linkBtn.one('click', (event) => {\n event.preventDefault();\n\n deferred.resolve({\n range: linkInfo.range,\n url: $linkUrl.val(),\n text: $linkText.val(),\n isNewWindow: $openInNewWindow.is(':checked'),\n checkProtocol: $useProtocol.is(':checked'),\n });\n this.ui.hideDialog(this.$dialog);\n });\n });\n\n this.ui.onDialogHidden(this.$dialog, () => {\n // detach events\n $linkText.off();\n $linkUrl.off();\n $linkBtn.off();\n\n if (deferred.state() === 'pending') {\n deferred.reject();\n }\n });\n\n this.ui.showDialog(this.$dialog);\n }).promise();\n }\n\n /**\n * @param {Object} layoutInfo\n */\n show() {\n const linkInfo = this.context.invoke('editor.getLinkInfo');\n\n this.context.invoke('editor.saveRange');\n this.showLinkDialog(linkInfo).then((linkInfo) => {\n this.context.invoke('editor.restoreRange');\n this.context.invoke('editor.createLink', linkInfo);\n }).fail(() => {\n this.context.invoke('editor.restoreRange');\n });\n }\n}\n","import $ from 'jquery';\nimport lists from '../core/lists';\nimport dom from '../core/dom';\n\nexport default class LinkPopover {\n constructor(context) {\n this.context = context;\n\n this.ui = $.summernote.ui;\n this.options = context.options;\n this.events = {\n 'summernote.keyup summernote.mouseup summernote.change summernote.scroll': () => {\n this.update();\n },\n 'summernote.disable summernote.dialog.shown summernote.blur': () => {\n this.hide();\n },\n };\n }\n\n shouldInitialize() {\n return !lists.isEmpty(this.options.popover.link);\n }\n\n initialize() {\n this.$popover = this.ui.popover({\n className: 'note-link-popover',\n callback: ($node) => {\n const $content = $node.find('.popover-content,.note-popover-content');\n $content.prepend('
');\n },\n }).render().appendTo(this.options.container);\n const $content = this.$popover.find('.popover-content,.note-popover-content');\n\n this.context.invoke('buttons.build', $content, this.options.popover.link);\n\n this.$popover.on('mousedown', (e) => { e.preventDefault(); });\n }\n\n destroy() {\n this.$popover.remove();\n }\n\n update() {\n // Prevent focusing on editable when invoke('code') is executed\n if (!this.context.invoke('editor.hasFocus')) {\n this.hide();\n return;\n }\n\n const rng = this.context.invoke('editor.getLastRange');\n if (rng.isCollapsed() && rng.isOnAnchor()) {\n const anchor = dom.ancestor(rng.sc, dom.isAnchor);\n const href = $(anchor).attr('href');\n this.$popover.find('a').attr('href', href).text(href);\n\n const pos = dom.posFromPlaceholder(anchor);\n const containerOffset = $(this.options.container).offset();\n pos.top -= containerOffset.top;\n pos.left -= containerOffset.left;\n\n this.$popover.css({\n display: 'block',\n left: pos.left,\n top: pos.top,\n });\n } else {\n this.hide();\n }\n }\n\n hide() {\n this.$popover.hide();\n }\n}\n","import $ from 'jquery';\nimport env from '../core/env';\nimport key from '../core/key';\n\nexport default class ImageDialog {\n constructor(context) {\n this.context = context;\n this.ui = $.summernote.ui;\n this.$body = $(document.body);\n this.$editor = context.layoutInfo.editor;\n this.options = context.options;\n this.lang = this.options.langInfo;\n }\n\n initialize() {\n let imageLimitation = '';\n if (this.options.maximumImageFileSize) {\n const unit = Math.floor(Math.log(this.options.maximumImageFileSize) / Math.log(1024));\n const readableSize = (this.options.maximumImageFileSize / Math.pow(1024, unit)).toFixed(2) * 1 +\n ' ' + ' KMGTP'[unit] + 'B';\n imageLimitation = `
${this.lang.image.maximumFileSize + ' : ' + readableSize}`;\n }\n\n const $container = this.options.dialogsInBody ? this.$body : this.options.container;\n const body = [\n '
',\n '',\n '',\n imageLimitation,\n '
',\n '
',\n '',\n '',\n '
',\n ].join('');\n const buttonClass = 'btn btn-primary note-btn note-btn-primary note-image-btn';\n const footer = `
`;\n\n this.$dialog = this.ui.dialog({\n title: this.lang.image.insert,\n fade: this.options.dialogsFade,\n body: body,\n footer: footer,\n }).render().appendTo($container);\n }\n\n destroy() {\n this.ui.hideDialog(this.$dialog);\n this.$dialog.remove();\n }\n\n bindEnterKey($input, $btn) {\n $input.on('keypress', (event) => {\n if (event.keyCode === key.code.ENTER) {\n event.preventDefault();\n $btn.trigger('click');\n }\n });\n }\n\n show() {\n this.context.invoke('editor.saveRange');\n this.showImageDialog().then((data) => {\n // [workaround] hide dialog before restore range for IE range focus\n this.ui.hideDialog(this.$dialog);\n this.context.invoke('editor.restoreRange');\n\n if (typeof data === 'string') { // image url\n // If onImageLinkInsert set,\n if (this.options.callbacks.onImageLinkInsert) {\n this.context.triggerEvent('image.link.insert', data);\n } else {\n this.context.invoke('editor.insertImage', data);\n }\n } else { // array of files\n this.context.invoke('editor.insertImagesOrCallback', data);\n }\n }).fail(() => {\n this.context.invoke('editor.restoreRange');\n });\n }\n\n /**\n * show image dialog\n *\n * @param {jQuery} $dialog\n * @return {Promise}\n */\n showImageDialog() {\n return $.Deferred((deferred) => {\n const $imageInput = this.$dialog.find('.note-image-input');\n const $imageUrl = this.$dialog.find('.note-image-url');\n const $imageBtn = this.$dialog.find('.note-image-btn');\n\n this.ui.onDialogShown(this.$dialog, () => {\n this.context.triggerEvent('dialog.shown');\n\n // Cloning imageInput to clear element.\n $imageInput.replaceWith($imageInput.clone().on('change', (event) => {\n deferred.resolve(event.target.files || event.target.value);\n }).val(''));\n\n $imageUrl.on('input paste propertychange', () => {\n this.ui.toggleBtn($imageBtn, $imageUrl.val());\n }).val('');\n\n if (!env.isSupportTouch) {\n $imageUrl.trigger('focus');\n }\n\n $imageBtn.click((event) => {\n event.preventDefault();\n deferred.resolve($imageUrl.val());\n });\n\n this.bindEnterKey($imageUrl, $imageBtn);\n });\n\n this.ui.onDialogHidden(this.$dialog, () => {\n $imageInput.off();\n $imageUrl.off();\n $imageBtn.off();\n\n if (deferred.state() === 'pending') {\n deferred.reject();\n }\n });\n\n this.ui.showDialog(this.$dialog);\n });\n }\n}\n","import $ from 'jquery';\nimport lists from '../core/lists';\nimport dom from '../core/dom';\n\n/**\n * Image popover module\n * mouse events that show/hide popover will be handled by Handle.js.\n * Handle.js will receive the events and invoke 'imagePopover.update'.\n */\nexport default class ImagePopover {\n constructor(context) {\n this.context = context;\n this.ui = $.summernote.ui;\n\n this.editable = context.layoutInfo.editable[0];\n this.options = context.options;\n\n this.events = {\n 'summernote.disable summernote.blur': () => {\n this.hide();\n },\n };\n }\n\n shouldInitialize() {\n return !lists.isEmpty(this.options.popover.image);\n }\n\n initialize() {\n this.$popover = this.ui.popover({\n className: 'note-image-popover',\n }).render().appendTo(this.options.container);\n const $content = this.$popover.find('.popover-content,.note-popover-content');\n this.context.invoke('buttons.build', $content, this.options.popover.image);\n\n this.$popover.on('mousedown', (e) => { e.preventDefault(); });\n }\n\n destroy() {\n this.$popover.remove();\n }\n\n update(target, event) {\n if (dom.isImg(target)) {\n const position = $(target).offset();\n const containerOffset = $(this.options.container).offset();\n let pos = {};\n if (this.options.popatmouse) {\n pos.left = event.pageX - 20;\n pos.top = event.pageY;\n } else {\n pos = position;\n }\n pos.top -= containerOffset.top;\n pos.left -= containerOffset.left;\n\n this.$popover.css({\n display: 'block',\n left: pos.left,\n top: pos.top,\n });\n } else {\n this.hide();\n }\n }\n\n hide() {\n this.$popover.hide();\n }\n}\n","import $ from 'jquery';\nimport env from '../core/env';\nimport lists from '../core/lists';\nimport dom from '../core/dom';\n\nexport default class TablePopover {\n constructor(context) {\n this.context = context;\n\n this.ui = $.summernote.ui;\n this.options = context.options;\n this.events = {\n 'summernote.mousedown': (we, e) => {\n this.update(e.target);\n },\n 'summernote.keyup summernote.scroll summernote.change': () => {\n this.update();\n },\n 'summernote.disable summernote.blur': () => {\n this.hide();\n },\n };\n }\n\n shouldInitialize() {\n return !lists.isEmpty(this.options.popover.table);\n }\n\n initialize() {\n this.$popover = this.ui.popover({\n className: 'note-table-popover',\n }).render().appendTo(this.options.container);\n const $content = this.$popover.find('.popover-content,.note-popover-content');\n\n this.context.invoke('buttons.build', $content, this.options.popover.table);\n\n // [workaround] Disable Firefox's default table editor\n if (env.isFF) {\n document.execCommand('enableInlineTableEditing', false, false);\n }\n\n this.$popover.on('mousedown', (e) => { e.preventDefault(); });\n }\n\n destroy() {\n this.$popover.remove();\n }\n\n update(target) {\n if (this.context.isDisabled()) {\n return false;\n }\n\n const isCell = dom.isCell(target);\n\n if (isCell) {\n const pos = dom.posFromPlaceholder(target);\n const containerOffset = $(this.options.container).offset();\n pos.top -= containerOffset.top;\n pos.left -= containerOffset.left;\n\n this.$popover.css({\n display: 'block',\n left: pos.left,\n top: pos.top,\n });\n } else {\n this.hide();\n }\n\n return isCell;\n }\n\n hide() {\n this.$popover.hide();\n }\n}\n","import $ from 'jquery';\nimport env from '../core/env';\nimport key from '../core/key';\n\nexport default class VideoDialog {\n constructor(context) {\n this.context = context;\n\n this.ui = $.summernote.ui;\n this.$body = $(document.body);\n this.$editor = context.layoutInfo.editor;\n this.options = context.options;\n this.lang = this.options.langInfo;\n }\n\n initialize() {\n const $container = this.options.dialogsInBody ? this.$body : this.options.container;\n const body = [\n '
',\n ``,\n ``,\n '
',\n ].join('');\n const buttonClass = 'btn btn-primary note-btn note-btn-primary note-video-btn';\n const footer = `
`;\n\n this.$dialog = this.ui.dialog({\n title: this.lang.video.insert,\n fade: this.options.dialogsFade,\n body: body,\n footer: footer,\n }).render().appendTo($container);\n }\n\n destroy() {\n this.ui.hideDialog(this.$dialog);\n this.$dialog.remove();\n }\n\n bindEnterKey($input, $btn) {\n $input.on('keypress', (event) => {\n if (event.keyCode === key.code.ENTER) {\n event.preventDefault();\n $btn.trigger('click');\n }\n });\n }\n\n createVideoNode(url) {\n // video url patterns(youtube, instagram, vimeo, dailymotion, youku, mp4, ogg, webm)\n const ytRegExp = /\\/\\/(?:(?:www|m)\\.)?(?:youtu\\.be\\/|youtube\\.com\\/(?:embed\\/|v\\/|watch\\?v=|watch\\?.+&v=))([\\w|-]{11})(?:(?:[\\?&]t=)(\\S+))?$/;\n const ytRegExpForStart = /^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/;\n const ytMatch = url.match(ytRegExp);\n\n const igRegExp = /(?:www\\.|\\/\\/)instagram\\.com\\/p\\/(.[a-zA-Z0-9_-]*)/;\n const igMatch = url.match(igRegExp);\n\n const vRegExp = /\\/\\/vine\\.co\\/v\\/([a-zA-Z0-9]+)/;\n const vMatch = url.match(vRegExp);\n\n const vimRegExp = /\\/\\/(player\\.)?vimeo\\.com\\/([a-z]*\\/)*(\\d+)[?]?.*/;\n const vimMatch = url.match(vimRegExp);\n\n const dmRegExp = /.+dailymotion.com\\/(video|hub)\\/([^_]+)[^#]*(#video=([^_&]+))?/;\n const dmMatch = url.match(dmRegExp);\n\n const youkuRegExp = /\\/\\/v\\.youku\\.com\\/v_show\\/id_(\\w+)=*\\.html/;\n const youkuMatch = url.match(youkuRegExp);\n\n const qqRegExp = /\\/\\/v\\.qq\\.com.*?vid=(.+)/;\n const qqMatch = url.match(qqRegExp);\n\n const qqRegExp2 = /\\/\\/v\\.qq\\.com\\/x?\\/?(page|cover).*?\\/([^\\/]+)\\.html\\??.*/;\n const qqMatch2 = url.match(qqRegExp2);\n\n const mp4RegExp = /^.+.(mp4|m4v)$/;\n const mp4Match = url.match(mp4RegExp);\n\n const oggRegExp = /^.+.(ogg|ogv)$/;\n const oggMatch = url.match(oggRegExp);\n\n const webmRegExp = /^.+.(webm)$/;\n const webmMatch = url.match(webmRegExp);\n\n const fbRegExp = /(?:www\\.|\\/\\/)facebook\\.com\\/([^\\/]+)\\/videos\\/([0-9]+)/;\n const fbMatch = url.match(fbRegExp);\n\n let $video;\n if (ytMatch && ytMatch[1].length === 11) {\n const youtubeId = ytMatch[1];\n var start = 0;\n if (typeof ytMatch[2] !== 'undefined') {\n const ytMatchForStart = ytMatch[2].match(ytRegExpForStart);\n if (ytMatchForStart) {\n for (var n = [3600, 60, 1], i = 0, r = n.length; i < r; i++) {\n start += (typeof ytMatchForStart[i + 1] !== 'undefined' ? n[i] * parseInt(ytMatchForStart[i + 1], 10) : 0);\n }\n }\n }\n $video = $('