PCX Forum
Smalltalk Für dies und das
Anordnung
Threadliste
  1. Welches Auto fahrt ihr? reality.sys am ( Views: 651 | Antworten: 25 | Letzte: )
  2. Dinge, die gar nicht gehen - Nummer 19 Pascal Parvex am ( Views: 15193 | Antworten: 267 | Letzte: )
  3. RIP Bioware Singler am ( Views: 129 | Antworten: 2 | Letzte: )
  4. Der 2. RIP-Thread Mindeye am ( Views: 76652 | Antworten: 1015 | Letzte: )
  5. Diablo 4? Yay or Nay Sven am ( Views: 713 | Antworten: 11 | Letzte: )
  6. PCX 2.0 ZeroCool am ( Views: 305 | Antworten: 14 | Letzte: )
  7. Dies und das und jenes!-Thread Teil 2 Smooth am ( Views: 46589 | Antworten: 858 | Letzte: )
  8. Stay Forever: Wie der IBM-PC spielen lernte Pascal Parvex am ( Views: 120 | Antworten: 2 | Letzte: )
  9. Der Beginn von Skynet Freeman am ( Views: 366 | Antworten: 7 | Letzte: )
  10. Ich bin jetzt auf 8K-Gaming umgestiegen. Pascal Parvex am ( Views: 20049 | Antworten: 394 | Letzte: )
  11. Steam V Pascal Parvex am ( Views: 65939 | Antworten: 974 | Letzte: )
  12. Der Witze Thread 1 Pascal Parvex am ( Views: 10031 | Antworten: 141 | Letzte: )
  13. Grand Theft Auto VI Mindeye am ( Views: 1870 | Antworten: 40 | Letzte: )
  14. Der I. "ich hab Keys über" Thread Schallmauer am ( Views: 38479 | Antworten: 692 | Letzte: )
  15. Best Price 4 Mindeye am ( Views: 30053 | Antworten: 353 | Letzte: )
  16. Der große Apple Thread - Part 2 Matze am ( Views: 14005 | Antworten: 290 | Letzte: )
  17. Der Politik-Thread IV ossi_osram am ( Views: 32421 | Antworten: 542 | Letzte: )
  18. Neue, aber dennoch alte Hardware: Neo-Geo AES+ reality.sys am ( Views: 361 | Antworten: 9 | Letzte: )
  19. No Man's Sky Killersepp am ( Views: 16114 | Antworten: 300 | Letzte: )
  20. Erinnert sich sonst noch wer an den Textmodus-Snake-Klon "Kibowurm"? Pascal Parvex am ( Views: 815 | Antworten: 9 | Letzte: )
  21. Das PCX-Forum informiert ZeroCool am ( Views: 44032 | Antworten: 868 | Letzte: )
  22. Nvidia RTX wiede am ( Views: 23259 | Antworten: 495 | Letzte: )
  23. Witcher 3 - Ankündigung im Februar? Mindeye am ( Views: 24151 | Antworten: 545 | Letzte: )
  24. Wann hattet ihr das letzte Mal einen Bluescreen? Pascal Parvex am ( Views: 205 | Antworten: 1 | Letzte: )
  25. Seine Jörglangerigkeit mal wieder Famou$ am ( Views: 29489 | Antworten: 634 | Letzte: )
  26. Trittst im Morgenrot daher... Pascal Parvex am ( Views: 1951 | Antworten: 25 | Letzte: )
  27. Der KI-Thread hellbringer am ( Views: 2539 | Antworten: 50 | Letzte: )
  28. Fußball-Thread 2015/2016 Border am ( Views: 50149 | Antworten: 973 | Letzte: )
  29. Der Musik-Tipp-Thread Border am ( Views: 33655 | Antworten: 366 | Letzte: )
  30. "Frogger" Demo von Hasbro Interactive von 1997 Pascal Parvex am ( Views: 94 | Antworten: 0 )
  31. was gefällt! ossi_osram am ( Views: 69114 | Antworten: 1005 | Letzte: )
  32. Der Geburtstagsthread Pascal Parvex am ( Views: 2139 | Antworten: 41 | Letzte: )
  33. Diablo 2 (Resurrected) Christoph am ( Views: 1294 | Antworten: 9 | Letzte: )
  34. Cyberpunk 2077 - neues CDP Spiel Mindeye am ( Views: 11544 | Antworten: 169 | Letzte: )
  35. Der Foto-Thread 2 Pascal Parvex am ( Views: 61951 | Antworten: 882 | Letzte: )
  36. Bei AMD ist heute Weihnachten wiede am ( Views: 15351 | Antworten: 288 | Letzte: )
  37. Technische Fragen - Wake on LAN und VMD-Raid Lupo am ( Views: 321 | Antworten: 2 | Letzte: )
  38. Civilization VII Pascal Parvex am ( Views: 994 | Antworten: 9 | Letzte: )
  39. Der Intel-Thread Singler am ( Views: 4307 | Antworten: 65 | Letzte: )
  40. Der 2. Film-Thread (Kino / Heimkino / TV) Matze am ( Views: 29807 | Antworten: 455 | Letzte: )
  41. Das Maniac Forum Teh Sachiel am ( Views: 9858 | Antworten: 206 | Letzte: )
  42. Was wird/wurde gespielt? - Nr1 Mindeye am ( Views: 70990 | Antworten: 802 | Letzte: )
  43. GOG - I ossi_osram am ( Views: 19529 | Antworten: 273 | Letzte: )
  44. Weiterentwicklung Forum (technisch) TrifftNix am ( Views: 1045 | Antworten: 28 | Letzte: )
  45. WoW SauerCrowd TrifftNix am ( Views: 648 | Antworten: 11 | Letzte: )
  46. Monitor gesucht TrifftNix am ( Views: 241 | Antworten: 3 | Letzte: )
  47. FF7 Renmake für PC (und auch PS3) Schallmauer am ( Views: 1110 | Antworten: 13 | Letzte: )
  48. Ich (als Mod) veranstalte mal wieder ein Giveaway auf r/3ddruck... Singler am ( Views: 434 | Antworten: 6 | Letzte: )
  49. Frohes neues Jahr 2026 reality.sys am ( Views: 233 | Antworten: 4 | Letzte: )
  50. Switch 2 hellbringer am ( Views: 1856 | Antworten: 37 | Letzte: )
  51. Star Wars - Fate of the old Republic Goran am ( Views: 286 | Antworten: 3 | Letzte: )
  52. Internet-Geschwindigkeitsvergleich Pascal Parvex am ( Views: 12390 | Antworten: 245 | Letzte: )
  53. Anthem: Kein Thema? MrAnchovy am ( Views: 2099 | Antworten: 22 | Letzte: )
  54. Ubisoft verschiebt Geschäftszahlen ossi_osram am ( Views: 277 | Antworten: 2 | Letzte: )
  55. 21 Bud Spencer und Terence Hill Filme gratis bis Ende Jahr Pascal Parvex am ( Views: 408 | Antworten: 17 | Letzte: )
  56. 007 First Light Border am ( Views: 244 | Antworten: 1 | Letzte: )
  57. Der Adventure-Thread Joschi am ( Views: 59209 | Antworten: 676 | Letzte: )
  58. Frag das PCX-Forum 4 Pascal Parvex am ( Views: 15384 | Antworten: 345 | Letzte: )
  59. Jemand Interesse an 3DDruckern? Ihr könnt einen gewinnen Singler am ( Views: 194 | Antworten: 1 | Letzte: )
  60. TMP aktiviert - kein Windows 11 Lupo am ( Views: 317 | Antworten: 5 | Letzte: )
  61. Battlefield 6 Mindeye am ( Views: 645 | Antworten: 2 | Letzte: )
  62. VR-Gear MatD am ( Views: 5074 | Antworten: 110 | Letzte: )
  63. Star Citizen Mindeye am ( Views: 6465 | Antworten: 73 | Letzte: )
  64. Anno 117: Pax Romana Mindeye am ( Views: 643 | Antworten: 3 | Letzte: )
  65. Windows 11 reality.sys am ( Views: 2058 | Antworten: 37 | Letzte: )
  66. Asus ROG Ally vs. Steam Deck Mindeye am ( Views: 986 | Antworten: 2 | Letzte: )
  67. Borderlands 4 kommt am 12. September wiede am ( Views: 365 | Antworten: 1 | Letzte: )
  68. The Witcher 4 (Reveal Trailer) Border am ( Views: 547 | Antworten: 4 | Letzte: )
  69. Windows Surface Problem Lupo am ( Views: 404 | Antworten: 5 | Letzte: )
  70. Kingdom Come Deliverance II ZeroCool am ( Views: 1119 | Antworten: 9 | Letzte: )
  71. WLAN Mesh Repeater als "Kabelersatz" Lupo am ( Views: 554 | Antworten: 7 | Letzte: )
  72. Stalker 2 - Entwicklung geht weiter Slochy am ( Views: 3314 | Antworten: 33 | Letzte: )
  73. For Sale Topic reality.sys am ( Views: 7622 | Antworten: 92 | Letzte: )
  74. Path of Exile Schallmauer am ( Views: 4013 | Antworten: 26 | Letzte: )
  75. MiSTer FPGA - gibt es hier Erfahrungen? reality.sys am ( Views: 419 | Antworten: 2 | Letzte: )

