Replacing avatar & username for a specific post

The problem: I want users to be able to post as RPG characters. I would like for them to be able to insert a template into a post - something like the below:

[wrap="characterpost"]
[characterav]https://image.link.example.png[/characterav]
[charactername][[Character Name]][/charactername]
[/wrap]

And then use a script so that the image replaces the original avatar and the charactername topic link goes in front of the username. For example, if the post usually says “Username,” I want to replace that with “Character Name played by Username”.
(“Character Name” would include a link to a topic with the character’s sheet, hopefully just using the wikilinks theme component for ease of use.)

I pasted a post skeleton into a Codepen and I was able to write some javascript that would do exactly this. However, when it came to adding it into a post decorator and getting it working live with the API, I ran into a wall.

Here’s what I have in common>header right now:

<script type="text/discourse-plugin" version="0.8">
api.decorateCookedElement(
  element => {
    
    // find the characterpost tag within the post
    const characterPost = element.querySelector('[data-wrap="characterpost"]');
	
    // find the parent of the characterpost tag, which contains avatar and username
    const characterPostParent = characterPost.closest('article');
	
    // turn it red to see if it's working
    characterPostParent.style.backgroundColor = "red";
    
  },
  {
    id: 'render-character-post', onlyStream: true, afterAdopt: true
  }
);
</script>

This has been throwing an error. Is it possible to access the “article” wrapper for posts using decorateCookedElement so that I can get to the username and avatar? If not, how can I go about this?

3 Likes

Hi, unfairest,

I stumbled on this post, and I thought I could give it a try!

Here is the first version; hoping the code is not too horrible. :slightly_smiling_face:
I did not extensively test all edge cases. It’s a starting point.

  • Expected wrap format: [wrap=character avatar="<URL>" name="<Name>"][/wrap]
  • Make sure there is an empty line below [wrap]
  • You can customize the character name with CSS (see character and character-extra classes)

Let me know if you have any issues :slight_smile:

Head
<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: sanity check for avatar/name
    
    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', 'played by'), // optional
                ...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;
    }
}

Here is a small demo of how it looked:

5 Likes

This is very cool, thank you for sharing! I was looking for something similar as well. Do you know if this affects how posts appear in search as well?

Good question! The tag name and its content is part of the post content; this might affect the search result.
I will try something cleaner, like using a modal and saving in custom fields instead.

EDIT: I think I’ve misunderstood :smile: if you’re talking about changes appearing in the search result, the answer is no (nor will the HTML appear).

1 Like