wysiwyg.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /**
  2. * Simple WYSIWYG Editor
  3. * Lightweight rich text editor for publication content
  4. */
  5. class WYSIWYGEditor {
  6. constructor(textareaId, options = {}) {
  7. this.textarea = document.getElementById(textareaId);
  8. this.options = {
  9. toolbar: ['bold', 'italic', 'underline', '|', 'link', 'image', '|', 'ul', 'ol', '|', 'h1', 'h2', 'h3'],
  10. ...options
  11. };
  12. this.createEditor();
  13. this.bindEvents();
  14. }
  15. createEditor() {
  16. // Create editor container
  17. this.container = document.createElement('div');
  18. this.container.className = 'wysiwyg-container';
  19. // Create toolbar
  20. this.toolbar = document.createElement('div');
  21. this.toolbar.className = 'wysiwyg-toolbar';
  22. this.createToolbarButtons();
  23. // Create content area
  24. this.content = document.createElement('div');
  25. this.content.className = 'wysiwyg-content';
  26. this.content.contentEditable = true;
  27. // Create character count
  28. this.charCount = document.createElement('div');
  29. this.charCount.className = 'wysiwyg-char-count';
  30. // Assemble editor
  31. this.container.appendChild(this.toolbar);
  32. this.container.appendChild(this.content);
  33. this.container.appendChild(this.charCount);
  34. // Replace textarea
  35. this.textarea.parentNode.insertBefore(this.container, this.textarea);
  36. this.textarea.style.display = 'none';
  37. // Initialize content
  38. this.content.innerHTML = this.textarea.value;
  39. this.updateCharCount();
  40. }
  41. createToolbarButtons() {
  42. this.options.toolbar.forEach(item => {
  43. if (item === '|') {
  44. const separator = document.createElement('div');
  45. separator.className = 'wysiwyg-separator';
  46. this.toolbar.appendChild(separator);
  47. } else {
  48. const button = document.createElement('button');
  49. button.className = 'wysiwyg-btn';
  50. button.type = 'button';
  51. button.innerHTML = this.getButtonLabel(item);
  52. button.dataset.command = item;
  53. button.addEventListener('click', () => this.execCommand(item));
  54. this.toolbar.appendChild(button);
  55. }
  56. });
  57. }
  58. getButtonLabel(command) {
  59. const labels = {
  60. 'bold': 'B',
  61. 'italic': 'I',
  62. 'underline': 'U',
  63. 'link': '🔗',
  64. 'image': '🖼️',
  65. 'ul': '• List',
  66. 'ol': '1. List',
  67. 'h1': 'H1',
  68. 'h2': 'H2',
  69. 'h3': 'H3'
  70. };
  71. return labels[command] || command;
  72. }
  73. execCommand(command) {
  74. switch (command) {
  75. case 'bold':
  76. document.execCommand('bold', false, null);
  77. break;
  78. case 'italic':
  79. document.execCommand('italic', false, null);
  80. break;
  81. case 'underline':
  82. document.execCommand('underline', false, null);
  83. break;
  84. case 'link':
  85. this.insertLink();
  86. break;
  87. case 'image':
  88. this.insertImage();
  89. break;
  90. case 'ul':
  91. document.execCommand('insertUnorderedList', false, null);
  92. break;
  93. case 'ol':
  94. document.execCommand('insertOrderedList', false, null);
  95. break;
  96. case 'h1':
  97. this.formatHeading('h1');
  98. break;
  99. case 'h2':
  100. this.formatHeading('h2');
  101. break;
  102. case 'h3':
  103. this.formatHeading('h3');
  104. break;
  105. }
  106. this.content.focus();
  107. }
  108. insertLink() {
  109. const selection = window.getSelection();
  110. const url = prompt('Enter URL:');
  111. if (url) {
  112. document.execCommand('createLink', false, url);
  113. }
  114. }
  115. insertImage() {
  116. const url = prompt('Enter image URL:');
  117. if (url) {
  118. document.execCommand('insertImage', false, url);
  119. }
  120. }
  121. formatHeading(tag) {
  122. const selection = window.getSelection();
  123. const range = selection.getRangeAt(0);
  124. const heading = document.createElement(tag);
  125. heading.textContent = range.toString();
  126. range.deleteContents();
  127. range.insertNode(heading);
  128. }
  129. bindEvents() {
  130. // Update textarea when content changes
  131. this.content.addEventListener('input', () => {
  132. this.textarea.value = this.content.innerHTML;
  133. this.updateCharCount();
  134. });
  135. // Update content when textarea changes (for form submission)
  136. this.textarea.addEventListener('input', () => {
  137. this.content.innerHTML = this.textarea.value;
  138. this.updateCharCount();
  139. });
  140. // Handle paste events
  141. this.content.addEventListener('paste', (e) => {
  142. e.preventDefault();
  143. const text = e.clipboardData.getData('text/html') || e.clipboardData.getData('text/plain');
  144. document.execCommand('insertHTML', false, text);
  145. });
  146. // Keyboard shortcuts
  147. this.content.addEventListener('keydown', (e) => {
  148. if (e.ctrlKey || e.metaKey) {
  149. switch (e.key) {
  150. case 'b':
  151. e.preventDefault();
  152. this.execCommand('bold');
  153. break;
  154. case 'i':
  155. e.preventDefault();
  156. this.execCommand('italic');
  157. break;
  158. case 'u':
  159. e.preventDefault();
  160. this.execCommand('underline');
  161. break;
  162. }
  163. }
  164. });
  165. }
  166. updateCharCount() {
  167. const text = this.content.innerText || this.content.textContent || '';
  168. const count = text.length;
  169. this.charCount.textContent = `Characters: ${count}`;
  170. }
  171. getContent() {
  172. return this.content.innerHTML;
  173. }
  174. setContent(html) {
  175. this.content.innerHTML = html;
  176. this.textarea.value = html;
  177. this.updateCharCount();
  178. }
  179. destroy() {
  180. this.textarea.style.display = 'block';
  181. this.textarea.value = this.content.innerHTML;
  182. this.container.remove();
  183. }
  184. }
  185. // Initialize editor when DOM is ready
  186. document.addEventListener('DOMContentLoaded', () => {
  187. const editor = new WYSIWYGEditor('content');
  188. // Make editor globally accessible
  189. window.wysiwygEditor = editor;
  190. // Handle form submission
  191. const form = document.querySelector('.publication-form');
  192. if (form) {
  193. form.addEventListener('submit', () => {
  194. // Ensure textarea has latest content
  195. document.getElementById('content').value = editor.getContent();
  196. });
  197. }
  198. });