Cytowanie posta/komentarza plugin

1

Jak, że było dość sporo postów o taką funkcjonalność.

Dodałem tę opcję w formie plugina, jest dołączane z zewnątrz, można wkleić do konsoli żeby zaczęło działać, na danej stronie lub w formie pluginu wtedy na każdej.
W Vue czy React raczej by to miało inną formę niż w surowym javascript.

(() => {
  let d = document.createElement('div')
  d.id = 'cycuj'
  d.innerText = 'Cytuj'
  d.setAttribute('style', 'display: none')
  d.addEventListener('click', (e) => {
    let cyc = document.getElementById('cycuj')
    cyc.setAttribute('style', 'display: none')

    let editor = document.querySelector('div.editor').querySelector('div.cm-content')
    let placeholder = editor.childNodes[0].querySelector('span.cm-placeholder')
    if (placeholder) {
      editor.childNodes[0].remove();
    }

    let name = cyc.getAttribute('user')
    let id = cyc.getAttribute('user-id')
    let text = cyc.getAttribute('user-quote')

    let quote = document.createElement('div')
    quote.classList.add('highlight-quote', 'cm-line')
    quote.innerHTML = `<div class="highlight-quote cm-line"><span class="ͼr">&gt;</span> <span class="ͼ10 ͼr">#####</span><span class="ͼ10"> </span><span class="ͼ10 ͼ1a ͼr">[</span><span class="ͼ10 ͼ1a">${name} napisał(a)</span><span class="ͼ10 ͼ1a ͼr">]</span><span class="ͼ10 ͼ1a ͼr">(</span><span class="ͼ10 ͼ1a ͼ1b">/Forum/${id}</span><span class="ͼ10 ͼ1a ͼr">)</span><span class="ͼ10">:</span></div>`
    let quote_text = document.createElement('div')
    quote_text.classList.add('highlight-quote', 'cm-line')
    quote_text.innerHTML = `<span class="ͼr">&gt;</span>${text}`
    let br = document.createElement('div')
    br.classList.add('cm-line')
    br.innerHTML = `<br>`
    editor.append(quote, quote_text, br)
  })
  document.body.append(d)

  document.addEventListener("mouseup", (e) => {
    let selection = document.getSelection()
    let text = selection.toString()
    let cyc = document.getElementById('cycuj')

    let elem = selection.anchorNode.parentElement.closest('.post-comment')
    if (elem === null) {
      elem = selection.anchorNode.parentElement.closest('.card-post')
    }

    if (text.trim() === '' || elem === null) {
      cyc.setAttribute('style', 'display: none')
      return
    }

    let author = elem.querySelector('a[data-user-id]')
    let id = author.getAttribute('data-user-id')
    let name = author.text

    let pageX = window.scrollX
    let pageY = window.scrollY
    let client_rect = selection.getRangeAt(0).getBoundingClientRect()
    let x_pos = client_rect.left + pageX
    let y_pos = client_rect.top + pageY - 50

    cyc.setAttribute('user', name)
    cyc.setAttribute('user-id', id)
    cyc.setAttribute('user-quote', text)
    cyc.setAttribute('style', `display:block; box-shadow: 1px 1px 5px black;background-color: black; color: white; position: absolute; top: ${y_pos}px; left: ${x_pos}px; index: 9999; padding: 5px 10px 5px 10px; border: 1px; border-radius: 10px; font-weight: bold; font-size: 15px; cursor: pointer`)
  })
})()

Oczywiście jest to wersja eksperymentalna, na testy nie było czasu.

Teraz opiszę jak to działa, jeśli ktoś by chciał zrozumieć.

Tworzę jeden globalny obiekt div, który działa jako popup/tooltip, na początku nie jest wyświetlany.
Dodaję tam event click.

Potem po podniesieniu przycisku myszy, sprawdzam czy zostało coś zaznaczone, czy to jest komentarz, czy post.
Jeśli nie to wychodzę przy okazji ustawiam ukryty tooltip w razie jakby był widoczny bo wcześniej było zaznaczenie.

Jeśli tak, zapamiętuję autora, id, jego treść, ustawiam tooltip odpowiednią pozycję na stronie.
I robię widzialny ten tooltip div.

Potem jak ktoś go kliknie, to odczytuje autor, id, quote.
Tworzę markdown, ale w bardzo prosty sposób i dodaje go do edytora.

1

Cześć!

Po pierwsze, super że chciało Ci się poświęcić czas na napisanie pluginu do edytora. Fajnie, że znalazła się taka inicjatywa.

