Skip to content

[NodeBB] Import custom Font

Solved Customisation
12 3 3.3k 1
  • Hi @phenomlab

    I have a custom Font and I would like to use it for my Title Website.
    The font are not in Google Font.

    I was test @fontface with fontSquirel but it doesn’t work
    no problem with google fonts

    Have you got a solution ?

    Thanks for your precious help.

  • Hi @phenomlab

    I have a custom Font and I would like to use it for my Title Website.
    The font are not in Google Font.

    I was test @fontface with fontSquirel but it doesn’t work
    no problem with google fonts

    Have you got a solution ?

    Thanks for your precious help.

    @downpw yes. Are you hosting the font yourself or from a remote source ?

  • Myself on my server

  • @downpw said in [NodeBB] Import custom Font:

    Myself on my server

    Test to create Woff files with https://transfonter.org

    no chance 😞

    I search again

  • @downpw said in [NodeBB] Import custom Font:

    Myself on my server

    Test to create Woff files with https://transfonter.org

    no chance 😞

    I search again

    @downpw can you provide the path where the font files are stored ? Feel free to use PM if you’d prefer. As long as they are in the same relative path as the NodeBB assets, this should work but will need some custom css.

  • @phenomlab

    Check yourPM 😉

  • Hi @phenomlab

    I have a custom Font and I would like to use it for my Title Website.
    The font are not in Google Font.

    I was test @fontface with fontSquirel but it doesn’t work
    no problem with google fonts

    Have you got a solution ?

    Thanks for your precious help.

    @downpw Typically, it should work as per the below example

    @font-face {
      font-family: 'Poppins';
      font-style: normal;
      font-weight: 400;
      font-display: swap;
      src: url('/assets/fonts/poppins-v15-latin-regular.eot');
      src: local(''),
           url('/assets/fonts/poppins-v15-latin-regular.woff2') format('woff2'), 
           url('/assets/fonts/poppins-v15-latin-regular.woff') format('woff'),
           url('/assets/fonts/poppins-v15-latin-regular.ttf') format('truetype'),
    }
    @font-face {
      font-family: 'Syncopate';
      font-style: normal;
      font-weight: 400;
      src: url('/assets/fonts/syncopate-v12-latin-regular.eot');
      src: local(''),
           url('/assets/fonts/syncopate-v12-latin-regular.eot?#iefix') format('embedded-opentype'),
           url('/assets/fonts/syncopate-v12-latin-regular.woff2') format('woff2'),
           url('/assets/fonts/syncopate-v12-latin-regular.woff') format('woff'), 
           url('/assets/fonts/syncopate-v12-latin-regular.ttf') format('truetype'),
           url('/assets/fonts/syncopate-v12-latin-regular.svg#Syncopate') format('svg');
    }
    
  • Looking further at this, if we decide to use

    @font-face {
      font-family: 'Exodar';
      font-style: normal;
      font-weight: 400;
      src: url('https://domain.net/assets/customfonts/Exodar.eot');
      src: local(''),
           url('https://domain.net/assets/customfonts/Exodar.eot?#iefix') format('embedded-opentype'),
           url('https://domain.net/assets/customfonts/Exodar.woff2') format('woff2'),
           url('https://domain.net/assets/customfonts/Exodar.woff') format('woff'), 
           url('https://domain.net/assets/customfonts/Exodar.ttf') format('truetype'),
           url('https://domain.net/assets/customfonts/Exodar.svg#Exodar') format('svg');
    }
    

    Then this will fail because of the CORS policy at the remote end - see below from the console log

    xx.xx.xxx.xxx/:1 Access to font at 'https://domain.net/assets/customfonts/Exodar.woff2' from origin 'http://xx.xx.xxx.xxx:32775' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    domain.net/assets/customfonts/Exodar.woff2:1 GET https://domain.net/assets/customfonts/Exodar.woff2 net::ERR_FAILED
    xx.xx.xxx.xxx/:1 Access to font at 'https://domain.net/assets/customfonts/Exodar.woff' from origin 'http://xx.xx.xxx.xxx:32775' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    domain.net/assets/customfonts/Exodar.woff:1 GET https://domain.net/assets/customfonts/Exodar.woff net::ERR_FAILED
    xx.xx.xxx.xxx/:1 Access to font at 'https://domain.net/assets/customfonts/Exodar.ttf' from origin 'http://xx.xx.xxx.xxx:32775' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    domain.net/assets/customfonts/Exodar.ttf:1 GET https://domain.net/assets/customfonts/Exodar.ttf net::ERR_FAILED
    

    To work around this

    1. Copy the font files to the local server (they need to be in the assets location for the server to be able to see them)
    2. Modify the CSS block so that it looks like the below
    @font-face {
      font-family: 'Exodar';
      font-style: normal;
      font-weight: 400;
      src: url('/assets/customfonts/Exodar.eot');
      src: local(''),
           url('/assets/customfonts/Exodar.eot?#iefix') format('embedded-opentype'),
           url('/assets/customfonts/Exodar.woff2') format('woff2'),
           url('/assets/customfonts/Exodar.woff') format('woff'), 
           url('/assets/customfonts/Exodar.ttf') format('truetype'),
           url('/assets/customfonts/Exodar.svg#Exodar') format('svg');
    }
    

    Then, you should be able to target any CSS class with

    font-family: "Exodar";
    

    Just be aware of the capital “e” / “E” when referring to the fonts as Linux is case sensitive, whereas Windows is not.

  • Great, that worked !

    I understand better why it didn’t work.

    The error in the log “No 'Access-Control-Allow-Origin” is may be due to my nginx config.

    But specify directly in the assets without specifying the domain name suits me just fine.

    It’s even simpler at the syntax level 🙂

  • DownPWundefined DownPW has marked this topic as solved on
  • Great, that worked !

    I understand better why it didn’t work.

    The error in the log “No 'Access-Control-Allow-Origin” is may be due to my nginx config.

    But specify directly in the assets without specifying the domain name suits me just fine.

    It’s even simpler at the syntax level 🙂

    @downpw plus the fact that it’s up to 5 less html requests your server needs to perform which means less resources are being used.

    It’s always best to host fonts locally from the performance perspective.

  • Looking further at this, if we decide to use

    @font-face {
      font-family: 'Exodar';
      font-style: normal;
      font-weight: 400;
      src: url('https://domain.net/assets/customfonts/Exodar.eot');
      src: local(''),
           url('https://domain.net/assets/customfonts/Exodar.eot?#iefix') format('embedded-opentype'),
           url('https://domain.net/assets/customfonts/Exodar.woff2') format('woff2'),
           url('https://domain.net/assets/customfonts/Exodar.woff') format('woff'), 
           url('https://domain.net/assets/customfonts/Exodar.ttf') format('truetype'),
           url('https://domain.net/assets/customfonts/Exodar.svg#Exodar') format('svg');
    }
    

    Then this will fail because of the CORS policy at the remote end - see below from the console log

    xx.xx.xxx.xxx/:1 Access to font at 'https://domain.net/assets/customfonts/Exodar.woff2' from origin 'http://xx.xx.xxx.xxx:32775' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    domain.net/assets/customfonts/Exodar.woff2:1 GET https://domain.net/assets/customfonts/Exodar.woff2 net::ERR_FAILED
    xx.xx.xxx.xxx/:1 Access to font at 'https://domain.net/assets/customfonts/Exodar.woff' from origin 'http://xx.xx.xxx.xxx:32775' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    domain.net/assets/customfonts/Exodar.woff:1 GET https://domain.net/assets/customfonts/Exodar.woff net::ERR_FAILED
    xx.xx.xxx.xxx/:1 Access to font at 'https://domain.net/assets/customfonts/Exodar.ttf' from origin 'http://xx.xx.xxx.xxx:32775' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    domain.net/assets/customfonts/Exodar.ttf:1 GET https://domain.net/assets/customfonts/Exodar.ttf net::ERR_FAILED
    

    To work around this

    1. Copy the font files to the local server (they need to be in the assets location for the server to be able to see them)
    2. Modify the CSS block so that it looks like the below
    @font-face {
      font-family: 'Exodar';
      font-style: normal;
      font-weight: 400;
      src: url('/assets/customfonts/Exodar.eot');
      src: local(''),
           url('/assets/customfonts/Exodar.eot?#iefix') format('embedded-opentype'),
           url('/assets/customfonts/Exodar.woff2') format('woff2'),
           url('/assets/customfonts/Exodar.woff') format('woff'), 
           url('/assets/customfonts/Exodar.ttf') format('truetype'),
           url('/assets/customfonts/Exodar.svg#Exodar') format('svg');
    }
    

    Then, you should be able to target any CSS class with

    font-family: "Exodar";
    

    Just be aware of the capital “e” / “E” when referring to the fonts as Linux is case sensitive, whereas Windows is not.

    @phenomlab i m using like below;

    @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap');
    body {
    font-family: 'Poppins', sans-serif;
    
    }
    

    But how can i play font size 14px or 15px or 16px etc cant find.

  • @phenomlab i m using like below;

    @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap');
    body {
    font-family: 'Poppins', sans-serif;
    
    }
    

    But how can i play font size 14px or 15px or 16px etc cant find.

    @cagatay you’ll need to define this in the body tag (or another element if you want greater or more granular targets) - for example

    body {
    font-family: "Poppins";
    font-size: 16px;
    }
    

    Essentially, you use the font-size CSS directive.


