Renderer.js 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  1. define([
  2. 'summernote/core/agent',
  3. 'summernote/core/dom',
  4. 'summernote/core/func',
  5. 'summernote/core/list'
  6. ], function (agent, dom, func, list) {
  7. /**
  8. * @class Renderer
  9. *
  10. * renderer
  11. *
  12. * rendering toolbar and editable
  13. */
  14. var Renderer = function () {
  15. /**
  16. * bootstrap button template
  17. * @private
  18. * @param {String} label button name
  19. * @param {Object} [options] button options
  20. * @param {String} [options.event] data-event
  21. * @param {String} [options.className] button's class name
  22. * @param {String} [options.value] data-value
  23. * @param {String} [options.title] button's title for popup
  24. * @param {String} [options.dropdown] dropdown html
  25. * @param {String} [options.hide] data-hide
  26. */
  27. var tplButton = function (label, options) {
  28. var event = options.event;
  29. var value = options.value;
  30. var title = options.title;
  31. var className = options.className;
  32. var dropdown = options.dropdown;
  33. var hide = options.hide;
  34. return (dropdown ? '<div class="btn-group' +
  35. (className ? ' ' + className : '') + '">' : '') +
  36. '<button type="button"' +
  37. ' class="btn btn-default btn-sm' +
  38. ((!dropdown && className) ? ' ' + className : '') +
  39. (dropdown ? ' dropdown-toggle' : '') +
  40. '"' +
  41. (dropdown ? ' data-toggle="dropdown"' : '') +
  42. (title ? ' title="' + title + '"' : '') +
  43. (event ? ' data-event="' + event + '"' : '') +
  44. (value ? ' data-value=\'' + value + '\'' : '') +
  45. (hide ? ' data-hide=\'' + hide + '\'' : '') +
  46. ' tabindex="-1">' +
  47. label +
  48. (dropdown ? ' <span class="caret"></span>' : '') +
  49. '</button>' +
  50. (dropdown || '') +
  51. (dropdown ? '</div>' : '');
  52. };
  53. /**
  54. * bootstrap icon button template
  55. * @private
  56. * @param {String} iconClassName
  57. * @param {Object} [options]
  58. * @param {String} [options.event]
  59. * @param {String} [options.value]
  60. * @param {String} [options.title]
  61. * @param {String} [options.dropdown]
  62. */
  63. var tplIconButton = function (iconClassName, options) {
  64. var label = '<i class="' + iconClassName + '"></i>';
  65. return tplButton(label, options);
  66. };
  67. /**
  68. * bootstrap popover template
  69. * @private
  70. * @param {String} className
  71. * @param {String} content
  72. */
  73. var tplPopover = function (className, content) {
  74. var $popover = $('<div class="' + className + ' popover bottom in" style="display: none;">' +
  75. '<div class="arrow"></div>' +
  76. '<div class="popover-content">' +
  77. '</div>' +
  78. '</div>');
  79. $popover.find('.popover-content').append(content);
  80. return $popover;
  81. };
  82. /**
  83. * bootstrap dialog template
  84. *
  85. * @param {String} className
  86. * @param {String} [title='']
  87. * @param {String} body
  88. * @param {String} [footer='']
  89. */
  90. var tplDialog = function (className, title, body, footer) {
  91. return '<div class="' + className + ' modal" aria-hidden="false">' +
  92. '<div class="modal-dialog">' +
  93. '<div class="modal-content">' +
  94. (title ?
  95. '<div class="modal-header">' +
  96. '<button type="button" class="close" aria-hidden="true" tabindex="-1">&times;</button>' +
  97. '<h4 class="modal-title">' + title + '</h4>' +
  98. '</div>' : ''
  99. ) +
  100. '<div class="modal-body">' + body + '</div>' +
  101. (footer ?
  102. '<div class="modal-footer">' + footer + '</div>' : ''
  103. ) +
  104. '</div>' +
  105. '</div>' +
  106. '</div>';
  107. };
  108. /**
  109. * bootstrap dropdown template
  110. *
  111. * @param {String|String[]} contents
  112. * @param {String} [className='']
  113. * @param {String} [nodeName='']
  114. */
  115. var tplDropdown = function (contents, className, nodeName) {
  116. var classes = 'dropdown-menu' + (className ? ' ' + className : '');
  117. nodeName = nodeName || 'ul';
  118. if (contents instanceof Array) {
  119. contents = contents.join('');
  120. }
  121. return '<' + nodeName + ' class="' + classes + '">' + contents + '</' + nodeName + '>';
  122. };
  123. var tplButtonInfo = {
  124. picture: function (lang, options) {
  125. return tplIconButton(options.iconPrefix + options.icons.image.image, {
  126. event: 'showImageDialog',
  127. title: lang.image.image,
  128. hide: true
  129. });
  130. },
  131. link: function (lang, options) {
  132. return tplIconButton(options.iconPrefix + options.icons.link.link, {
  133. event: 'showLinkDialog',
  134. title: lang.link.link,
  135. hide: true
  136. });
  137. },
  138. table: function (lang, options) {
  139. var dropdown = [
  140. '<div class="note-dimension-picker">',
  141. '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>',
  142. '<div class="note-dimension-picker-highlighted"></div>',
  143. '<div class="note-dimension-picker-unhighlighted"></div>',
  144. '</div>',
  145. '<div class="note-dimension-display"> 1 x 1 </div>'
  146. ];
  147. return tplIconButton(options.iconPrefix + options.icons.table.table, {
  148. title: lang.table.table,
  149. dropdown: tplDropdown(dropdown, 'note-table')
  150. });
  151. },
  152. style: function (lang, options) {
  153. var items = options.styleTags.reduce(function (memo, v) {
  154. var label = lang.style[v === 'p' ? 'normal' : v];
  155. return memo + '<li><a data-event="formatBlock" href="#" data-value="' + v + '">' +
  156. (
  157. (v === 'p' || v === 'pre') ? label :
  158. '<' + v + '>' + label + '</' + v + '>'
  159. ) +
  160. '</a></li>';
  161. }, '');
  162. return tplIconButton(options.iconPrefix + options.icons.style.style, {
  163. title: lang.style.style,
  164. dropdown: tplDropdown(items)
  165. });
  166. },
  167. fontname: function (lang, options) {
  168. var realFontList = [];
  169. var items = options.fontNames.reduce(function (memo, v) {
  170. if (!agent.isFontInstalled(v) && !list.contains(options.fontNamesIgnoreCheck, v)) {
  171. return memo;
  172. }
  173. realFontList.push(v);
  174. return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '" style="font-family:\'' + v + '\'">' +
  175. '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v +
  176. '</a></li>';
  177. }, '');
  178. var hasDefaultFont = agent.isFontInstalled(options.defaultFontName);
  179. var defaultFontName = (hasDefaultFont) ? options.defaultFontName : realFontList[0];
  180. var label = '<span class="note-current-fontname">' +
  181. defaultFontName +
  182. '</span>';
  183. return tplButton(label, {
  184. title: lang.font.name,
  185. className: 'note-fontname',
  186. dropdown: tplDropdown(items, 'note-check')
  187. });
  188. },
  189. fontsize: function (lang, options) {
  190. var items = options.fontSizes.reduce(function (memo, v) {
  191. return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' +
  192. '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v +
  193. '</a></li>';
  194. }, '');
  195. var label = '<span class="note-current-fontsize">11</span>';
  196. return tplButton(label, {
  197. title: lang.font.size,
  198. className: 'note-fontsize',
  199. dropdown: tplDropdown(items, 'note-check')
  200. });
  201. },
  202. color: function (lang, options) {
  203. var colorButtonLabel = '<i class="' +
  204. options.iconPrefix + options.icons.color.recent +
  205. '" style="color:black;background-color:yellow;"></i>';
  206. var colorButton = tplButton(colorButtonLabel, {
  207. className: 'note-recent-color',
  208. title: lang.color.recent,
  209. event: 'color',
  210. value: '{"backColor":"yellow"}'
  211. });
  212. var items = [
  213. '<li><div class="btn-group">',
  214. '<div class="note-palette-title">' + lang.color.background + '</div>',
  215. '<div class="note-color-reset" data-event="backColor"',
  216. ' data-value="inherit" title="' + lang.color.transparent + '">' + lang.color.setTransparent + '</div>',
  217. '<div class="note-color-palette" data-target-event="backColor"></div>',
  218. '</div><div class="btn-group">',
  219. '<div class="note-palette-title">' + lang.color.foreground + '</div>',
  220. '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">',
  221. lang.color.resetToDefault,
  222. '</div>',
  223. '<div class="note-color-palette" data-target-event="foreColor"></div>',
  224. '</div></li>'
  225. ];
  226. var moreButton = tplButton('', {
  227. title: lang.color.more,
  228. dropdown: tplDropdown(items)
  229. });
  230. return colorButton + moreButton;
  231. },
  232. bold: function (lang, options) {
  233. return tplIconButton(options.iconPrefix + options.icons.font.bold, {
  234. event: 'bold',
  235. title: lang.font.bold
  236. });
  237. },
  238. italic: function (lang, options) {
  239. return tplIconButton(options.iconPrefix + options.icons.font.italic, {
  240. event: 'italic',
  241. title: lang.font.italic
  242. });
  243. },
  244. underline: function (lang, options) {
  245. return tplIconButton(options.iconPrefix + options.icons.font.underline, {
  246. event: 'underline',
  247. title: lang.font.underline
  248. });
  249. },
  250. strikethrough: function (lang, options) {
  251. return tplIconButton(options.iconPrefix + options.icons.font.strikethrough, {
  252. event: 'strikethrough',
  253. title: lang.font.strikethrough
  254. });
  255. },
  256. superscript: function (lang, options) {
  257. return tplIconButton(options.iconPrefix + options.icons.font.superscript, {
  258. event: 'superscript',
  259. title: lang.font.superscript
  260. });
  261. },
  262. subscript: function (lang, options) {
  263. return tplIconButton(options.iconPrefix + options.icons.font.subscript, {
  264. event: 'subscript',
  265. title: lang.font.subscript
  266. });
  267. },
  268. clear: function (lang, options) {
  269. return tplIconButton(options.iconPrefix + options.icons.font.clear, {
  270. event: 'removeFormat',
  271. title: lang.font.clear
  272. });
  273. },
  274. ul: function (lang, options) {
  275. return tplIconButton(options.iconPrefix + options.icons.lists.unordered, {
  276. event: 'insertUnorderedList',
  277. title: lang.lists.unordered
  278. });
  279. },
  280. ol: function (lang, options) {
  281. return tplIconButton(options.iconPrefix + options.icons.lists.ordered, {
  282. event: 'insertOrderedList',
  283. title: lang.lists.ordered
  284. });
  285. },
  286. paragraph: function (lang, options) {
  287. var leftButton = tplIconButton(options.iconPrefix + options.icons.paragraph.left, {
  288. title: lang.paragraph.left,
  289. event: 'justifyLeft'
  290. });
  291. var centerButton = tplIconButton(options.iconPrefix + options.icons.paragraph.center, {
  292. title: lang.paragraph.center,
  293. event: 'justifyCenter'
  294. });
  295. var rightButton = tplIconButton(options.iconPrefix + options.icons.paragraph.right, {
  296. title: lang.paragraph.right,
  297. event: 'justifyRight'
  298. });
  299. var justifyButton = tplIconButton(options.iconPrefix + options.icons.paragraph.justify, {
  300. title: lang.paragraph.justify,
  301. event: 'justifyFull'
  302. });
  303. var outdentButton = tplIconButton(options.iconPrefix + options.icons.paragraph.outdent, {
  304. title: lang.paragraph.outdent,
  305. event: 'outdent'
  306. });
  307. var indentButton = tplIconButton(options.iconPrefix + options.icons.paragraph.indent, {
  308. title: lang.paragraph.indent,
  309. event: 'indent'
  310. });
  311. var dropdown = [
  312. '<div class="note-align btn-group">',
  313. leftButton + centerButton + rightButton + justifyButton,
  314. '</div><div class="note-list btn-group">',
  315. indentButton + outdentButton,
  316. '</div>'
  317. ];
  318. return tplIconButton(options.iconPrefix + options.icons.paragraph.paragraph, {
  319. title: lang.paragraph.paragraph,
  320. dropdown: tplDropdown(dropdown, '', 'div')
  321. });
  322. },
  323. height: function (lang, options) {
  324. var items = options.lineHeights.reduce(function (memo, v) {
  325. return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' +
  326. '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v +
  327. '</a></li>';
  328. }, '');
  329. return tplIconButton(options.iconPrefix + options.icons.font.height, {
  330. title: lang.font.height,
  331. dropdown: tplDropdown(items, 'note-check')
  332. });
  333. },
  334. help: function (lang, options) {
  335. return tplIconButton(options.iconPrefix + options.icons.options.help, {
  336. event: 'showHelpDialog',
  337. title: lang.options.help,
  338. hide: true
  339. });
  340. },
  341. fullscreen: function (lang, options) {
  342. return tplIconButton(options.iconPrefix + options.icons.options.fullscreen, {
  343. event: 'fullscreen',
  344. title: lang.options.fullscreen
  345. });
  346. },
  347. codeview: function (lang, options) {
  348. return tplIconButton(options.iconPrefix + options.icons.options.codeview, {
  349. event: 'codeview',
  350. title: lang.options.codeview
  351. });
  352. },
  353. undo: function (lang, options) {
  354. return tplIconButton(options.iconPrefix + options.icons.history.undo, {
  355. event: 'undo',
  356. title: lang.history.undo
  357. });
  358. },
  359. redo: function (lang, options) {
  360. return tplIconButton(options.iconPrefix + options.icons.history.redo, {
  361. event: 'redo',
  362. title: lang.history.redo
  363. });
  364. },
  365. hr: function (lang, options) {
  366. return tplIconButton(options.iconPrefix + options.icons.hr.insert, {
  367. event: 'insertHorizontalRule',
  368. title: lang.hr.insert
  369. });
  370. }
  371. };
  372. var tplPopovers = function (lang, options) {
  373. var tplLinkPopover = function () {
  374. var linkButton = tplIconButton(options.iconPrefix + options.icons.link.edit, {
  375. title: lang.link.edit,
  376. event: 'showLinkDialog',
  377. hide: true
  378. });
  379. var unlinkButton = tplIconButton(options.iconPrefix + options.icons.link.unlink, {
  380. title: lang.link.unlink,
  381. event: 'unlink'
  382. });
  383. var content = '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
  384. '<div class="note-insert btn-group">' +
  385. linkButton + unlinkButton +
  386. '</div>';
  387. return tplPopover('note-link-popover', content);
  388. };
  389. var tplImagePopover = function () {
  390. var fullButton = tplButton('<span class="note-fontsize-10">100%</span>', {
  391. title: lang.image.resizeFull,
  392. event: 'resize',
  393. value: '1'
  394. });
  395. var halfButton = tplButton('<span class="note-fontsize-10">50%</span>', {
  396. title: lang.image.resizeHalf,
  397. event: 'resize',
  398. value: '0.5'
  399. });
  400. var quarterButton = tplButton('<span class="note-fontsize-10">25%</span>', {
  401. title: lang.image.resizeQuarter,
  402. event: 'resize',
  403. value: '0.25'
  404. });
  405. var leftButton = tplIconButton(options.iconPrefix + options.icons.image.floatLeft, {
  406. title: lang.image.floatLeft,
  407. event: 'floatMe',
  408. value: 'left'
  409. });
  410. var rightButton = tplIconButton(options.iconPrefix + options.icons.image.floatRight, {
  411. title: lang.image.floatRight,
  412. event: 'floatMe',
  413. value: 'right'
  414. });
  415. var justifyButton = tplIconButton(options.iconPrefix + options.icons.image.floatNone, {
  416. title: lang.image.floatNone,
  417. event: 'floatMe',
  418. value: 'none'
  419. });
  420. var roundedButton = tplIconButton(options.iconPrefix + options.icons.image.shapeRounded, {
  421. title: lang.image.shapeRounded,
  422. event: 'imageShape',
  423. value: 'img-rounded'
  424. });
  425. var circleButton = tplIconButton(options.iconPrefix + options.icons.image.shapeCircle, {
  426. title: lang.image.shapeCircle,
  427. event: 'imageShape',
  428. value: 'img-circle'
  429. });
  430. var thumbnailButton = tplIconButton(options.iconPrefix + options.icons.image.shapeThumbnail, {
  431. title: lang.image.shapeThumbnail,
  432. event: 'imageShape',
  433. value: 'img-thumbnail'
  434. });
  435. var noneButton = tplIconButton(options.iconPrefix + options.icons.image.shapeNone, {
  436. title: lang.image.shapeNone,
  437. event: 'imageShape',
  438. value: ''
  439. });
  440. var removeButton = tplIconButton(options.iconPrefix + options.icons.image.remove, {
  441. title: lang.image.remove,
  442. event: 'removeMedia',
  443. value: 'none'
  444. });
  445. var content = (options.disableResizeImage ? '' : '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>') +
  446. '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div><br>' +
  447. '<div class="btn-group">' + roundedButton + circleButton + thumbnailButton + noneButton + '</div>' +
  448. '<div class="btn-group">' + removeButton + '</div>';
  449. return tplPopover('note-image-popover', content);
  450. };
  451. var tplAirPopover = function () {
  452. var $content = $('<div />');
  453. for (var idx = 0, len = options.airPopover.length; idx < len; idx ++) {
  454. var group = options.airPopover[idx];
  455. var $group = $('<div class="note-' + group[0] + ' btn-group">');
  456. for (var i = 0, lenGroup = group[1].length; i < lenGroup; i++) {
  457. var $button = $(tplButtonInfo[group[1][i]](lang, options));
  458. $button.attr('data-name', group[1][i]);
  459. $group.append($button);
  460. }
  461. $content.append($group);
  462. }
  463. return tplPopover('note-air-popover', $content.children());
  464. };
  465. var $notePopover = $('<div class="note-popover" />');
  466. $notePopover.append(tplLinkPopover());
  467. $notePopover.append(tplImagePopover());
  468. if (options.airMode) {
  469. $notePopover.append(tplAirPopover());
  470. }
  471. return $notePopover;
  472. };
  473. var tplHandles = function (options) {
  474. return '<div class="note-handle">' +
  475. '<div class="note-control-selection">' +
  476. '<div class="note-control-selection-bg"></div>' +
  477. '<div class="note-control-holder note-control-nw"></div>' +
  478. '<div class="note-control-holder note-control-ne"></div>' +
  479. '<div class="note-control-holder note-control-sw"></div>' +
  480. '<div class="' +
  481. (options.disableResizeImage ? 'note-control-holder' : 'note-control-sizing') +
  482. ' note-control-se"></div>' +
  483. (options.disableResizeImage ? '' : '<div class="note-control-selection-info"></div>') +
  484. '</div>' +
  485. '</div>';
  486. };
  487. /**
  488. * shortcut table template
  489. * @param {String} title
  490. * @param {String} body
  491. */
  492. var tplShortcut = function (title, keys) {
  493. var keyClass = 'note-shortcut-col col-xs-6 note-shortcut-';
  494. var body = [];
  495. for (var i in keys) {
  496. if (keys.hasOwnProperty(i)) {
  497. body.push(
  498. '<div class="' + keyClass + 'key">' + keys[i].kbd + '</div>' +
  499. '<div class="' + keyClass + 'name">' + keys[i].text + '</div>'
  500. );
  501. }
  502. }
  503. return '<div class="note-shortcut-row row"><div class="' + keyClass + 'title col-xs-offset-6">' + title + '</div></div>' +
  504. '<div class="note-shortcut-row row">' + body.join('</div><div class="note-shortcut-row row">') + '</div>';
  505. };
  506. var tplShortcutText = function (lang) {
  507. var keys = [
  508. { kbd: '⌘ + B', text: lang.font.bold },
  509. { kbd: '⌘ + I', text: lang.font.italic },
  510. { kbd: '⌘ + U', text: lang.font.underline },
  511. { kbd: '⌘ + \\', text: lang.font.clear }
  512. ];
  513. return tplShortcut(lang.shortcut.textFormatting, keys);
  514. };
  515. var tplShortcutAction = function (lang) {
  516. var keys = [
  517. { kbd: '⌘ + Z', text: lang.history.undo },
  518. { kbd: '⌘ + ⇧ + Z', text: lang.history.redo },
  519. { kbd: '⌘ + ]', text: lang.paragraph.indent },
  520. { kbd: '⌘ + [', text: lang.paragraph.outdent },
  521. { kbd: '⌘ + ENTER', text: lang.hr.insert }
  522. ];
  523. return tplShortcut(lang.shortcut.action, keys);
  524. };
  525. var tplShortcutPara = function (lang) {
  526. var keys = [
  527. { kbd: '⌘ + ⇧ + L', text: lang.paragraph.left },
  528. { kbd: '⌘ + ⇧ + E', text: lang.paragraph.center },
  529. { kbd: '⌘ + ⇧ + R', text: lang.paragraph.right },
  530. { kbd: '⌘ + ⇧ + J', text: lang.paragraph.justify },
  531. { kbd: '⌘ + ⇧ + NUM7', text: lang.lists.ordered },
  532. { kbd: '⌘ + ⇧ + NUM8', text: lang.lists.unordered }
  533. ];
  534. return tplShortcut(lang.shortcut.paragraphFormatting, keys);
  535. };
  536. var tplShortcutStyle = function (lang) {
  537. var keys = [
  538. { kbd: '⌘ + NUM0', text: lang.style.normal },
  539. { kbd: '⌘ + NUM1', text: lang.style.h1 },
  540. { kbd: '⌘ + NUM2', text: lang.style.h2 },
  541. { kbd: '⌘ + NUM3', text: lang.style.h3 },
  542. { kbd: '⌘ + NUM4', text: lang.style.h4 },
  543. { kbd: '⌘ + NUM5', text: lang.style.h5 },
  544. { kbd: '⌘ + NUM6', text: lang.style.h6 }
  545. ];
  546. return tplShortcut(lang.shortcut.documentStyle, keys);
  547. };
  548. var tplExtraShortcuts = function (lang, options) {
  549. var extraKeys = options.extraKeys;
  550. var keys = [];
  551. for (var key in extraKeys) {
  552. if (extraKeys.hasOwnProperty(key)) {
  553. keys.push({ kbd: key, text: extraKeys[key] });
  554. }
  555. }
  556. return tplShortcut(lang.shortcut.extraKeys, keys);
  557. };
  558. var tplShortcutTable = function (lang, options) {
  559. var colClass = 'class="note-shortcut note-shortcut-col col-sm-6 col-xs-12"';
  560. var template = [
  561. '<div ' + colClass + '>' + tplShortcutAction(lang, options) + '</div>' +
  562. '<div ' + colClass + '>' + tplShortcutText(lang, options) + '</div>',
  563. '<div ' + colClass + '>' + tplShortcutStyle(lang, options) + '</div>' +
  564. '<div ' + colClass + '>' + tplShortcutPara(lang, options) + '</div>'
  565. ];
  566. if (options.extraKeys) {
  567. template.push('<div ' + colClass + '>' + tplExtraShortcuts(lang, options) + '</div>');
  568. }
  569. return '<div class="note-shortcut-row row">' +
  570. template.join('</div><div class="note-shortcut-row row">') +
  571. '</div>';
  572. };
  573. var replaceMacKeys = function (sHtml) {
  574. return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
  575. };
  576. var tplDialogInfo = {
  577. image: function (lang, options) {
  578. var imageLimitation = '';
  579. if (options.maximumImageFileSize) {
  580. var unit = Math.floor(Math.log(options.maximumImageFileSize) / Math.log(1024));
  581. var readableSize = (options.maximumImageFileSize / Math.pow(1024, unit)).toFixed(2) * 1 +
  582. ' ' + ' KMGTP'[unit] + 'B';
  583. imageLimitation = '<small>' + lang.image.maximumFileSize + ' : ' + readableSize + '</small>';
  584. }
  585. var body = '<div class="form-group row note-group-select-from-files">' +
  586. '<label>' + lang.image.selectFromFiles + '</label>' +
  587. '<input class="note-image-input form-control" type="file" name="files" accept="image/*" multiple="multiple" />' +
  588. imageLimitation +
  589. '</div>' +
  590. '<div class="form-group row">' +
  591. '<label>' + lang.image.url + '</label>' +
  592. '<input class="note-image-url form-control col-md-12" type="text" />' +
  593. '</div>';
  594. var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
  595. return tplDialog('note-image-dialog', lang.image.insert, body, footer);
  596. },
  597. link: function (lang, options) {
  598. var body = '<div class="form-group row">' +
  599. '<label>' + lang.link.textToDisplay + '</label>' +
  600. '<input class="note-link-text form-control col-md-12" type="text" />' +
  601. '</div>' +
  602. '<div class="form-group row">' +
  603. '<label>' + lang.link.url + '</label>' +
  604. '<input class="note-link-url form-control col-md-12" type="text" value="http://" />' +
  605. '</div>' +
  606. (!options.disableLinkTarget ?
  607. '<div class="checkbox">' +
  608. '<label>' + '<input type="checkbox" checked> ' +
  609. lang.link.openInNewWindow +
  610. '</label>' +
  611. '</div>' : ''
  612. );
  613. var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
  614. return tplDialog('note-link-dialog', lang.link.insert, body, footer);
  615. },
  616. help: function (lang, options) {
  617. var body = '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">' + lang.shortcut.close + '</a>' +
  618. '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
  619. (agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
  620. '<p class="text-center">' +
  621. '<a href="//summernote.org/" target="_blank">Summernote @VERSION</a> · ' +
  622. '<a href="//github.com/summernote/summernote" target="_blank">Project</a> · ' +
  623. '<a href="//github.com/summernote/summernote/issues" target="_blank">Issues</a>' +
  624. '</p>';
  625. return tplDialog('note-help-dialog', '', body, '');
  626. }
  627. };
  628. var tplDialogs = function (lang, options) {
  629. var dialogs = '';
  630. $.each(tplDialogInfo, function (idx, tplDialog) {
  631. dialogs += tplDialog(lang, options);
  632. });
  633. return '<div class="note-dialog">' + dialogs + '</div>';
  634. };
  635. var tplStatusbar = function () {
  636. return '<div class="note-resizebar">' +
  637. '<div class="note-icon-bar"></div>' +
  638. '<div class="note-icon-bar"></div>' +
  639. '<div class="note-icon-bar"></div>' +
  640. '</div>';
  641. };
  642. var representShortcut = function (str) {
  643. if (agent.isMac) {
  644. str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
  645. }
  646. return str.replace('BACKSLASH', '\\')
  647. .replace('SLASH', '/')
  648. .replace('LEFTBRACKET', '[')
  649. .replace('RIGHTBRACKET', ']');
  650. };
  651. /**
  652. * createTooltip
  653. *
  654. * @param {jQuery} $container
  655. * @param {Object} keyMap
  656. * @param {String} [sPlacement]
  657. */
  658. var createTooltip = function ($container, keyMap, sPlacement) {
  659. var invertedKeyMap = func.invertObject(keyMap);
  660. var $buttons = $container.find('button');
  661. $buttons.each(function (i, elBtn) {
  662. var $btn = $(elBtn);
  663. var sShortcut = invertedKeyMap[$btn.data('event')];
  664. if (sShortcut) {
  665. $btn.attr('title', function (i, v) {
  666. return v + ' (' + representShortcut(sShortcut) + ')';
  667. });
  668. }
  669. // bootstrap tooltip on btn-group bug
  670. // https://github.com/twbs/bootstrap/issues/5687
  671. }).tooltip({
  672. container: 'body',
  673. trigger: 'hover',
  674. placement: sPlacement || 'top'
  675. }).on('click', function () {
  676. $(this).tooltip('hide');
  677. });
  678. };
  679. // createPalette
  680. var createPalette = function ($container, options) {
  681. var colorInfo = options.colors;
  682. $container.find('.note-color-palette').each(function () {
  683. var $palette = $(this), eventName = $palette.attr('data-target-event');
  684. var paletteContents = [];
  685. for (var row = 0, lenRow = colorInfo.length; row < lenRow; row++) {
  686. var colors = colorInfo[row];
  687. var buttons = [];
  688. for (var col = 0, lenCol = colors.length; col < lenCol; col++) {
  689. var color = colors[col];
  690. buttons.push(['<button type="button" class="note-color-btn" style="background-color:', color,
  691. ';" data-event="', eventName,
  692. '" data-value="', color,
  693. '" title="', color,
  694. '" data-toggle="button" tabindex="-1"></button>'].join(''));
  695. }
  696. paletteContents.push('<div class="note-color-row">' + buttons.join('') + '</div>');
  697. }
  698. $palette.html(paletteContents.join(''));
  699. });
  700. };
  701. /**
  702. * create summernote layout (air mode)
  703. *
  704. * @param {jQuery} $holder
  705. * @param {Object} options
  706. */
  707. this.createLayoutByAirMode = function ($holder, options) {
  708. var langInfo = options.langInfo;
  709. var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
  710. var id = func.uniqueId();
  711. $holder.addClass('note-air-editor note-editable panel-body');
  712. $holder.attr({
  713. 'id': 'note-editor-' + id,
  714. 'contentEditable': true
  715. });
  716. var body = document.body;
  717. // create Popover
  718. var $popover = $(tplPopovers(langInfo, options));
  719. $popover.addClass('note-air-layout');
  720. $popover.attr('id', 'note-popover-' + id);
  721. $popover.appendTo(body);
  722. createTooltip($popover, keyMap);
  723. createPalette($popover, options);
  724. // create Handle
  725. var $handle = $(tplHandles(options));
  726. $handle.addClass('note-air-layout');
  727. $handle.attr('id', 'note-handle-' + id);
  728. $handle.appendTo(body);
  729. // create Dialog
  730. var $dialog = $(tplDialogs(langInfo, options));
  731. $dialog.addClass('note-air-layout');
  732. $dialog.attr('id', 'note-dialog-' + id);
  733. $dialog.find('button.close, a.modal-close').click(function () {
  734. $(this).closest('.modal').modal('hide');
  735. });
  736. $dialog.appendTo(body);
  737. };
  738. /**
  739. * create summernote layout (normal mode)
  740. *
  741. * @param {jQuery} $holder
  742. * @param {Object} options
  743. */
  744. this.createLayoutByFrame = function ($holder, options) {
  745. var langInfo = options.langInfo;
  746. //01. create Editor
  747. var $editor = $('<div class="note-editor panel panel-default" />');
  748. if (options.width) {
  749. $editor.width(options.width);
  750. }
  751. //02. statusbar (resizebar)
  752. if (options.height > 0) {
  753. $('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor);
  754. }
  755. //03 editing area
  756. var $editingArea = $('<div class="note-editing-area" />');
  757. //03. create editable
  758. var isContentEditable = !$holder.is(':disabled');
  759. var $editable = $('<div class="note-editable panel-body" contentEditable="' + isContentEditable + '"></div>').prependTo($editingArea);
  760. if (options.height) {
  761. $editable.height(options.height);
  762. }
  763. if (options.direction) {
  764. $editable.attr('dir', options.direction);
  765. }
  766. var placeholder = $holder.attr('placeholder') || options.placeholder;
  767. if (placeholder) {
  768. $editable.attr('data-placeholder', placeholder);
  769. }
  770. $editable.html(dom.html($holder) || dom.emptyPara);
  771. //031. create codable
  772. $('<textarea class="note-codable"></textarea>').prependTo($editingArea);
  773. //04. create Popover
  774. var $popover = $(tplPopovers(langInfo, options)).prependTo($editingArea);
  775. createPalette($popover, options);
  776. createTooltip($popover, keyMap);
  777. //05. handle(control selection, ...)
  778. $(tplHandles(options)).prependTo($editingArea);
  779. $editingArea.prependTo($editor);
  780. //06. create Toolbar
  781. var $toolbar = $('<div class="note-toolbar panel-heading" />');
  782. for (var idx = 0, len = options.toolbar.length; idx < len; idx ++) {
  783. var groupName = options.toolbar[idx][0];
  784. var groupButtons = options.toolbar[idx][1];
  785. var $group = $('<div class="note-' + groupName + ' btn-group" />');
  786. for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
  787. var buttonInfo = tplButtonInfo[groupButtons[i]];
  788. // continue creating toolbar even if a button doesn't exist
  789. if (!$.isFunction(buttonInfo)) { continue; }
  790. var $button = $(buttonInfo(langInfo, options));
  791. $button.attr('data-name', groupButtons[i]); // set button's alias, becuase to get button element from $toolbar
  792. $group.append($button);
  793. }
  794. $toolbar.append($group);
  795. }
  796. var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
  797. createPalette($toolbar, options);
  798. createTooltip($toolbar, keyMap, 'bottom');
  799. $toolbar.prependTo($editor);
  800. //07. create Dropzone
  801. $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
  802. //08. create Dialog
  803. var $dialogContainer = options.dialogsInBody ? $(document.body) : $editor;
  804. var $dialog = $(tplDialogs(langInfo, options)).prependTo($dialogContainer);
  805. $dialog.find('button.close, a.modal-close').click(function () {
  806. $(this).closest('.modal').modal('hide');
  807. });
  808. //09. Editor/Holder switch
  809. $editor.insertAfter($holder);
  810. $holder.hide();
  811. };
  812. this.hasNoteEditor = function ($holder) {
  813. return this.noteEditorFromHolder($holder).length > 0;
  814. };
  815. this.noteEditorFromHolder = function ($holder) {
  816. if ($holder.hasClass('note-air-editor')) {
  817. return $holder;
  818. } else if ($holder.next().hasClass('note-editor')) {
  819. return $holder.next();
  820. } else {
  821. return $();
  822. }
  823. };
  824. /**
  825. * create summernote layout
  826. *
  827. * @param {jQuery} $holder
  828. * @param {Object} options
  829. */
  830. this.createLayout = function ($holder, options) {
  831. if (options.airMode) {
  832. this.createLayoutByAirMode($holder, options);
  833. } else {
  834. this.createLayoutByFrame($holder, options);
  835. }
  836. };
  837. /**
  838. * returns layoutInfo from holder
  839. *
  840. * @param {jQuery} $holder - placeholder
  841. * @return {Object}
  842. */
  843. this.layoutInfoFromHolder = function ($holder) {
  844. var $editor = this.noteEditorFromHolder($holder);
  845. if (!$editor.length) {
  846. return;
  847. }
  848. // connect $holder to $editor
  849. $editor.data('holder', $holder);
  850. return dom.buildLayoutInfo($editor);
  851. };
  852. /**
  853. * removeLayout
  854. *
  855. * @param {jQuery} $holder - placeholder
  856. * @param {Object} layoutInfo
  857. * @param {Object} options
  858. *
  859. */
  860. this.removeLayout = function ($holder, layoutInfo, options) {
  861. if (options.airMode) {
  862. $holder.removeClass('note-air-editor note-editable')
  863. .removeAttr('id contentEditable');
  864. layoutInfo.popover().remove();
  865. layoutInfo.handle().remove();
  866. layoutInfo.dialog().remove();
  867. } else {
  868. $holder.html(layoutInfo.editable().html());
  869. if (options.dialogsInBody) {
  870. layoutInfo.dialog().remove();
  871. }
  872. layoutInfo.editor().remove();
  873. $holder.show();
  874. }
  875. };
  876. /**
  877. *
  878. * @return {Object}
  879. * @return {function(label, options=):string} return.button {@link #tplButton function to make text button}
  880. * @return {function(iconClass, options=):string} return.iconButton {@link #tplIconButton function to make icon button}
  881. * @return {function(className, title=, body=, footer=):string} return.dialog {@link #tplDialog function to make dialog}
  882. */
  883. this.getTemplate = function () {
  884. return {
  885. button: tplButton,
  886. iconButton: tplIconButton,
  887. dialog: tplDialog
  888. };
  889. };
  890. /**
  891. * add button information
  892. *
  893. * @param {String} name button name
  894. * @param {Function} buttonInfo function to make button, reference to {@link #tplButton},{@link #tplIconButton}
  895. */
  896. this.addButtonInfo = function (name, buttonInfo) {
  897. tplButtonInfo[name] = buttonInfo;
  898. };
  899. /**
  900. *
  901. * @param {String} name
  902. * @param {Function} dialogInfo function to make dialog, reference to {@link #tplDialog}
  903. */
  904. this.addDialogInfo = function (name, dialogInfo) {
  905. tplDialogInfo[name] = dialogInfo;
  906. };
  907. };
  908. return Renderer;
  909. });