Weiterentwicklung Forum (technisch)

28 Antworten

Re:WYSIWYG Editor

von TrifftNix am Antwort auf WYSIWYG Editor von TrifftNix

Ich bin eben auf den tiptap gewechselt. Das geht deutlich besser -> siehe Testforum. Bei der Gelegenheit habe ich gleich Mentions mit eingebaut. Läuft auch.
Quote und Messageparser müssen noch angepasst werden. Da steht eine Entscheidung an. Bleibt es beim Quoten mit > pro Zeile oder wird doch gleich auf [q:....] umgestellt, was der Funktionsweise der Editoren entspricht, aber mit der Messagehistorie bricht. Da würden Zitate nicht mehr grau angezeigt, sondern nur noch mit den > davor. ...oder ein Migrationsscript muss her.
Über die Sache mit HTML direkt in der DB habe ich auch noch mal nachgedacht. Es hätte neben Sicherheitsaspekten noch einen anderen Nachteil. Wenn sich z.B. bei Youtube mal die Syntax ändert oder man es anders aufrufen will steht alles falsch in der DB. Ich denke ich bleibe bei der PXM Syntax. Läuft problemlos mit dem Editor.

Das als Anregung für die KI:

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'
import Underline from '@tiptap/extension-underline'
import Mention from '@tiptap/extension-mention'
import suggestion from './mention-suggestion.js'