Did this solution help you?
Did you find the suggested solution useful? Support 💗 Sudonix with a coffee
If your organisation needs deeper expertise around infrastructure, security, or technology leadership, learn more about Phenomlab Ltd. Many of the deeper technical guides behind Sudonix are published there.

Related Topics
  • CSS codes to prevent votes to disappear

    Solved Customisation nodebb
    3
    1 Votes
    3 Posts
    1k Views
    @phenomlab yes, it is thanks a lot…
  • Block Domain

    Solved Let's Build It code javascript block domain nodebb
    26
    1 Votes
    26 Posts
    7k Views
    Yes ogproxy too is functionnal on dev
  • Adding a banner to chat messages

    Tips banner custom
    38
    1
    18 Votes
    38 Posts
    10k Views
    @phenomlab said: @DownPW Possible, yes, but not using the existing code. It would need to be changed to test for multiple entries based on two distinct widget areas. This should work (it’s already applied on your DEV server) function chatBanner() { var roomName = $("h5[component='chat/header/title']").text().trim(); var roomNameWidget = $('[id*="chat-modal"] .btn-ghost.btn-sm.dropdown-toggle').text().trim(); var bannerContent; if (roomName === "General" || roomNameWidget === "General") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Message 1. </div>'; } else if (roomName === "Support" || roomNameWidget === "Support") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Message 2.</div>'; } else if (roomName === "Info" || roomNameWidget === "Info") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Message 3</div>'; } else if (roomName === "xxxxxx" || roomNameWidget === "xxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Message 4</div>'; } else if (roomName === "xxxxxx" || roomNameWidget === "xxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Message 5</div>'; } else if (roomName === "xxxxxx" || roomNameWidget === "xxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Message 6</div>'; } else { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Ce canal est une discussion privée. </div>'; } var chatMessagesContainer = $('[component="chat/system-message"]:last-of-type'); //var existingMessages = $('[component="chat/message"]'); var existingMessages = $('[component="chat/composer"]'); if (existingMessages.length === 0) { // If there are no messages, append the banner to the messages container chatMessagesContainer.first().after(bannerContent); } else { // If there are messages, add the banner after the last message //existingMessages.last().after(bannerContent); existingMessages.before(bannerContent); } } Here, we are using || which is essentially an OR operator. Because we cannot know the chat room ID in advance, it is necessary to use a wildcard to track it [id*="chat-modal"] .btn-ghost.btn-sm.dropdown-toggle I see bugs with this code and chat box widget I use on my categories page What was happening NodeBB allows multiple chat windows to be open simultaneously , the widget and the full/modal-page DM view. Both exist in the DOM at the same time. The original code used global jQuery selectors like $(‘[component=“chat/composer”]’) which scanned the entire page and found elements from both chat windows at once. When you opened “XY” caht while “XXY” was still open in the widget, the selectors would pick up the wrong room name or inject the banner into the wrong window. The key discovery was that the action:chat.loaded event passes the modal DOM element directly as data. By wrapping it in $(data) and using $modal.find(…) for every selector, all queries are scoped exclusively to the correct modal, making it impossible for two open chat windows to interfere with each other. FIX code (to adapt to your rooms) : function chatBanner(modalElement) { var $modal = $(modalElement); $modal.find('#chatbanner').remove(); var roomName = $modal.find('[component="chat/room/name"]').text().trim(); if (!roomName) { var placeholder = $modal.find('[component="chat/input"]').attr('placeholder') || ''; roomName = placeholder.replace(/^Message #?/, '').trim(); } var bannerContent; if (roomName === "General") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } else if (roomName === "xxxxxxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } else if (roomName === "xxxxxxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } else if (roomName === "xxxxxxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } else if (roomName === "xxxxxxxxxx") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } else if (roomName === "Les geeks de l'espace") { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } else { bannerContent = '<div id="chatbanner"><i class="fa fa-fw fa-circle-info link-primary" aria-hidden="true"></i> Chat message banner</div>'; } $modal.find('[component="chat/composer"]').first().before(bannerContent); } $(window).on('action:chat.loaded', function(ev, data) { chatBanner(data); });
  • Composer options on nodebb

    Solved Configure nodebb
    8
    1
    3 Votes
    8 Posts
    2k Views
    @Panda You should be able to expose the CSS for these using F12 to get into console [image: 1686833368846-3591518c-e3a3-4ada-a43c-6b32a5e0359c-image.png] [image: 1686833399483-a2b8ed46-4157-4ff2-85f0-576543380107-image.png] That should then expose the element once selected [image: 1686833431032-89d9c545-a47a-40d1-98f4-80cf3b958e8f-image.png] Here’s the below CSS you need based on the screenshot provided. .composer .formatting-bar .formatting-group li[data-format="picture-o"], .composer .formatting-bar .formatting-group li[data-format="spoiler"] { display: none; }
  • Custom badges

    Solved Customisation nodebb
    103
    2
    49 Votes
    103 Posts
    34k Views
    Perfect
  • Bootstrap Version

    Solved Customisation nodebb bootstrap
    8
    5 Votes
    8 Posts
    2k Views
    @phenomlab That will be nice once they have completed that. It will be interesting to see how long that takes. So for now I will use custom css to make it look the way I want. Frameworks just make things a little faster. Thanks @phenomlab
  • Social icon (Nodebb)

    Solved Customisation nodebb social
    7
    0 Votes
    7 Posts
    2k Views
    @phenomlab said in Social icon (Nodebb): @jac I just tested my theory around using the OG image, and according to the Twitter card validator, it works fine [image: 1638880098289-73e805e1-997b-41bf-9259-51c5052ca8fc-image.png] fixed
  • nodebb-plugin-customize error

    Solved Customisation
    25
    2 Votes
    25 Posts
    11k Views
    @phenomlab it work, thanks