From b80f8dfb7f7e770a9666ce08c8f13b847dd6876e Mon Sep 17 00:00:00 2001 From: Katsuya HIDAKA Date: Wed, 24 Sep 2025 02:41:16 +0900 Subject: Remove unnecessary files for quote-reply feature --- app/assets/javascripts/quote_reply.js | 224 ------------------- app/assets/javascripts/turndown-7.2.0.min.js | 0 2 files changed, 224 deletions(-) delete mode 100644 app/assets/javascripts/quote_reply.js delete mode 100644 app/assets/javascripts/turndown-7.2.0.min.js diff --git a/app/assets/javascripts/quote_reply.js b/app/assets/javascripts/quote_reply.js deleted file mode 100644 index dd05d27fe..000000000 --- a/app/assets/javascripts/quote_reply.js +++ /dev/null @@ -1,224 +0,0 @@ -import { Controller } from '@hotwired/stimulus' -import TurndownService from 'turndown' -import { post } from '@rails/request.js' - -class QuoteExtractor { - static extract(targetElement) { - return new QuoteExtractor(targetElement).extract(); - } - - constructor(targetElement) { - this.targetElement = targetElement; - this.selection = window.getSelection(); - } - - extract() { - const range = this.retriveSelectedRange(); - - if (!range) { - return null; - } - - if (!this.targetElement.contains(range.startContainer)) { - range.setStartBefore(this.targetElement); - } - if (!this.targetElement.contains(range.endContainer)) { - range.setEndAfter(this.targetElement); - } - - return range; - } - - retriveSelectedRange() { - if (!this.isSelected) { - return null; - } - - // Retrive the first range that intersects with the target element. - // NOTE: Firefox allows to select multiple ranges in the document. - for (let i = 0; i < this.selection.rangeCount; i++) { - let range = this.selection.getRangeAt(i); - if (range.intersectsNode(this.targetElement)) { - return range; - } - } - return null; - } - - get isSelected() { - return this.selection.containsNode(this.targetElement, true); - } -} - -class QuoteTextFormatter { - format(selectedRange) { - if (!selectedRange) { - return null; - } - - const fragment = document.createElement('div'); - fragment.appendChild(selectedRange.cloneContents()); - - // Remove all unnecessary anchor elements - fragment.querySelectorAll('a.wiki-anchor').forEach(e => e.remove()); - - const html = this.adjustLineBreaks(fragment.innerHTML); - - const result = document.createElement('div'); - result.innerHTML = html; - - // Replace continuous line breaks with a single line break and remove tab characters - return result.textContent - .trim() - .replace(/\t/g, '') - .replace(/\n+/g, "\n"); - } - - adjustLineBreaks(html) { - return html - .replace(/<\/(h1|h2|h3|h4|div|p|li|tr)>/g, "\n") - .replace(/
/g, "\n") - } -} - -class QuoteCommonMarkFormatter { - format(selectedRange) { - if (!selectedRange) { - return null; - } - - const htmlFragment = this.extractHtmlFragmentFrom(selectedRange); - const preparedHtml = this.prepareHtml(htmlFragment); - - return this.convertHtmlToCommonMark(preparedHtml); - } - - extractHtmlFragmentFrom(range) { - const fragment = document.createElement('div'); - const ancestorNodeName = range.commonAncestorContainer.nodeName; - - if (ancestorNodeName == 'CODE' || ancestorNodeName == '#text') { - fragment.appendChild(this.wrapPreCode(range)); - } else { - fragment.appendChild(range.cloneContents()); - } - - return fragment; - } - - // When only the content within the `` element is selected, - // the HTML within the selection range does not include the `
` element itself.
-  // To create a complete code block, wrap the selected content with the `
` tags.
-  //
-  // selected contentes => 
selected contents
- wrapPreCode(range) { - const rangeAncestor = range.commonAncestorContainer; - - let codeElement = null; - - if (rangeAncestor.nodeName == 'CODE') { - codeElement = rangeAncestor; - } else { - codeElement = rangeAncestor.parentElement.closest('code'); - } - - if (!codeElement) { - return range.cloneContents(); - } - - const pre = document.createElement('pre'); - const code = codeElement.cloneNode(false); - - code.appendChild(range.cloneContents()); - pre.appendChild(code); - - return pre; - } - - convertHtmlToCommonMark(html) { - const turndownService = new TurndownService({ - codeBlockStyle: 'fenced', - headingStyle: 'atx' - }); - - turndownService.addRule('del', { - filter: ['del'], - replacement: content => `~~${content}~~` - }); - - turndownService.addRule('checkList', { - filter: node => { - return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'; - }, - replacement: (content, node) => { - return node.checked ? '[x]' : '[ ]'; - } - }); - - // Table does not maintain its original format, - // and the text within the table is displayed as it is - // - // | A | B | C | - // |---|---|---| - // | 1 | 2 | 3 | - // => - // A B C - // 1 2 3 - turndownService.addRule('table', { - filter: ['td', 'th'], - replacement: (content, node) => { - const separator = node.parentElement.lastElementChild === node ? '' : ' '; - return content + separator; - } - }); - turndownService.addRule('tableHeading', { - filter: ['thead', 'tbody', 'tfoot', 'tr'], - replacement: (content, _node) => content - }); - turndownService.addRule('tableRow', { - filter: ['tr'], - replacement: (content, _node) => { - return content + '\n' - } - }); - - return turndownService.turndown(html); - } - - prepareHtml(htmlFragment) { - // Remove all anchor elements. - //

Title1ΒΆ

=>

Title1

- htmlFragment.querySelectorAll('a.wiki-anchor').forEach(e => e.remove()); - - // Convert code highlight blocks to CommonMark format code blocks. - // => - htmlFragment.querySelectorAll('code[data-language]').forEach(e => { - e.classList.replace(e.dataset['language'], 'language-' + e.dataset['language']) - }); - - return htmlFragment.innerHTML; - } -} - -export default class extends Controller { - static targets = [ 'content' ]; - - quote(event) { - const { url, textFormatting } = event.params; - const selectedRange = QuoteExtractor.extract(this.contentTarget); - - let formatter; - - if (textFormatting === 'common_mark') { - formatter = new QuoteCommonMarkFormatter(); - } else { - formatter = new QuoteTextFormatter(); - } - - post(url, { - body: JSON.stringify({ quote: formatter.format(selectedRange) }), - contentType: 'application/json', - responseKind: 'script' - }); - } -} diff --git a/app/assets/javascripts/turndown-7.2.0.min.js b/app/assets/javascripts/turndown-7.2.0.min.js deleted file mode 100644 index e69de29bb..000000000 -- 2.51.0