ChristophP / elm-mark / String.Mark

This library gives you the tools you need to mark and highlight text according to one or multiple search terms. You can use the defaults or configure the search with options.

Marking

mark : String -> String -> List (Html msg)

Highlight search terms within text.

import String.Mark as Mark

main =
    Html.p [] <| Mark.mark "ness" "Tennessee"

-- will render <p>Ten<mark>ness</mark>ee</p>

mark uses sensible defaults. Check out what they are in detail here. It is simply defined as:

mark : String -> String -> List (Html msg)
mark =
    markWith defaultOptions

If you need more flexibility, use markWith for custom options.

markWith : Options a -> String -> String -> List a

Customize how marking works. Check out the options for what can be configured.

import String.Mark as Mark exposing (defaultOptions, normalSearch, matchCase)

-- for example make searching case sensitive
let
  options = { defaultOptions | searchType = normalSearch matchCase }
in p [] <| Mark.markWith options "iss" "MissISSippi"

-- will render <p>M<mark>iss</mark>ISSippi</p>

Options


type alias Options a =
{ searchType : SearchType
, whitespace : Whitespace
, minTermLength : Basics.Int
, mapHit : String -> a
, mapMiss : String -> a 
}

The options you have for configuring the search behavior. searchType is explained here and whitespace is explained here.

minTermLength sets a threshold at which search is performed. As your typing you may not want to show hits for 1 or 2 letters so the default is 3, but you can set it to whatever you want.

You want to mark your hits and misses somehow. mapHit and mapMiss let you do just that. By default misses are plain text and hits will be put into <mark> tags. Customize it, if you want to.

defaultOptions : Options (Html msg)

The options used for mark. The defaults should work for most simple use cases. If you only want to slightly tweak these options, you can use them with the record update syntax. The default values are the following:

defaultOptions : Options (Html msg)
defaultOptions =
    { searchType = normalSearch ignoreCase
    , whitespace = singleWord
    , minTermLength = 3
    , mapHit = \hit -> Html.mark [] [ Html.text hit ]
    , mapMiss = Html.text
    }

This means that search will ignore case, the search term will be treated as a single word (including whitespace) and no matches will be generated if the search term is under three characters. Also, hits will be transformed into into <mark> tags misses into plain HTML text.


type SearchType

Which search type are you using? Pick between ease of use and custom flexibility. You probably wanna start with normalSearch.

normalSearch : Case -> SearchType

Normal search is the easiest to start with. You can configure case sensitivity. If you need lots of flexibility, use customSearch.

customSearch : (String -> String -> List ( Basics.Int, Basics.Int )) -> SearchType

Custom search will let you pass your own searching logic. Check first if the provided options for case sensitivity, minimum search term length and single/multi line search are enough for you. But maybe you need glob search, or quoting like Google has, etc. If so, you can provide a function, which gets the search term and the text to search as arguments. Your function needs to return a list of index pairs where each marks the start and end index of a match. You need to make sure the returned indexes are sane. Sane in this case means the index pairs are ...

  1. sorted ascendingly: Meaning for every index pair, the end index is always smaller than the start index of the next pair.

    For example: [(3, 4), (0, 2)] is not sorted, [(0, 2), (3, 4)] is sorted. You can verify

  2. non-overlapping: The ranges expressed by the pairs don't overlap.

    For example: [(1, 3), (2, 4)] overlap at 2-3, [(1, 3), (5, 6)] don't.

You can use funtions like String.indexes, Regex.find or all the good stuff in elm/parser for your implementation. You can find an example for a basic glob search in the tests for this package.

Hints for usage with Whitespace

Say your search term is "typed functional programming". When using singleWord your custom function is called once and passed the entire search term "typed functional programming". However, when using multiWord your custom search function is called 3 times: With "typed", "functional" and "programming" as search terms respectively. The results will be combined. When using multiWord the result will be sorted and filtered for overlaps automatically.


type Case

Match or ignore case. Use with normalSearch.

ignoreCase : Case

Search will ignore case (case insensitive).

matchCase : Case

Search will match case (case sensitive).


type Whitespace

This option lets you configure how whitespace in search terms is treated. In some cases you may want to search for the string "functional programming" and want results to have both of those words in them, in that order. Other times you may wanna search for "Leonardo Raphael Donatello" and search for occurances of either one of those.

singleWord : Whitespace

Will treat the search term as ONE. This means "Peter Parker" will match occurances of "Peter Parker" or "peter parker" in the content(depending on your case settings). You can also search for whole sentences with this option, like "To infinity and beyond" and the entire sentence will be marked as a hit if it occurs in the text you're searching.

multiWord : Whitespace

Will treat the searchterm as MULTIPLE. This means "Peter Parker" would be split into the words "Peter" and "Parker". This will run a search for each word in your search term separately. If hits are overlapping then the one that starts later in the text will be ignored and not marked.