/**
* Tiptap to PXM Converter
* Konvertiert Tiptap JSON zu PXM-Syntax
* Basierend auf der Stack-basierten PXM-Syntax:
*   text → <b>text</b>
*   text → <b><i>text</i></b>
*/
function tiptapToPxm(editor) {
 const json = editor.getJSON();

 const convertNode = (node) => {
   // Text-Node mit Marks
   if (node.type === 'text') {
     let text = node.text;

     // Marks von außen nach innen anwenden (Stack-basiert)
     if (node.marks) {
       // Reverse, damit innerste Marks zuerst applied werden
       node.marks.slice().reverse().forEach(mark => {
         switch(mark.type) {
           case 'bold':
             text = `${text}`;
             break;
           case 'italic':
             text = `${text}`;
             break;
           case 'underline':
             text = `${text}`;
             break;
           case 'strike':
             text = `${text}`;
             break;
           case 'link':
             // Link-Format: [url]
             const href = mark.attrs.href;
             if (href.startsWith('http://') || href.startsWith('https://')) {
               text = `[${href}]`;
             } else if (href.startsWith('www.')) {
               text = `[${href}]`;
             } else if (href.startsWith('mailto:')) {
               text = `[${href}]`;
             }
             break;
         }
       });
     }
     return text;
   }

   // Mention-Node handling: Convert to PXM format [user:id]
   if (node.type === 'mention') {
     const userId = node.attrs?.id || 0;
     return `[user:${userId}]`;
   }

   // Block-Nodes rekursiv verarbeiten
   const content = node.content
     ? node.content.map(convertNode).join('')
     : '';

   switch(node.type) {
     case 'paragraph':
       // Paragraph = Content + Newline
       return content + '\n';

     case 'blockquote':
       // Quote: Jede Zeile mit > prefixen
       return content.split('\n')
         .map(line => line ? `>${line}` : '')
         .filter(line => line)  // Leere Zeilen entfernen
         .join('\n') + '\n';

     case 'hardBreak':
       return '\n';

     case 'image':
       // Bild-Format: [img:url]
       const src = node.attrs?.src || '';
       return `[img:${src}]`;

     case 'doc':
       return content;

     default:
       console.warn('Unbekannter Node-Typ:', node.type);
       return content;
   }
 };

 return convertNode(json).trim();
}

