# Substituir opções de quoteState?

**URL:** https://meta.discourse.org/t/override-quotestate-opts/256612
**Category:** Development
**Created:** [1 Março , 2023 03:02 UTC](https://meta.discourse.org/t/override-quotestate-opts/256612 "2023-03-01T03:02:59Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![asc](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/asc/32/288324_2.png) [@asc](https://meta.discourse.org/u/asc)
#### Post date: [1 Março , 2023 03:02 UTC](https://meta.discourse.org/t/override-quotestate-opts/256612/1 "2023-03-01T03:02:59Z")

</div>

Estou tentando tornar as postagens anônimas mais anônimas, sobrescrevendo como as citações são exibidas em categorias onde apenas postagens anônimas estão habilitadas. Então, em vez do padrão

> anônimo4302934  
> algum texto citado

o que eu quero é

> Anônimo:  
> algum texto citado

Consegui fazer isso para o texto que é citado via o botão da barra de ferramentas, com base em [How to override the buildQuote function? - #7 by Canapin](https://meta.discourse.org/t/how-to-override-the-buildquote-function/164355/7) (tive que adicionar uma consulta de contêiner no final para fazê-lo funcionar conforme a solução alternativa aqui: [api.modifyClass sometimes(!) not working - #12 by RGJ](https://meta.discourse.org/t/api-modifyclass-sometimes-not-working/212413/12))

Revirei os arquivos principais e não consegui descobrir exatamente onde a citação é construída quando uma citação é inserida no compositor via seleção e clicando no botão de citação. Vejo em [\_selectionChanged() em quote-button.js](https://github.com/discourse/discourse/blob/1598e6b489125bade2a7bfc144ae346815dedf00/app/assets/javascripts/discourse/app/components/quote-button.js#L162-L191) que opts.username é definido se o texto selecionado estiver em um blockquote, mas se eu tentar modificar isso para definir manualmente opts.username, nada é afetado.

Isso é o certo a tentar sobrescrever? É o modifyClass a maneira correta de abordar isso?

```plaintext
<script type="text/discourse-plugin" version="0.8">
const controller = api.container.lookup('controller:topic');
const anon_categories = [5, 6, 14, 15, 16, 17, 18]

    function buildQuote(post, contents, opts = {}) {
      if (!post || !contents) {
        return "";
      }
    
    let anon_name = ``;
    // console.log(anon_categories, controller.get("model.category_id"))
        if (anon_categories.includes(controller.get("model.category_id"))) {
            anon_name = `Anônimo`;
        } else {
            anon_name = opts.username || post.username;
        }
        
    const params = [
        anon_name,
        `post:${opts.post || post.post_number}`,
        `topic:${opts.topic || post.topic_id}`
      ];
    
      if (opts.full) params.push("full:true");
    
      return `\n[quote="${params.join(", ")}"]\n${contents.trim()}\n[/quote]\n\n`;
    }
    
    api.modifyClass('controller:composer', {
        pluginId: 'anonymize-quotes',
        actions: {
            importQuote(toolbarEvent) {
              const postStream = this.get("topic.postStream");
              let postId = this.get("model.post.id");
        
              // Se não houver postagem atual, use o primeiro ID de postagem do fluxo
              if (!postId && postStream) {
                postId = postStream.get("stream.firstObject");
              }
        
              // Se estivermos editando uma postagem, busque a resposta ao importar uma citação
              if (this.get("model.editingPost")) {
                const replyToPostNumber = this.get("model.post.reply_to_post_number");
                if (replyToPostNumber) {
                  const replyPost = postStream.posts.findBy(
                    "post_number",
                    replyToPostNumber
                  );
        
                  if (replyPost) {
                    postId = replyPost.id;
                  }
                }
              }
        
              if (postId) {
                this.set("model.loading", true);
        
                return this.store.find("post", postId).then(post => {
                  const quote = buildQuote(post, post.raw, {
                    full: true
                  });
        
                  toolbarEvent.addText(quote);
                  this.set("model.loading", false);
                });
              }
            }
        }
    });
    
    api.modifyClass('component:quote-button', {
        pluginId: 'anonymize-quotes',
        actions: {
            _selectionChanged() {
    if (this._displayFastEditInput) {
      return;
    }

    const quoteState = this.quoteState;

    const selection = window.getSelection();
    if (selection.isCollapsed) {
      if (this.visible) {
        this._hideButton();
      }
      return;
    }

    // garantir que selecionamos conteúdo dentro de apenas 1 postagem
    let firstRange, postId;
    for (let r = 0; r < selection.rangeCount; r++) {
      const range = selection.getRangeAt(r);
      const $selectionStart = $(range.startContainer);
      const $ancestor = $(range.commonAncestorContainer);

      if ($selectionStart.closest(".cooked").length === 0) {
        return;
      }

      firstRange = firstRange || range;
      postId = postId || $ancestor.closest(".boxed, .reply").data("post-id");

      if ($ancestor.closest(".contents").length === 0 || !postId) {
        if (this.visible) {
          this._hideButton();
        }
        return;
      }
    }

    const _selectedElement = selectedElement();
    const _selectedText = selectedText();

    const $selectedElement = $(_selectedElement);
    const cooked =
      $selectedElement.find(".cooked")[0] ||
      $selectedElement.closest(".cooked")[0];

    // calcular markdown leva muito tempo em postagens longas
    // este código tenta calculá-lo apenas quando não podemos fazer um atalho
    let opts = {
      full:
        selectedRange().startOffset > 0
          ? false
          : _selectedText === toMarkdown(cooked.innerHTML),
    };

    for (
      let element = _selectedElement;
      element && element.tagName !== "ARTICLE";
      element = element.parentElement
    ) {
      if (element.tagName === "ASIDE" && element.classList.contains("quote")) {
        opts.username = element.dataset.username || getQuoteTitle(element);
        opts.post = element.dataset.post;
        opts.topic = element.dataset.topic;
        break;
      }
    }
    
    opts.username = `Anônimo`

    quoteState.selected(postId, _selectedText, opts);
    this.set("visible", quoteState.buffer.length > 0);

    if (this.siteSettings.enable_fast_edit) {
      this.set(
        "_canEditPost",
        this.topic.postStream.findLoadedPost(postId)?.can_edit
      );

      if (this._canEditPost) {
        const regexp = new RegExp(regexSafeStr(quoteState.buffer), "gi");
        const matches = cooked.innerHTML.match(regexp);

        if (
          quoteState.buffer.length < 1 ||
          quoteState.buffer.includes("|") || // tabelas são muito complexas
          quoteState.buffer.match(/\n/g) || // quebras de linha são muito complexas
          matches?.length > 1 // duplicatas são muito complexas
        ) {
          this.set("_isFastEditable", false);
          this.set("_fastEditInitalSelection", null);
          this.set("_fastEditNewSelection", null);
        } else if (matches?.length === 1) {
          this.set("_isFastEditable", true);
          this.set("_fastEditInitalSelection", quoteState.buffer);
          this.set("_fastEditNewSelection", quoteState.buffer);
        }
      }
    }

    // evitar loops rígidos na seleção de citação incondicionalmente
    // isso pode acontecer se você clicar três vezes no texto no Firefox
    if (this._prevSelection === _selectedText) {
      return;
    }

    this._prevSelection = _selectedText;

    // no Desktop, mostra o botão no início da seleção
    // no Mobile, mostra o botão no final da seleção
    const isMobileDevice = this.site.isMobileDevice;
    const { isIOS, isAndroid, isOpera } = this.capabilities;
    const showAtEnd = isMobileDevice || isIOS || isAndroid || isOpera;

    const boundaryPosition = this._getRangeBoundaryRect(firstRange, showAtEnd);

    // mudar a posição do botão
    schedule("afterRender", () => {
      if (!this.element || this.isDestroying || this.isDestroyed) {
        return;
      }

      let top = 0;
      let left = 0;
      const pxFromSelection = 5;

      if (showAtEnd) {
        // As alças de seleção no iOS têm uma área de clique de ~50px de raio
        // então precisamos garantir que nossos botões estejam fora desse raio
        // Aplique a mesma lógica em todos os dispositivos móveis para consistência

        top = boundaryPosition.bottom + pxFromSelection;
        left = boundaryPosition.left;

        const safeRadius = 50;

        const topicArea = document
          .querySelector(".topic-area")
          .getBoundingClientRect();
        topicArea.x += document.documentElement.scrollLeft;
        topicArea.y += document.documentElement.scrollTop;

        const endHandlePosition = boundaryPosition;
        const width = this.element.clientWidth;

        const possiblePositions = [
          {
            // mover para a esquerda
            top,
            left: left - width - safeRadius,
          },
          {
            // mover para a direita
            top,
            left: left + safeRadius,
          },
          {
            // centralizado abaixo da alça final
            top: top + safeRadius,
            left: left - width / 2,
          },
        ];

        for (const pos of possiblePositions) {
          // Garantir que os botões estejam totalmente dentro da .topic-area
          pos.left = Math.max(topicArea.left, pos.left);
          pos.left = Math.min(topicArea.right - width, pos.left);

          let clearOfStartHandle = true;
          if (isAndroid) {
            // No Android, a alça de seleção inicial se estende abaixo da linha, então também precisamos evitá-la:
            const startHandlePosition = this._getRangeBoundaryRect(
              firstRange,
              false
            );

            clearOfStartHandle =
              pos.top - startHandlePosition.bottom >= safeRadius ||
              pos.left + width <= startHandlePosition.left - safeRadius ||
              pos.left >= startHandlePosition.left + safeRadius;
          }

          const clearOfEndHandle =
            pos.top - endHandlePosition.top >= safeRadius ||
            pos.left + width <= endHandlePosition.left - safeRadius ||
            pos.left >= endHandlePosition.left + safeRadius;

          if (clearOfStartHandle && clearOfEndHandle) {
            left = pos.left;
            top = pos.top;
            break;
          }
        }
      } else {
        // Desktop
        top =
          boundaryPosition.top - this.element.clientHeight - pxFromSelection;
        left = boundaryPosition.left;
      }

      Object.assign(this.element.style, { top: `${top}px`, left: `${left}px` });

      if (!this.animated) {
        // Habilitamos as transições CSS apenas após o posicionamento inicial
        // caso contrário, o botão pode parecer voar de fora da tela
        next(() => this.set("animated", true));
      }
    });
  }
        }
    });
    
    const composerController = api.container.lookup("controller:composer");
    const componentQuoteButton = api.container.lookup("component:quote-button");
</script>

```

---

<div class="post-metadata">

### Author: ![asc](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/asc/32/288324_2.png) [@asc](https://meta.discourse.org/u/asc)
#### Post date: [3 Março , 2023 19:46 UTC](https://meta.discourse.org/t/override-quotestate-opts/256612/2 "2023-03-03T19:46:32Z")

</div>

Em caso de alguém se deparar com isso, descobri como substituir as opções de `quoteState` em `quote-button.js` `insertQuote`. Código completo abaixo, que substituirá o nome de usuário em citações em categorias especificadas com a citação da barra de ferramentas e o botão de citação de seleção.

```plaintext
<script type="text/discourse-plugin" version="0.8">
const controller = api.container.lookup('controller:topic');
const anon_categories = [5, 6, 14, 15, 16, 17, 18]

    function buildQuote(post, contents, opts = {}) {
      if (!post || !contents) {
        return "";
      }
    
        let anon_name = ``;
        // console.log(anon_categories, controller.get("model.category_id"))
        if (anon_categories.includes(controller.get("model.category_id"))) {
            anon_name = `Anonymous`;
        } else {
            anon_name = opts.username || post.username;
        }
        
        const params = [
            anon_name,
            `post:${opts.post || post.post_number}`,
            `topic:${opts.topic || post.topic_id}`
          ];
    
        if (opts.full) params.push("full:true");
    
        return `\n[quote="${params.join(", ")}"]\n${contents.trim()}\n[/quote]\n\n`;
    }
    
    api.modifyClass('controller:composer', {
        pluginId: 'anonymize-quotes',
        actions: {
            importQuote(toolbarEvent) {
              const postStream = this.get("topic.postStream");
              let postId = this.get("model.post.id");
        
              // If there is no current post, use the first post id from the stream
              if (!postId && postStream) {
                postId = postStream.get("stream.firstObject");
              }
        
              // If we're editing a post, fetch the reply when importing a quote
              if (this.get("model.editingPost")) {
                const replyToPostNumber = this.get("model.post.reply_to_post_number");
                if (replyToPostNumber) {
                  const replyPost = postStream.posts.findBy(
                    "post_number",
                    replyToPostNumber
                  );
        
                  if (replyPost) {
                    postId = replyPost.id;
                  }
                }
              }
        
              if (postId) {
                this.set("model.loading", true);
        
                return this.store.find("post", postId).then(post => {
                  const quote = buildQuote(post, post.raw, {
                    full: true
                  });
        
                  toolbarEvent.addText(quote);
                  this.set("model.loading", false);
                });
              }
            }
        }
    });
    
    api.modifyClass("component:quote-button", {
        pluginId: 'anonymize-quotes',
        actions: {
            insertQuote() {
                let anon_name = ``;
                // console.log(anon_categories, controller.get("model.category_id"))
                if (anon_categories.includes(controller.get("model.category_id"))) {
                    anon_name = `Anonymous`;
                } else {
                    anon_name = opts.username || post.username;
                }
                this.attrs.quoteState.value.opts.username = anon_name;
                this.attrs.selectText().then(() => this._hideButton());
                console.log(anon_name, this.attrs.quoteState.value.opts)
              }
        }
    });
    
    const composerController = api.container.lookup("controller:composer");
    const quoteButton = api.container.lookup("component:quote-button");
</script>
```
