Remplacer les options quoteState ?

J’essaie de rendre les publications anonymes encore plus anonymes en remplaçant la façon dont les citations sont affichées dans les catégories où seule la publication anonyme est autorisée. Ainsi, au lieu de la valeur par défaut :

anonymous4302934
un texte cité

ce que je veux, c’est :

Anonyme :
un texte cité

Je parviens à le faire pour le texte cité via le bouton de la barre d’outils, en me basant sur How to override the buildQuote function? - #7 by Canapin (j’ai dû ajouter une recherche de conteneur en bas pour que cela fonctionne, conformément à la solution de contournement présentée ici : api.modifyClass sometimes(!) not working - #12 by RGJ).

J’ai fouillé dans les fichiers principaux et je n’arrive pas à déterminer exactement où la citation est générée lorsqu’une citation est insérée dans le compositeur via la sélection et en cliquant sur le bouton de citation. Je vois dans _selectionChanged() dans quote-button.js que opts.username est défini si le texte sélectionné se trouve dans un bloc de citation, mais si j’essaie de le modifier pour définir manuellement opts.username, cela n’a aucun effet.

Est-ce la bonne approche pour remplacer cela ? Est-ce que modifyClass est la bonne méthode pour aborder ce problème ?

<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 = `Anonyme`;
        } 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");
        
              // Si aucun post actuel, utiliser le premier ID de post du flux
              if (!postId && postStream) {
                postId = postStream.get("stream.firstObject");
              }
        
              // Si nous modifions un post, récupérer la réponse lors de l'importation d'une citation
              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;
    }

    // s'assurer que nous avons sélectionné du contenu dans un seul post *seulement*
    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];

    // le calcul du markdown prend beaucoup de temps sur les longs posts
    // ce code tente de le calculer uniquement lorsque nous ne pouvons pas accélérer le processus
    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 = `Anonyme`

    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("|") || // les tableaux sont trop complexes
          quoteState.buffer.match(/\n/g) || // les sauts de ligne sont trop complexes
          matches?.length > 1 // les doublons sont trop complexes
        ) {
          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);
        }
      }
    }

    // éviter les boucles infinies dans la sélection de citation sans condition
    // cela peut arriver si vous cliquez trois fois sur du texte dans Firefox
    if (this._prevSelection === _selectedText) {
      return;
    }

    this._prevSelection = _selectedText;

    // sur Desktop, affiche le bouton au début de la sélection
    // sur Mobile, affiche le bouton à la fin de la sélection
    const isMobileDevice = this.site.isMobileDevice;
    const { isIOS, isAndroid, isOpera } = this.capabilities;
    const showAtEnd = isMobileDevice || isIOS || isAndroid || isOpera;

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

    // changer la position du bouton
    schedule("afterRender", () => {
      if (!this.element || this.isDestroying || this.isDestroyed) {
        return;
      }

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

      if (showAtEnd) {
        // Les poignées de sélection sur iOS ont une zone de clic d'environ 50px de rayon
        // nous devons donc nous assurer que nos boutons sont en dehors de ce rayon
        // Appliquer la même logique sur tous les appareils mobiles pour la cohérence

        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 = [
          {
            // déplacer vers la gauche
            top,
            left: left - width - safeRadius,
          },
          {
            // déplacer vers la droite
            top,
            left: left + safeRadius,
          },
          {
            // centré sous la poignée de fin
            top: top + safeRadius,
            left: left - width / 2,
          },
        ];

        for (const pos of possiblePositions) {
          // S'assurer que les boutons sont entièrement dans .topic-area
          pos.left = Math.max(topicArea.left, pos.left);
          pos.left = Math.min(topicArea.right - width, pos.left);

          let clearOfStartHandle = true;
          if (isAndroid) {
            // Sur Android, la poignée de sélection de départ s'étend sous la ligne, nous devons donc l'éviter également :
            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) {
        // Nous n'activons les transitions CSS qu'après le positionnement initial
        // sinon le bouton peut sembler arriver de l'écran
        next(() => this.set("animated", true));
      }
    });
  }
        }
    });
    
    const composerController = api.container.lookup("controller:composer");
    const componentQuoteButton = api.container.lookup("component:quote-button");
</script>

Au cas où quelqu’un d’autre tomberait sur ceci, j’ai trouvé comment remplacer les options de quoteState dans insertQuote de quote-button.js. Le code complet ci-dessous remplacera le nom d’utilisateur dans les citations des catégories spécifiées par la citation de la barre d’outils et le bouton de citation de sélection.

<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>