HtmlEditor.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. Ext.define('saas.override.form.field.HtmlEditor', {
  2. override: 'Ext.form.field.HtmlEditor',
  3. fontFamilies: [{
  4. name: '微软雅黑',
  5. value: 'Microsoft YaHei'
  6. }, {
  7. name: '宋体',
  8. value: 'SimSun'
  9. }, {
  10. name: '新宋体',
  11. value: 'NSimSun'
  12. }, {
  13. name: '仿宋',
  14. value: 'FangSong'
  15. }, {
  16. name: '楷体',
  17. value: 'KaiTi'
  18. }, {
  19. name: '黑体',
  20. value: 'SimHei'
  21. }, 'Arial', 'Arial Black', 'Courier New', 'Tahoma', 'Times New Roman', 'Verdana'],
  22. headers: [{
  23. block: false,
  24. name: '普通文本',
  25. size: '14px',
  26. color: 'rgb(57, 57, 57)'
  27. }, {
  28. block: true,
  29. name: '标题',
  30. size: '32px',
  31. color: 'rgb(57, 57, 57)',
  32. bold: true
  33. }, {
  34. block: true,
  35. name: '副标题',
  36. size: '18px',
  37. color: 'rgb(89, 89, 89)',
  38. bold: true
  39. }, {
  40. block: true,
  41. name: '标题1',
  42. size: '28px',
  43. color: 'rgb(57, 57, 57)',
  44. bold: true
  45. }, {
  46. block: true,
  47. name: '标题2',
  48. size: '20px',
  49. color: 'rgb(57, 57, 57)',
  50. bold: true
  51. }, {
  52. block: true,
  53. name: '标题3',
  54. size: '16px',
  55. color: 'rgb(57, 57, 57)',
  56. bold: true
  57. }, {
  58. block: true,
  59. name: '标题4',
  60. size: '14px',
  61. color: 'rgb(57, 57, 57)',
  62. bold: true
  63. }],
  64. blocks: [{
  65. class: 'doc-block',
  66. name: '带背景内容'
  67. }, {
  68. class: 'doc-tip doc-tip-info',
  69. name: '提示内容'
  70. }, {
  71. class: 'doc-tip doc-tip-warn',
  72. name: '警告内容'
  73. }, {
  74. class: 'doc-tip doc-tip-error',
  75. name: '错误内容'
  76. }, {
  77. class: 'doc-quote',
  78. name: '引用内容'
  79. }, {
  80. class: 'doc-line',
  81. html: '<div class="doc-line"></div>'
  82. }],
  83. enableHeaders: true,
  84. enableFontSize: false,
  85. enableUpload: true,
  86. enableSourceEdit: false,
  87. enableBlocks: true,
  88. publishes: {
  89. textValue: 1
  90. },
  91. initComponent: function () {
  92. var me = this;
  93. me.callParent();
  94. // 用于获取innerText
  95. me.on('change', function () {
  96. me.publishState('textValue', me.getTextValue());
  97. });
  98. },
  99. initEditor: function () {
  100. var me = this;
  101. me.callParent();
  102. /**
  103. * 扩展监听事件
  104. */
  105. var doc = me.getDoc(), docEl = Ext.get(doc);
  106. if (docEl) {
  107. docEl.on({
  108. mousedown: '_onMouseDown',
  109. keyup: '_onKeyup',
  110. scope: me,
  111. delegated: false
  112. });
  113. }
  114. },
  115. _onMouseDown: function (e) {
  116. var target = e.parentEvent.target;
  117. if (target.nodeName == 'IMG') {
  118. e.preventDefault();
  119. this.resizeImage(target);
  120. }
  121. },
  122. _onKeyup: function (e) {
  123. if (e.keyCode == e.ENTER && e.ctrlKey) {
  124. e.preventDefault();
  125. // 换行
  126. // this.insertAtCursor('<br>');
  127. this.execCmd('insertHTML', '<div><br></div>');
  128. }
  129. },
  130. getDocMarkup: function () {
  131. // 添加额外样式、脚本
  132. var me = this,
  133. h = me.iframeEl.getHeight() - me.iframePad * 2,
  134. extraCss = Ext.getResourcePath('css/htmleditor.css');
  135. return Ext.String.format('<!DOCTYPE html>' + '<html><head><link type="text/css" rel="stylesheet" href="' + extraCss + '"></link><style type="text/css">' + (Ext.isOpera || Ext.isIE ? 'p{margin:0;}' : '') + 'body{border:0;margin:0;padding:{0}px;direction:' + (me.rtl ? 'rtl;' : 'ltr;') + (Ext.isIE8 ? Ext.emptyString : 'min-') + 'height:{1}px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;cursor:text;background-color:white;' + (Ext.isIE ? '' : 'font-size:14px;font-family:{2}') + '}</style></head><body></body></html>', me.iframePad, h, me.defaultFont);
  136. },
  137. initDefaultFont: function () {
  138. var me = this,
  139. selIdx = 0,
  140. fonts, font, select, option, i, len, fontName, fontValue;
  141. if (!me.defaultFont) {
  142. fonts = Ext.Array.clone(me.fontFamilies);
  143. select = me.down('#fontSelect').selectEl.dom;
  144. for (i = 0, len = fonts.length; i < len; ++i) {
  145. font = fonts[i];
  146. if (Ext.isString(font)) {
  147. fontName = font;
  148. fontValue = font.toLowerCase();
  149. } else {
  150. fontName = font.name;
  151. fontValue = font.value.toLowerCase();
  152. }
  153. option = new Option(fontName, fontValue);
  154. if (i == selIdx) {
  155. me.defaultFont = fontName;
  156. }
  157. option.style.fontFamily = fontValue;
  158. if (Ext.isIE) {
  159. select.add(option);
  160. } else {
  161. select.options.add(option);
  162. }
  163. }
  164. // Old IE versions have a problem if we set the selected property
  165. // in the loop, so set it after.
  166. select.options[selIdx].selected = true;
  167. }
  168. },
  169. getToolbarCfg: function () {
  170. var me = this, cfg = me.callParent();
  171. // 选择标题样式
  172. if (me.enableHeaders) {
  173. cfg.items.splice(0, 0, '-');
  174. cfg.items.splice(0, 0, me.getHeadersBtn());
  175. }
  176. // 上传图片
  177. if (me.enableUpload) {
  178. cfg.items.push('-', Ext.merge({
  179. xtype: 'fileuploadfield',
  180. name: 'file',
  181. allowBlank: false,
  182. buttonOnly: true,
  183. hideLabel: true,
  184. itemId: 'insertImage',
  185. buttonText: '',
  186. ui: 'plain',
  187. margin: 0,
  188. buttonConfig: {
  189. ui: 'plain-toolbar',
  190. cls: 'x-btn-icon',
  191. iconCls: 'x-fa fa-upload',
  192. accept: 'image/*'
  193. },
  194. scope: me,
  195. listeners: {
  196. change: function (field) {
  197. if (!field.getValue()) {
  198. return;
  199. }
  200. var fileEl = field.fileInputEl.dom;
  201. me.saveImage(fileEl.files[0]);
  202. field.reset();
  203. }
  204. },
  205. clickEvent: 'mousedown',
  206. tooltip: '插入图片',
  207. tabIndex: -1
  208. }, me.buttonDefaults));
  209. cfg.listeners.click = function (e) {
  210. if (!e.getTarget('.x-form-file-input')) {
  211. e.preventDefault();
  212. }
  213. }
  214. }
  215. // 自定义语句块
  216. if (me.enableBlocks) {
  217. cfg.items.push('-', Ext.merge(me.getBlocks(), me.buttonDefaults));
  218. }
  219. return cfg;
  220. },
  221. /**
  222. * 保存图片方式一 - 服务端保存
  223. * @param {*} file
  224. */
  225. saveImage: function (file) {
  226. var me = this, data = new FormData();
  227. data.append('file', file);
  228. Ext.Ajax.request({
  229. url: '/api/file/upload',
  230. cors: true,
  231. useDefaultXhrHeader: false,
  232. method: 'post',
  233. headers: {
  234. 'Content-Type': null
  235. },
  236. rawData: data,
  237. success: function (response) {
  238. var res = Ext.decode(response.responseText);
  239. if (res.success) {
  240. if (!me.activated) {
  241. me.onFirstFocus();
  242. }
  243. me.insertAtCursor('<img src="' + Ext.Ajax.getServerBasePath() + '/api/file/download?path=' + res.data.fullPath + '" alt />');
  244. me.syncValue();
  245. } else {
  246. saas.util.BaseUtil.showErrorToast('上传失败: ' + res.message);
  247. }
  248. },
  249. failure: function () {
  250. saas.util.BaseUtil.showErrorToast('上传失败');
  251. }
  252. });
  253. },
  254. /**
  255. * 保存图片方式二 - base64格式
  256. * 这样保证文档删除时附件一并删除
  257. * 截屏直接粘贴的图片均为base64格式
  258. * @param {*} file
  259. */
  260. saveBase64Image: function (file) {
  261. var me = this, reader = new FileReader();
  262. reader.readAsDataURL(file);
  263. reader.onload = function () {
  264. if (!me.activated) {
  265. me.onFirstFocus();
  266. }
  267. me.insertAtCursor('<img src="' + this.result + '" alt />');
  268. me.syncValue();
  269. }
  270. },
  271. /**
  272. * 选择标题
  273. */
  274. getHeadersBtn: function () {
  275. var me = this,
  276. items = me.headers.map(h => {
  277. var style = `color:${h.color};font-size:${h.size};font-weight:`
  278. + (h.bold ? 'bold' : 'normal'),
  279. text = `<span style="${style}">${h.name}</span>`,
  280. value = Ext.clone(h);
  281. return {
  282. text: text,
  283. value,
  284. handler: function (item) {
  285. var val = item.value;
  286. me.execHeaderStyle(val);
  287. item.ownerCt.ownerCmp.setText(val.name);
  288. }
  289. }
  290. });
  291. return {
  292. itemId: 'headers',
  293. width: 90,
  294. padding: '7 0',
  295. text: items[0].text,
  296. tabIndex: -1,
  297. menu: Ext.widget('menu', {
  298. plain: true,
  299. items
  300. })
  301. };
  302. },
  303. /**
  304. * 选择语句块
  305. */
  306. getBlocks: function () {
  307. var me = this,
  308. items = me.blocks.map(h => {
  309. var text = h.html || `<div class="${h.class}"><pre>${h.name}</pre></div>`,
  310. value = Ext.clone(h);
  311. return {
  312. text: text,
  313. value,
  314. handler: function (item) {
  315. var val = item.value;
  316. me.execBlockStyle(val);
  317. }
  318. }
  319. });
  320. return {
  321. itemId: 'blocks',
  322. iconCls: 'x-fa fa-ellipsis-v',
  323. tooltip: '插入块',
  324. tabIndex: -1,
  325. arrowVisible: false,
  326. menuAlign: 'tr-br?',
  327. menu: Ext.widget('menu', {
  328. plain: true,
  329. cls: 'doc-blocks__ex',
  330. width: 180,
  331. items
  332. })
  333. };
  334. },
  335. /**
  336. * 解决fontSize只支持 1 - 7 的问题
  337. */
  338. execHeaderStyle: function (header) {
  339. var me = this,
  340. win = me.getWin(),
  341. doc = me.getDoc(),
  342. text = win.getSelection() || me.defaultValue;
  343. if (!me.destroyed) {
  344. var span = doc.createElement('span');
  345. span.innerHTML = text;
  346. span.style.fontSize = header.size;
  347. span.style.color = header.color;
  348. span.style.fontWeight = header.bold ? 'bold' : 'normal';
  349. win.focus();
  350. me.execCmd('insertHTML', '<div>' + span.outerHTML + '</div>');
  351. }
  352. },
  353. /**
  354. * 特殊样式的语句块
  355. */
  356. execBlockStyle: function (block) {
  357. var me = this,
  358. win = me.getWin(),
  359. text = win.getSelection() || '<br>',
  360. html = block.html;
  361. if (!me.destroyed) {
  362. win.focus();
  363. if (!html) {
  364. html = `<div class="${block.class}"><pre><span class="place-holder">${text}</span></pre></div>`;
  365. }
  366. me.execCmd('insertHTML', `<br>${html}<br>`);
  367. }
  368. },
  369. setTextValue: function () {
  370. },
  371. getTextValue: function () {
  372. return this.getEditorBody().innerText;
  373. },
  374. /**
  375. * 调整图片大小
  376. * @param {*} target
  377. */
  378. resizeImage: function (target) {
  379. var me = this,
  380. el = Ext.fly(target),
  381. padding = 73,
  382. width = el.getWidth(),
  383. maxWidth = Math.min(window.innerWidth, 880),
  384. widthPerc = width / maxWidth,
  385. height = el.getHeight(),
  386. maxHeight = window.innerHeight - padding,
  387. heightPerc = height / maxHeight,
  388. perc = Math.max(widthPerc, heightPerc);
  389. if (perc > 1) {
  390. width = width / perc;
  391. height = height / perc + padding;
  392. } else {
  393. height += padding;
  394. }
  395. var win = Ext.create('Ext.window.Window', {
  396. modal: true,
  397. autoShow: true,
  398. ui: 'simple',
  399. title: '拖动改变图片大小',
  400. maxWidth: maxWidth,
  401. width,
  402. height,
  403. tabIndex: -1,
  404. layout: 'fit',
  405. items: {
  406. itemId: 'image',
  407. xtype: 'image',
  408. src: target.src
  409. },
  410. buttons: [{
  411. text: '确定',
  412. ui: 'primary',
  413. handler: function () {
  414. var image = win.child('#image');
  415. if (!target.getAttribute('data-original-width')) {
  416. target.setAttribute('data-original-width', width);
  417. target.setAttribute('data-original-height', height);
  418. }
  419. target.setAttribute('width', image.getWidth());
  420. target.setAttribute('height', image.getHeight());
  421. me.syncValue();
  422. win.close();
  423. }
  424. }, {
  425. text: '取消',
  426. ui: 'simple',
  427. handler: function () {
  428. win.close();
  429. }
  430. }]
  431. });
  432. win.focus();
  433. }
  434. });