Niestety stety, edytor jest zaprojektowany w Model-View-Controller. Model to jest string, parsowany na drzewo które zwiera AST markdownu. Widok renderuje AST jako elementy DOM, a controller przechwytuje myszkę, klawiaturę i kontrolki, i tłumaczy na zmiany modelu. Więc cały stan treści postu jest w modelu. Po napisaniu klawisza i zmianie modelu, widok jest odświeżany - co znaczy że zmieniane jest drzewo, i na podstawie nowego drzewa nowy widok jest renderowany - elementy DOM wtedy są tworzone. Twój plugin zmienia tylko widok, więc po wpisaniu dalszej treści, to co dodał ten plugin będzie cofnięte, to raz. Po drugie, jak klikamy "Enter", to do serwisu coyote leci właśnie model, nie widok - więc to co wkleiłeś do view nie będzie w ogóle wzięte pod uwagę.

Żeby napisać dobry plugin, należałoby zmienić model w edytorze (po prostu string), i potem wywołać rerender widoku (czyli zmiana stringa na DOM).

0

No dopisałem markdowna, do widoku, zobaczę czy łatwo pójdzie się podpiąć z zewnątrz do modelu, bo raczej jest enkapsulowane, a przeglądarki dobrze to strzegą.

1
tumor napisał(a):

No dopisałem markdowna, do widoku, zobaczę czy łatwo pójdzie się podpiąć z zewnątrz do modelu, bo raczej jest enkapsulowane, a przeglądarki dobrze to strzegą.

Aktualnie edytor pozwala tylko na dostarczenie początkowej treści (np jak zaczniesz pisać treść, zamkniesz okno, i potem otworzysz znowu, to do edytora można wsadzić poprzednią treść, jako początkową wartość), ale nie da się jej zmienić z zewnątrz, inaczej niż wywołując metody do tego przeznaczone. Więc nie możesz "tak o" sobie po prostu zmienić treści na inną.

Ale do dodania cytatu, jest wystawiona odpowiednia funkcja, jest uruchamiana kiedy ktoś klika "Odpowiedz":

screenshot-20231021170731.png

Wtedy dodajemy odpowiednią treść do modelu, i widok jest odpowiednio pokazywany. Jeśli z Twojego plugina udałoby Ci się wywołać to, co woła przycisk "Odpowiedz" z innym argumentem, wtedy osiagnąłbyś swój cel.

Jeśli chcesz, to możesz też wywołać funkcje w edytorze bezpośrednio, jest to metoda appendBlockQuote(content:string) w klasie Editor4Play.

1

Fixed version i nauczyłem się manipulować vue z zewnątrz, ciekawe dość prosto to działa, obiekt vue jest podczepiony pod posta, trochę wiedzy doszło jak fuzję z vue zrobić.

(() => {
  let name = ''
  let id = ''
  let text = ''

  let d = document.createElement('div')
  d.id = 'cycuj'
  d.innerText = 'Cytuj'
  d.setAttribute('style', 'display: none')
  
  d.addEventListener('click', (e) => {
    let cyc = document.getElementById('cycuj')
    cyc.setAttribute('style', 'display: none')

    let vue_obj = document.querySelector('.card-post').__vue__
    vue_obj.$emit("reply", {"user": {"name": name}, "id": id, "text": text}, false)
  })

  document.addEventListener("mouseup", (e) => {
    let selection = document.getSelection()
    let cyc = document.getElementById('cycuj')

    let elem = selection.anchorNode.parentElement.closest('.post-comment')
    if (elem === null) {
      elem = selection.anchorNode.parentElement.closest('.card-post')
    }

    if (selection.toString().trim() === '' || elem === null) {
      cyc.setAttribute('style', 'display: none')
      return
    }

    let author = elem.querySelector('a[data-user-id]')
    id = author.getAttribute('data-user-id')
    name = author.text
    text = selection.toString()

    let pageX = window.scrollX
    let pageY = window.scrollY
    let client_rect = selection.getRangeAt(0).getBoundingClientRect()
    let x_pos = client_rect.left + pageX
    let y_pos = client_rect.top + pageY - 50

    cyc.setAttribute('style', `display:block; box-shadow: 1px 1px 5px black;background-color: black; color: white; position: absolute; top: ${y_pos}px; left: ${x_pos}px; index: 9999; padding: 5px 10px 5px 10px; border: 1px; border-radius: 10px; font-weight: bold; font-size: 15px; cursor: pointer`)
  })

  document.body.append(d)
})()
0

No i to ma szansę działać, sam pisałem tą funkcję reply.

1 użytkowników online, w tym zalogowanych: 0, gości: 1