PageSourceSearch

https://noman-ai.jp/assets/components/app/editor/menu/component_controller-0c384b5a.js

js noman-ai.jp collected 2026-10-04 18:38:50 UTC 7,853 bytes, 288 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4
5  static get targets() {
6    return [
7      'menuRow',
8      'formatOptionList',
9      'h1',
10      'h2',
11      'h3',
12      'bold',
13      'italic',
14      'strike',
15      'bulletList',
16      'orderedList',
17      'quote',
18      'link',
19      'more'
20      // 'palette',
21      // 'paletteItem',
22      // 'image',
23      // 'inputImage',
24      // 'inputFile'
25    ]
26  }
27
28  static get values() {
29    return {
30      colors: String
31    }
32  }
33
34  initialize() {
35    this.colors = this.colorsValue.split(' ')
36  }
37
38  connect() {
39    this.showingMoreMenu = false;
40    this.renderMenuRows();
41    // this.hideMenuItemArea = (e) => {
42    //   this._hideMenuItemArea(e)
43    // }
44    // document.addEventListener("click", this.hideMenuItemArea);
45  }
46
47  disconnect() {
48    // document.removeEventListener("click", this.hideMenuItemArea);
49  }
50
51  /**
52   * Renders the menu rows by adjusting the visibility of menu items based on available space.
53   * It hides or shows menu items and updates the "More" menu as necessary.
54   */
55  renderMenuRows() {
56    const menuRows = this.menuRowTargets;
57    const rowWidth = this.element.offsetWidth;
58
59    // Number of format options that can be displayed
60    const firstRowCapacity = Math.floor((rowWidth - 16 - 172 - 9 + 4) / 44);
61    const otherRowCapacity = Math.floor((rowWidth - 16 + 4) / 44);
62
63    const allFormatOptions = Array.from(this.element.querySelectorAll('.format-option'));
64    const totalOptions = allFormatOptions.length;
65
66    if (totalOptions <= firstRowCapacity) {
67      // Show all format options in the first row
68      allFormatOptions.forEach((option) => {
69        this.formatOptionListTargets[0].insertBefore(option, this.moreTarget);
70      });
71    } else {
72      // Show (capacity - 1) format options in the first row
73      const firstRowOptions = allFormatOptions.slice(0, firstRowCapacity - 1);
74      firstRowOptions.forEach((option) => {
75        this.formatOptionListTargets[0].insertBefore(option, this.moreTarget);
76      });
77      for (let i = 1; i < menuRows.length; i++) {
78        const options = allFormatOptions.slice(firstRowCapacity - 1 + (i - 1) * otherRowCapacity, firstRowCapacity - 1 + i * otherRowCapacity);
79        if (options.length === 0) {
80          break;
81        }
82        options.forEach((option) => {
83          this.formatOptionListTargets[i].appendChild(option);
84        });
85      }
86    }
87    this._renderMoreMenu();
88  }
89
90  _renderMoreMenu() {
91    for (let i = 1; i < 3; i++) {
92      if (this.showingMoreMenu && this.formatOptionListTargets[i].children.length > 0) {
93        this.menuRowTargets[i].classList.remove('hidden');
94        this.menuRowTargets[i].classList.add('flex');
95      } else {
96        this.menuRowTargets[i].classList.add('hidden');
97        this.menuRowTargets[i].classList.remove('flex');
98      }
99    }
100    if (this.formatOptionListTargets[1].children.length === 0) {
101      this.moreTarget.classList.add('hidden');
102    } else {
103      this.moreTarget.classList.remove('hidden');
104    }
105  }
106
107  changeTemplate(e) {
108    document.getElementById('modal').insertAdjacentHTML(
109      "beforeend",
110      e.params.modal
111    );
112  }
113
114  editorConnected(editorController) {
115    // Always re-read the editor: a server-side state replacement rebuilds the
116    // editor instance, and a cached reference would target a destroyed editor.
117    this.editorController = editorController;
118    this.editor = editorController?.editor;
119    this._render();
120  }
121
122  editorUpdated() {
123    this._render();
124  }
125
126  h1() {
127    this._editor().chain().focus().toggleHeading({ level: 1 }).run()
128  }
129
130  h2() {
131    this._editor().chain().focus().toggleHeading({ level: 2 }).run()
132  }
133
134  h3() {
135    this._editor().chain().focus().toggleHeading({ level: 3 }).run()
136  }
137
138  bold() {
139    this._editor().chain().focus().toggleBold().run()
140  }
141
142  italic() {
143    this._editor().chain().focus().toggleItalic().run()
144  }
145
146  strike() {
147    this._editor().chain().focus().toggleStrike().run()
148  }
149
150  bulletList() {
151    this._editor().chain().focus().toggleBulletList().run()
152  }
153
154  orderedList() {
155    this._editor().chain().focus().toggleOrderedList().run()
156  }
157
158  quote() {
159    this._editor().chain().focus().toggleBlockquote().run()
160  }
161
162  palette(e) {
163    if (this._isTargetIcon(e, this.paletteTarget)) {
164      this._toggleActiveClass(this.paletteTarget, !this.paletteTarget.classList.contains('active'));
165      this._editor().chain().focus().run()
166    }
167  }
168
169  pickColor({ params }) {
170    this._editor().chain().focus().setColor(params.color).run()
171  }
172
173  more() {
174    this.showingMoreMenu = !this.showingMoreMenu;
175    this._render();
176    this.renderMenuRows()
177  }
178
179  image() { }
180
181  async imageSelected(e) {
182    const file = e.target.files[0];
183    if (file) {
184      const { url, error } = await this._editorController().uploadImage(file);
185      if (error) {
186        alert(error);
187      } else {
188        this._editor().chain().focus().setImage({ src: url }).run();
189      }
190      this.inputImageTarget.value = "";
191    }
192  }
193
194  file() { }
195
196  async fileSelected(e) {
197    const file = e.target.files[0];
198    if (file) {
199      const fileName = file.name;
200      const { url, error } = await this._editorController().uploadFile(file);
201      if (error) {
202        alert(error);
203      } else {
204        this._editor().chain().focus().setFile({ href: url, 'data-name': fileName }).run();
205      }
206      this.inputFileTarget.value = "";
207    }
208  }
209
210  _editorController() {
211    if (this.editorController) {
212      return this.editorController;
213    }
214    const editorControllerId = "app--editor--component"
215    const editor = this.element.closest(`[data-controller="${editorControllerId}"]`)
216    this.editorController = this.application.getControllerForElementAndIdentifier(editor, editorControllerId)
217    return this.editorController;
218  }
219
220  _editor() {
221    if (this.editor) {
222      return this.editor;
223    }
224
225    this.editor = this._editorController()?.editor
226    return this.editor
227  }
228
229  // _hideMenuItemArea(e) {
230  //   [
231  //     // this.headingTarget,
232  //     this.paletteTarget
233  //   ].forEach((target) => {
234  //     if (!target.contains(e.target)) {
235  //       target.classList.remove('active');
236  //     }
237  //   })
238  // }
239
240  _render() {
241    const editor = this._editor();
242    if (!editor) return;
243
244    this._toggleActiveClass(this.h1Target, editor.isActive('heading', { level: 1 }));
245    this._toggleActiveClass(this.h2Target, editor.isActive('heading', { level: 2 }));
246    this._toggleActiveClass(this.h3Target, editor.isActive('heading', { level: 3 }));
247    this._toggleActiveClass(this.boldTarget, editor.isActive('bold'));
248    this._toggleActiveClass(this.italicTarget, editor.isActive('italic'));
249    this._toggleActiveClass(this.strikeTarget, editor.isActive('strike'));
250    this._toggleActiveClass(this.moreTarget, this.showingMoreMenu);
251    // this._renderColorPicker()
252    // this._renderHeading()
253  }
254
255  // _renderColorPicker() {
256  //   this.paletteItemTargets.forEach((item) => {
257  //     const color = item.getAttribute(`data-${this.identifier}-color-param`)
258  //     if (this._editor().isActive('textStyle', { color: color })) {
259  //       item.classList.add('active')
260  //     } else {
261  //       item.classList.remove('active')
262  //     }
263  //   })
264  // }
265
266  // _renderHeading() {
267  //   this.headingTarget.value = 0;
268  //   [1, 2].forEach((level) => {
269  //     if (this._editor().isActive('heading', { level: level })) {
270  //       this.headingTarget.value = level;
271  //     }
272  //   });
273  // }
274
275  _toggleActiveClass(element, isActive) {
276    if (isActive) {
277      element.classList.add('active');
278    } else {
279      element.classList.remove('active');
280    }
281  }
282
283  _isTargetIcon(e, target) {
284    return e.target === target ||
285      e.target === target.children[0] ||
286      target.children[0].contains(e.target)
287  }
288}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.