(Retired) Use an ID in a custom user field to link to a user's external profile

Let’s say you want to add a link of User’s main profile page on Discourse user profile page and user card, clicking on that link will take user to main (external) website.

The link of main website user profile page will be like:

http://myawesomewebsite.com/user/USER_PROFILE_ID

Let’s get started!

Create a custom user field

Let’s create a user field with name User Profile.

Make sure “Show on public profile?” option is enabled.

The above field will store external user profile IDs.

Add custom CSS

Paste this CSS code in Admin > Customize > CSS/HTML > CSS section:

.public-user-field.user-profile {
  display: none;
}

#user-card .metadata h3 {
  float: left;
}

h3.user-card-public-field {
 clear: both;   
}

Feel free to further customize above CSS as per your requirement.

If your field name is different than User Profile make sure you replace .public-user-field.user-profile with the dasherized version of your field name.

Add custom JS

Paste this JS code in Admin > Customize > CSS/HTML > </head> section:

<script type="text/discourse-plugin" version="0.8.7">
    const Site = require("discourse/models/site");

    api.registerConnectorClass('user-profile-primary', 'external-site-link', {
      setupComponent(args, component) {
        component.set('externalSiteLink', args.model.get('externalSiteLink'));
      }
    });

    api.registerConnectorClass('user-card-metadata', 'external-site-link', {
      setupComponent(args, component) {
        component.set('externalSiteLink', args.user.get('externalSiteLink'));
      }
    });

    api.modifyClass('model:user', {
      externalSiteLink: function() {
          const siteUserFields = Site.currentProp('user_fields');
          if (!Ember.isEmpty(siteUserFields)) {
            const externalUserIdField = siteUserFields.filterBy('name', 'User Profile')[0]
            if (!externalUserIdField) {
              return null;
            }
            const userFieldId = externalUserIdField.get('id');
            const userFields = this.get('user_fields');
            if (userFields && userFields[userFieldId]) {
              const url = "http://myawesomewebsite.com/user/" + userFields[userFieldId];
              const link = "<a href='"+url+"' target='_blank'>"+url+"</a>";
              return Ember.Object.create({ link, name: externalUserIdField.get('name') });
            } else {
              return null;
            }
          }
      }.property('user_fields.@each.value')
    });
</script>

