color #hashtags while typing like in twitter with svelte

Viewed 58

I'm suing svelte and reactivity here is key, I want to color as I type #hashtags in my text, code:

<script> 
let tweet = "";
        function blueColor() {
    tweet = tweet.replace(/<a\b[^>]*>|\B(#[^\W_][\w-]*)/gi, function (m, p) {
        return p ? '<span class="hash_tag">' + m + '</span>' : m;
    });
}
</script>
                               <input
                                    id="message"
                                    rows="4"
                                    bind:value={ tweet }
                                    on:input={ blueColor }
                                    class="block p-2.5 w-full 
                                    rounded-lg border text-white focus:outline-none focus:ring
                                    bg-gray-700 border-gray-600 placeholder-gray-400
                                    hover:border-blue-600"
                                    placeholder="What's happening..."
                                    maxlength="200"
                                />

as you can see I can replace it while I am typing but I get this as output:

as <span class="hash_tag">#hashtag</span>

Do you have any quick solution?

2 Answers

Do you have any quick solution

There is no quick solution to this. The only way to get styled text input is using contenteditable which tends to be highly complex in terms of interaction logic.

Your best bet is look for a library that already handles all of the necessities (handling copy/paste, removing unwanted input & formatting, etc.).

If you want to implement the logic yourself, contenteditable="plaintext-only" can be used to restrict input right away. Synchronizing state correctly, removing/adding the elements necessary for the tags without interfering with user input, might still be complicated, though.

While I agree with @H.B.'s answer, I was asked if the solution to How to place the caret where it previously was after replacing the html of a contenteditable div? could be used with svelte:

(open in Svelte REPL)

<svelte:head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-textrange.js"></script>
</svelte:head>

<script>
    import { tick } from 'svelte';

    let div, html;
    let text = 'Type here';
    $: (async () => {
        if (!window.rangy) return;
        let sel = rangy.getSelection();
        let savedSel = sel.saveCharacterRanges(div);
        console.log(savedSel[0].characterRange, `"${text}"`, `"${html}"`)
        html = text.replace(/(^|\s)(#\w+)/g, " <a href=#>$2</a>");
        await tick();
        sel.restoreCharacterRanges(div, savedSel);
    })();
</script>

<div
    contenteditable
    bind:this={div}
    bind:innerHTML={html}
    bind:textContent={text}
></div>

This is not perfect (e.g. does not handle trailing spaces well in Firefox), only a translation of my other answer to svelte.

Related