Замена аватара и имени пользователя для конкретного сообщения

Проблема: я хочу, чтобы пользователи могли публиковать сообщения от лица персонажей ролевой игры. Мне бы хотелось, чтобы они могли вставлять в пост шаблон вроде этого:

[wrap="characterpost"]
[characterav]https://image.link.example.png[/characterav]
[charactername][[Имя персонажа]][/charactername]
[/wrap]

А затем использовать скрипт, который заменит изображение на оригинальный аватар, а ссылку на тему с именем персонажа добавит перед именем пользователя. Например, если пост обычно выглядит как «Имя_пользователя», я хочу заменить это на «Имя_персонажа (в исполнении Имя_пользователя)».
(«Имя_персонажа» должно включать ссылку на тему с листом персонажа, желательно используя компонент тем wikilinks для простоты.)

Я вставил скелет поста в Codepen и смог написать JavaScript, который делает именно это. Однако, когда дело дошло до добавления этого в декоратор постов и запуска вживую через API, я уперся в стену.

Вот что у меня сейчас есть в common>header:

<script type="text/discourse-plugin" version="0.8">
api.decorateCookedElement(
  element => {
    
    // найти тег characterpost внутри поста
    const characterPost = element.querySelector('[data-wrap="characterpost"]');
	
    // найти родительский элемент тега characterpost, содержащий аватар и имя пользователя
    const characterPostParent = characterPost.closest('article');
	
    // покрасить в красный, чтобы проверить, работает ли это
    characterPostParent.style.backgroundColor = "red";
    
  },
  {
    id: 'render-character-post', onlyStream: true, afterAdopt: true
  }
);
</script>

Это вызывает ошибку. Возможно ли получить доступ к обёртке «article» для постов через decorateCookedElement, чтобы я мог добраться до имени пользователя и аватара? Если нет, как мне это сделать?

Привет, unfairest,

Я наткнулся на этот пост и решил попробовать!

Вот первая версия; надеюсь, код не слишком ужасен. :slightly_smiling_face:
Я не проводил тщательное тестирование всех крайних случаев. Это лишь отправная точка.

  • Ожидаемый формат обёртки: [wrap=character avatar="<URL>" name="<Имя>"][/wrap]
  • Убедитесь, что под тегом [wrap] есть пустая строка
  • Вы можете настроить имя персонажа с помощью CSS (см. классы character и character-extra)

Дайте знать, если возникнут проблемы :slight_smile:

Заголовок
<script type="text/discourse-plugin" version="0.8.13">

let characters = new Map();

function searchTag(obj, tag) {
  if (!obj || !obj.firstObject) return;
  
  const firstObj = obj.firstObject;
  return firstObj.tagName === tag ? firstObj : searchTag(firstObj.children, tag);
}
            
api.addPostTransformCallback(post => 
{
    if (post.post_number <= 1 || post.post_type !== 1) {
        return;
    }

    const matches = post.cooked.match(/data-wrap="character"\s+data-avatar="(?<avatar>[^"]+)"\s+data-name="(?<name>[^"]+)"/i);
    
    if (!matches) {
        characters.delete(post.id);
        return;
    }
    
    // TODO: проверка корректности аватара/имени
    
    const {name, avatar} = matches.groups;
    
    characters.set(post.id, {name, avatar});
    
});

api.reopenWidget("post-avatar", {
    html(attrs) {
        const html = this._super(attrs);
        
        if (attrs.id && characters.has(attrs.id)) {
            const imageHtml = searchTag(html, 'IMG');
    
            if (imageHtml) {
                imageHtml.properties.attributes.src = characters.get(attrs.id).avatar;
            }
        }
        
        return html;
    }
});

api.reopenWidget("poster-name", {
    html(attrs) {
        let html = this._super(attrs);
        
        if (attrs.id && characters.has(attrs.id)) {
            const h = require("virtual-dom").h;
            
            html = [
                h('span.character', this.userLink(attrs, characters.get(attrs.id).name)),
                h('span.character-extra', 'в исполнении'), // опционально
                ...html
            ];
        }
        
        return html;
    }
});

</script>
CSS
.names {
    .character a {
        font-weight: bold;
        color: var(--tertiary-high);
    }
    
    .character-extra {
        
    }
}

.cooked, .d-editor-preview {
    p:has(> [data-wrap="character"]) {
        display: none;
    }
    
    p:has(> [data-wrap="character"]) + * {
        margin-top: 0;
    }
}

Вот небольшая демонстрация того, как это выглядит:

Это очень круто, спасибо, что поделились! Я тоже искал что-то похожее. Вы не знаете, влияет ли это на то, как посты отображаются в поиске?

Отличный вопрос! Имя тега и его содержимое являются частью текста поста; это может повлиять на результаты поиска.
Я попробую более чистое решение, например, использовать модальное окно и сохранять данные в пользовательских полях.

РЕДАКТИРОВАНИЕ: Кажется, я неправильно понял :smile: если речь идёт о том, что изменения отображаются в результатах поиска, то ответ — нет (и HTML-код тоже не появится).

Это довольно круто. Ваше видео прокручивается быстро. Ссылка находится на сайте форума?

Привет, Дэн. Что ты имеешь в виду?

URL аватара. Это просто загруженное изображение или ссылка на внешний сайт?

Это внешний URL. Должна быть возможность работать с локальным URL.

Круто, спасибо

Возможно, стоит предложить идею создания руководства по шаблонам форумов в вики сообщества. Часто пользователи ищут идеи для различных типов настроек форумов.

В данном случае тема может быть немного узкоспециализированной, но шаблон для настольных ролевых игр, включающий созданную вами функцию, был бы полезен.