@@ -1724,6 +1724,130 @@ func TestFillContentAcceptsHighlightedTopicNode(t *testing.T) {
17241724 }
17251725}
17261726
1727+ func TestFillContentAppendsTopicsAfterExistingText (t * testing.T ) {
1728+ page := testPage (t )
1729+
1730+ html := `<!doctype html>
1731+ <html>
1732+ <head><meta charset="utf-8"></head>
1733+ <body>
1734+ <div class="tiptap-container">
1735+ <div contenteditable="true" role="textbox" class="tiptap ProseMirror" tabindex="0"></div>
1736+ </div>
1737+ <script>
1738+ const editor = document.querySelector('.tiptap.ProseMirror');
1739+ let pendingTopic = '';
1740+ let spaceCount = 0;
1741+
1742+ function placeCaretAfterText(needle) {
1743+ const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT);
1744+ while (walker.nextNode()) {
1745+ const node = walker.currentNode;
1746+ const index = (node.nodeValue || '').indexOf(needle);
1747+ if (index < 0) continue;
1748+ const range = document.createRange();
1749+ range.setStart(node, index + needle.length);
1750+ range.collapse(true);
1751+ const selection = window.getSelection();
1752+ selection.removeAllRanges();
1753+ selection.addRange(range);
1754+ return true;
1755+ }
1756+ return false;
1757+ }
1758+
1759+ function insertSuggestionAtSelection() {
1760+ const selection = window.getSelection();
1761+ const range = selection.rangeCount ? selection.getRangeAt(0) : document.createRange();
1762+ if (!selection.rangeCount) {
1763+ range.selectNodeContents(editor);
1764+ range.collapse(false);
1765+ }
1766+ const suggestion = document.createElement('span');
1767+ suggestion.className = 'suggestion is-empty';
1768+ suggestion.textContent = '#';
1769+ range.deleteContents();
1770+ range.insertNode(suggestion);
1771+ range.setStartAfter(suggestion);
1772+ range.collapse(true);
1773+ selection.removeAllRanges();
1774+ selection.addRange(range);
1775+ }
1776+
1777+ let movedAfterFullText = false;
1778+ editor.addEventListener('input', () => {
1779+ if (movedAfterFullText || !(editor.textContent || '').includes('2. 第二条')) return;
1780+ movedAfterFullText = true;
1781+ queueMicrotask(() => placeCaretAfterText('1. 第一条'));
1782+ });
1783+ editor.addEventListener('click', () => {
1784+ if ((editor.textContent || '').includes('2. 第二条')) {
1785+ placeCaretAfterText('1. 第一条');
1786+ }
1787+ });
1788+ editor.addEventListener('beforeinput', (event) => {
1789+ if (event.inputType !== 'insertText' || !event.data) return;
1790+ const suggestion = editor.querySelector('.suggestion');
1791+ if (event.data === '#') {
1792+ event.preventDefault();
1793+ pendingTopic = '';
1794+ spaceCount = 0;
1795+ insertSuggestionAtSelection();
1796+ return;
1797+ }
1798+ if (suggestion && event.data !== ' ') {
1799+ event.preventDefault();
1800+ pendingTopic += event.data;
1801+ suggestion.className = 'suggestion';
1802+ suggestion.textContent = '#' + pendingTopic;
1803+ return;
1804+ }
1805+ if (suggestion && event.data === ' ') {
1806+ event.preventDefault();
1807+ }
1808+ });
1809+ editor.addEventListener('keyup', (event) => {
1810+ const suggestion = editor.querySelector('.suggestion');
1811+ if (!suggestion || event.code !== 'Space') return;
1812+ spaceCount++;
1813+ if (spaceCount < 2) return;
1814+ const data = JSON.stringify({id: 'topic-id', link: 'https://www.xiaohongshu.com/page/topics/topic-id?naviHidden=yes', name: pendingTopic});
1815+ const topic = document.createElement('a');
1816+ topic.className = 'tiptap-topic';
1817+ topic.setAttribute('data-topic', data);
1818+ topic.contentEditable = 'false';
1819+ topic.appendChild(document.createTextNode('#' + pendingTopic));
1820+ const hidden = document.createElement('span');
1821+ hidden.className = 'content-hide';
1822+ hidden.textContent = '[话题]#';
1823+ topic.appendChild(hidden);
1824+ suggestion.replaceWith(topic, document.createTextNode(' '));
1825+ });
1826+ </script>
1827+ </body>
1828+ </html>`
1829+ page .MustNavigate ("data:text/html;charset=utf-8," + url .PathEscape (html ))
1830+ page .MustWaitLoad ()
1831+ page .MustElement ("body" )
1832+
1833+ rodPage := & rodPage {page : page }
1834+ if err := rodPage .FillContent (context .Background (), "1. 第一条\n 2. 第二条" , []string {"AI编程" }); err != nil {
1835+ t .Fatalf ("FillContent() error = %v" , err )
1836+ }
1837+
1838+ text := page .MustEval (`() => document.querySelector('.tiptap.ProseMirror')?.textContent || ''` ).String ()
1839+ if strings .Index (text , "#AI编程" ) < strings .Index (text , "2. 第二条" ) {
1840+ t .Fatalf ("editor text = %q, want topic appended after existing text" , text )
1841+ }
1842+ htmlOut := page .MustEval (`() => document.querySelector('.tiptap.ProseMirror')?.innerHTML || ''` ).String ()
1843+ firstIndex := strings .Index (htmlOut , "1. 第一条" )
1844+ secondIndex := strings .Index (htmlOut , "2. 第二条" )
1845+ breakIndex := strings .Index (htmlOut , "<br" )
1846+ if firstIndex >= 0 && secondIndex >= 0 && breakIndex > firstIndex && breakIndex < secondIndex {
1847+ t .Fatalf ("editor html = %q, want no empty paragraph between content lines" , htmlOut )
1848+ }
1849+ }
1850+
17271851func TestConfirmOnlySelfPublishedAcceptsPublishedRedirect (t * testing.T ) {
17281852 page := testPage (t )
17291853
0 commit comments