/**
* Editor-Instanzen werden außerhalb von Alpine's Reactive-System gespeichert,
* damit ProseMirror's State-Management nicht durch Alpine's Proxy gestört wird.
*/
const editorInstances = new WeakMap();

function getEditor(el) {
 return editorInstances.get(el);
}

/**
* Alpine.js Component für Tiptap Editor
*/
let pxmEditorRegistered = false;

function initPxmEditor() {
 if (pxmEditorRegistered) {
   return;
 }

 if (typeof window.Alpine !== 'undefined') {
   pxmEditorRegistered = true;

   window.Alpine.data('pxmEditor', (initialHtmlContent = '') => ({
     pxmContent: '',
     loading: true,
     error: null,

     init() {
       const el = this.$refs.editor;
       if (!el || getEditor(el)) {
         return;
       }

       try {
         const component = this;
         const editor = new Editor({
           element: el,
           extensions: [
             StarterKit.configure({
               link: false,
               underline: false,
             }),
             Underline,
             Link.configure({
               openOnClick: false,
               HTMLAttributes: {
                 target: '_blank',
                 rel: 'noopener noreferrer'
               }
             }),
             Image.configure({
               inline: false,
               HTMLAttributes: {
                 class: 'max-w-full h-auto'
               }
             }),
             Mention.configure({
               HTMLAttributes: {
                 class: 'mention',
               },
               suggestion: suggestion,
               renderLabel({ node }) {
                 return `@${node.attrs.label}`;
               }
             })
           ],
           content: initialHtmlContent,
           onUpdate: ({ editor }) => {
             component.pxmContent = tiptapToPxm(editor);
           },
           editorProps: {
             attributes: {
               class: 'prose prose-sm max-w-none focus:outline-none min-h-[200px] p-4'
             }
           }
         });

         editorInstances.set(el, editor);
         this.pxmContent = tiptapToPxm(editor);
         this.loading = false;
       } catch (error) {
         console.error('Editor-Initialisierung fehlgeschlagen:', error);
         this.error = 'Editor konnte nicht geladen werden. Fehler: ' + error.message;
         this.loading = false;
       }
     },

     destroy() {
       const el = this.$refs.editor;
       const editor = el && getEditor(el);
       if (editor && !editor.isDestroyed) {
         editor.destroy();
       }
       if (el) {
         editorInstances.delete(el);
       }
     },

     // Toolbar-Aktionen
     toggleBold() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       editor.commands.toggleBold();
     },

     toggleItalic() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       editor.commands.toggleItalic();
     },

     toggleUnderline() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       editor.commands.toggleUnderline();
     },

     toggleStrike() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       editor.commands.toggleStrike();
     },

     setLink() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       const url = window.prompt('URL:');
       if (url) {
         editor.commands.setLink({ href: url });
       }
     },

     unsetLink() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       editor.commands.unsetLink();
     },

     insertImage() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       const url = window.prompt('Bild-URL:');
       if (url) {
         editor.commands.setImage({ src: url });
       }
     },

     toggleBlockquote() {
       const editor = getEditor(this.$refs.editor);
       if (!editor || editor.isDestroyed) return;
       editor.commands.toggleBlockquote();
     },

     // Status-Prüfungen für Toolbar Buttons
     isBold() {
       return getEditor(this.$refs.editor)?.isActive('bold');
     },

     isItalic() {
       return getEditor(this.$refs.editor)?.isActive('italic');
     },

     isUnderline() {
       return getEditor(this.$refs.editor)?.isActive('underline');
     },

     isStrike() {
       return getEditor(this.$refs.editor)?.isActive('strike');
     },

     isLink() {
       return getEditor(this.$refs.editor)?.isActive('link');
     },

     isBlockquote() {
       return getEditor(this.$refs.editor)?.isActive('blockquote');
     }
   }));
 }
}

// Versuche mehrere Event-Strategien, aber registriere nur einmal
if (document.readyState === 'loading') {
 document.addEventListener('DOMContentLoaded', initPxmEditor);
} else {
 initPxmEditor();
}

// Fallback: Alpine-spezifisches Event
document.addEventListener('alpine:init', initPxmEditor);