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 (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)
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 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?
<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>