vue3-replacer

A text replacer component for Vue 3.

Usage no npm install needed!

<script type="module">
  import vue3Replacer from 'https://cdn.skypack.dev/vue3-replacer';
</script>

README

A text replacer component for Vue 3.

Requires Vue 3 as peer-dependency.

Installation

Install it from npm.

npm install vue3-replacer
pnpm add vue3-replacer
yarn add vue3-replacer

Usage

Import the component and use it.

By default Replacer splits given text from white spaces (keeps whitespaces) to a string[], and by default Replacer understands mention (@someone), url (https://url.com) and hashtags (#JusticeForSomeone).

<script setup>
import { Replacer } from 'vue3-replacer';

import Mention from './components/Mention.vue';
import Hashtag from './components/Hashtag.vue';
</script>

<template>
   <Replacer
      text="Sample text https://yandex.com https://google.com @hello #JusticeForSomething"
   >
      <!-- Text will resolve #JusticeForSomething> -->
      <template #mention="text">
         <Mention>{{ text }}</Mention>
      </template>

      <!-- text will resolve https://yandex.com and then https://google.com> -->
      <template #url="text">
         <RouterLink :to="text">{{ text.slice(0, 12) }}</RouterLink>
      </template>

      <!-- text will resolve #JusticeForSomething -->
      <template #hashtag="text">
         <Hashtag>{{ text }}</Hashtag>
      </template>

      <!-- 
         If you want to resolve plain text a.k.a unmatched texts, you can 
         text can be whitespace too, ' '.
      -->
      <template #text>
         <span style="font-weight: bold">{{ text }}</span>
      </template>
   </Replacer>
</template>

How it works

If you give Hi welcome to @vue3-replacer https://github.com/kadiryazici/vue3-replacer #reallyGood text, Replacer will convert it to array of strings.

const given = 'Hi welcome to @vue3-replacer https://github.com/kadiryazici/vue3-replacer #reallyGood';

const out = [
   'Hi',
   ' ',
   'welcome',
   ' ',
   'to',
   ' ',
   '@vue3-replacer',
   ' ',
   'https://github.com/kadiryazici/vue3-replacer',
   ' ',
   '#reallyGood'
]

and then runs handlers for each text (stops at first match per text). If you have 2 matchers for single text first one will succeed. Queue matters.

Replacer will generate a simple schema like this to resolve texts with slots:

const out = [
   ['Hi', 'text'],
   [' ', 'text'],
   ['welcome', 'text'],
   [' ', 'text'],
   ['to', 'text'],
   [' ', 'text'],
   ['@vue3-replacer', 'mention'],
   [' ', 'text'],
   ['https://github.com/kadiryazici/vue3-replacer', 'url'],
   [' ', 'text'],
   ['#reallyGood', 'hashtag']
]

Extending handlers and splitter.

You can add your own handlers and can change splitters' behavior.

Let's write our own the splliter that only splits by brackets.

<script setup>
import { Replacer } from 'vue3-replacer'
import type { Handler } from 'vue3-replacer';

const addCharacterToLastElement = (array, character) => {
   if(typeof array[array.length - 1] === 'string') {
      array[array.length - 1] += character;
      return;
   }

   array.push(character)
}

/**
 * @param { string } text / this will be given text prop.
 * @returns { string[] } splliter has to return string[] to run handlers per each word/split.
 */
const handleSplit = (text) => {
   /**
      Lets iterate over character by character.
      Create a new string element for brackets and push characters inside the bracket to that
      element. Push an empty string to array when you exit bracket.
   */
   const splittedByBrackets = [...text].reduce((acc, character) => {
      if (character === '(') {
         acc.push('(');
      } 
      else if (character === ')') {
         addCharacterToLastElement(acc, ')');
         acc.push('');
      }
      else {
         addCharacterToLastElement(acc, character);
      }

      return acc;
   }, [])

   /*
      We got: ['Hi how are you? ', '(sad)', ' ', '(happy)', ''], we need to get rid of empty strings.
   */
   return splittedByBrackets.filter(Boolean);
}

// Let's write our own handler for brackets.
// Custom handlers should be an array.
const customHandlers: Handler[] = [
   {
      name: 'brackets', // we will use this name as slot name.
      /*
         we will get each element of the array, we need to return `true` or `false`
         to let Replacer know what it is.

         You can use whatever you want as long as you return boolean.
      */
      check(text) {
         return text.startsWith('(') && text.endsWith(')')
      }
   }
]

</script>

<template>
   <Replacer
      text="Hi how are you? (sad) (happy)"
      :splitter="handleSplit"
      :customHandlers="customHandlers"
   >
      <!-- Lets resolve our brackets -->
      <template #brackets="text">
         <span style="color: purple">{{ text }} </span>
      </template>
   </Replacer>
</template>

Overriding default handlers

By default Replacer has mention | url | hashtag handlers. If you want override these handlers, just call your handler's name with existing ones.

<script>
import { Replacer } from 'vue3-replacer'
import type { Handler } from 'vue3-replacer'

const customHandlers: Handler[] = {
   // Default hashtag is overwritten;
   {
      name: 'hashtag',
      check(text) {
         return text.startsWith('#')
      }
   }
}
</script>

<template>
   <Replacer text="hi mom #good", :customHandlers="customHandlers">
      <template #hashtag="text"> {{ text }} </template>
   </Replacer>
</template>