<script type='text/x-handlebars' data-template-name='/connectors/user-profile-primary/external-site-link'>
  {{#if externalSiteLink}}
    <div class="public-user-fields">
      <div class="public-user-field">
        <span class="user-field-name">{{externalSiteLink.name}}</span>:
        <span class="user-field-value">{{{externalSiteLink.link}}}</span>
      </div>
    </div>
  {{/if}}
</script>

<script type='text/x-handlebars' data-template-name='/connectors/user-card-metadata/external-site-link'>
  {{#if externalSiteLink}}
    <h3 class="user-card-public-field">
      <span class="user-field-name">{{externalSiteLink.name}}</span>:
      <span class="user-field-value">{{{externalSiteLink.link}}}</span>
    </h3>
  {{/if}}
</script>

Update:

const url = "http://myawesomewebsite.com/user/" + userFields[userFieldId];

with your website user profile link.

If your field name is different than User Profile make sure you update:

const externalUserIdField = siteUserFields.filterBy('name', 'User Profile')[0]

with your field name.

Voilà :tada:

That’s it, you will now see User Profile link on user profile page:

and user card:

22 Likes

Let’s say you want to add link on user field that contains Twitter username, clicking on username should take user to Twitter profile. Let’s do it!

Create a custom user field

Create a new user field with name Twitter that will store user’s Twitter username.

Make sure “Show on public profile?” option is enabled.

Add custom CSS

Paste this CSS code in Admin > Customize > CSS/HTML > CSS section:

.public-user-field.twitter {
  display: none;
}

#user-card .metadata h3 {
  float: left;
}

h3.user-card-public-field {
 clear: both;   
}

Add custom JS:

Paste this JS code in Admin > Customize > CSS/HTML > </head> section:

<script type="text/discourse-plugin" version="0.8.7">
    api.registerConnectorClass('user-profile-primary', 'twitter-link', {
      setupComponent(args, component) {
        component.set('twitterLink', args.model.get('twitterLink'));
      }
    });

    api.registerConnectorClass('user-card-metadata', 'twitter-link', {
      setupComponent(args, component) {
        component.set('twitterLink', args.user.get('twitterLink'));
      }
    });

    api.modifyClass('model:user', {
      twitterLink: function() {
          const siteUserFields = Discourse.Site.currentProp('user_fields');
          if (!Ember.isEmpty(siteUserFields)) {
            const twitterUsernameField = siteUserFields.filterBy('name', 'Twitter')[0]
            if (!twitterUsernameField) {
              return null;
            }
            const userFieldId = twitterUsernameField.get('id');
            const userFields = this.get('user_fields');
            if (userFields && userFields[userFieldId]) {
              const url = "https://twitter.com/" + userFields[userFieldId];
              const link = "<a href='"+url+"' target='_blank'>@"+userFields[userFieldId]+"</a>";
              return Ember.Object.create({ link, name: twitterUsernameField.get('name') });
            } else {
              return null;
            }
          }
      }.property('user_fields.@each.value')
    });
</script>

<script type='text/x-handlebars' data-template-name='/connectors/user-profile-primary/twitter-link'>
  {{#if twitterLink}}
    <div class="public-user-fields">
      <div class="public-user-field">
        <span class="user-field-name">{{twitterLink.name}}</span>:
        <span class="user-field-value">{{{twitterLink.link}}}</span>
      </div>
    </div>
  {{/if}}
</script>

<script type='text/x-handlebars' data-template-name='/connectors/user-card-metadata/twitter-link'>
  {{#if twitterLink}}
    <h3 class="user-card-public-field">
      <span class="user-field-name">{{twitterLink.name}}</span>:
      <span class="user-field-value">{{{twitterLink.link}}}</span>
    </h3>
  {{/if}}
</script>

Voilà :tada:

That’s it, you will now see Twitter link on user profile page:

and user card:

17 Likes

This is very cool, thanks!

Would be great to present these links as simply icons eg to Twitter and website and Facebook.

1 Like

You can. As you build the html it renders. You can simply use font awesome on the a tag and remove the text inside it

1 Like

How do I get the external_id from the SSO?

You will have to save the user external_id in custom field when creating SSO record. See example here:

https://github.com/discourse/discourse/blob/a3b2b4bacac92b0dc5ceaf6892f782f4b5584a57/spec/controllers/session_controller_spec.rb#L52-L57

Or you can fill the custom user field via sync_sso method. For example:

client.sync_sso(
  sso_secret: "discourse_sso_secret",
  name: "Test Name",
  username: "test_name",
  email: "name@example.com",
  external_id: "2"
  custom.user_profile_id: "2"
)

Relevant code here.

3 Likes

Ok, so no way to get to single_sign_on_record then?

Am I understading the code you referred to correctly, in that I should supply a field named “custom_external_id” with my SSO - then it will save to the custom field “custom_external_id” (it has to be prefixed “custom”), right?

Is there an easier way to traverse all users and set the new custom field, other than to write a script that calls sync_sso on the thousands of users? It seems a lot of work to get to a field we already have in the system :slight_smile:

I realize it’s a more general approach.
But why don’t make a “link template” setting for a custom field that, if set, will generate the link?

In this case, all you need to do is just set the setting to something like this:

http://myawesomewebsite.com/user/{{field}}


It would also allow the following scenario.
In our Discourse, goat farmers can have their “personal page” topic.
There’s also a special user field where they can enter the full URL to their personal page.

To make it clickable, I’d just use this setting and put {{field}} in it.

2 Likes

It would be great if someone could make a plug-in that showing a custom field in user profile as a link.:wink:

3 Likes

@techAPJ thanks for this - it was exactly what I was looking for. I’m planning to make a modified version it so it can be used for GitHub, which I’ll post when I’ve done.

I agree with the post above in that a {{templating}} type of option would be helpful in making these additional-fields-that-are-really-links more flexible. There are quite a few use cases: GitHub, LinkedIn, Facebook, Twitter, Medium, etc and I wouldn’t want to have to embed custom code for each one.

M

3 Likes

Hi @techAPJ, I am new to discourse customizing but attempting your tutorial at our forum.

To begin, I’ve used the same custom field name as yours (User Profile) and followed rest of the tutorial. However, I am not seeing any links being added to user cards or public profiles.

We are using SSO to register users to the forum but that should not affect getting a link with user ID right? Could you please check the implementation here. Thanks in advance for any suggestions as to what I am doing wrong.

Hi Mike,

I just updated the guide to work with latest Discourse version, try going through the steps again with updated code and everything should be good.

4 Likes

Thanks, @techAPJ.
I followed the tutorial 3 times to make sure I did not miss any instructions but I still can’t get the external profile URL to show up in user profile page or user card. I kept your field name as is to minimize alterations to the code. So the only change I did was to replace ‘myawesomewebsite’.

Do you know what could be the issue?

I am seeing this warning in console on your site:

Plugin API v0.6 is not supported

You need to be on latest Discourse version.

1 Like

@techAPJ Updated Discourse and all components now. And I no longer see console errors. However, the links are still not visible.

I also noticed the guide haven’t changed from what I did last time (28d ago), were your changes done via the discourse code itself? Thanks again for your help so far.

The issue was happening because this guide is not for linking a username to the main site.
If you are looking for such functionality please refer to this new guide

3 Likes

This is really nifty, I’ve added two fields users can fill, Twitch and Steam. Eventually might even add more.
I’m now trying to have the link displayed as fa icon. This is working, but this way it will display every icon on a new line, so I’ve been trying to combine this, but I’m having issues with it.

<script type="text/discourse-plugin" version="0.6">
    const User = api.container.lookupFactory('model:user');

    api.registerConnectorClass('user-profile-primary', 'steam-link', {
      setupComponent(args, component) {
        component.set('steamLink', args.model.get('steamLink'));
      }
    });

    api.registerConnectorClass('user-card-metadata', 'steam-link', {
      setupComponent(args, component) {
        component.set('steamLink', args.user.get('steamLink'));
      }
    });
    
    api.registerConnectorClass('user-profile-primary', 'twitch-link', {
      setupComponent(args, component) {
        component.set('twitchLink', args.model.get('twitchLink'));
      }
    });

    api.registerConnectorClass('user-card-metadata', 'twitch-link', {
      setupComponent(args, component) {
        component.set('twitchLink', args.user.get('twitchLink'));
      }
    });

This part isn’t that hard, the question is, how do I combine the data, and how do I configure an and/or statement so it also works if a user only added one of the two fields. And, if I add more fields in the future, how I would add them as well.

I figured, if this all happens before I build the HTML, it will be easier to add all icons on the card and profile on one line. :slight_smile:

Anyone with some tips/insights? Right now every field goes through something like this:

<script type='text/x-handlebars' data-template-name='/connectors/user-profile-primary/twitch-link'>
  {{#if twitchLink}}
    <div class="public-user-fields">
      <div class="public-user-field">
        <span class="user-field-value">{{{twitchLink.link}}}</span>
      </div>
    </div>
  {{/if}}
</script>

<script type='text/x-handlebars' data-template-name='/connectors/user-card-metadata/twitch-link'>
  {{#if twitchLink}}
    <h3 class="user-card-public-field">
      <span class="user-field-value">{{{twitchLink.link}}}</span>
    </h3>
  {{/if}}
</script>

How do I tell the {{#if twitchLink}} that it also needs to check for Steam (and maybe Twitter, Facebook, etc. etc.), and display all the icons on one line?

This is the way it is now, so it kind of works, but I would like to have the icons on one line. Because they are generated separately, there are emberxxx classes created, with div’s/span’s with the same name…

What would be the best way to have these icons “generated” on one line?

2 Likes

It sounds like you’re doing something like this currently:

<script type='text/x-handlebars'>
  {{#if twitchLink}}
     [...]
  {{/if}}
</script>

<script type='text/x-handlebars'>
  {{#if steamLink}}
    [...]
  {{/if}}
</script>

If instead you do this:

<script type='text/x-handlebars'>
  {{#if twitchLink}}
     [...]
  {{/if}}
  {{#if steamLink}}
    [...]
  {{/if}}
</script>

You can then simply set the style attribute on the outermost div or h3 to display: inline-block;, so, something like this:

{{#if twitchLink}}
  <div class="public-user-fields" style="display: inline-block;">
     [...]

(Or just use elements which are naturally inline).

If you’re looking for more inspiration, this gist should do what you want:

Change the object on line 4 of head.html to change the row of icons, and on line 50 to change the icon & text combo which appears next to the location/site.

11 Likes

Awesome! Gonna check that out later on my desktop. I’ve managed to get the icons on one line through some styling, that screenshot looks a lot better though. Thanks! I’ll post back later! :heart:

1 Like