Add an element to an indexed object, mutating it. Returns object.
add = (indexed, item) ->
Array.prototype.push.call(indexed, item)
indexed
Generic reduction over anything. This will come in handy for iterating over dom collections and having generic manipulation functions.
reduce = (thing, step, reduction) ->
if not thing? then return reduction
if thing? and not thing.length? then return step(reduction, thing)
reduction = step(reduction, item) for item in thing
return reduction
Generic filter function over anything. Returns a new true array of things that passed the test.
filter = (thing, predicate) ->
step = (array, item) -> if predicate(item) then add(array, item) else array
reduce(thing, step, [])
Generic reject function over anything. Returns a new true array of things that failed the test.
reject = (thing, predicate) ->
step = (array, item) -> if !predicate(item) then add(array, item) else array
reduce(thing, step, [])
Escape special RegExp characters in a string.
escapeRegExp = (string) -> string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")
Same as String.prototype.match
, but always guarantees an array as return value.
match = (string, pattern) -> string.match(pattern) || []
Make a RegExp
objects from an escaped string.
pattern = (string, flags) -> new RegExp(escapeRegExp(string), flags)
Create case insensitive patterns from strings. This is useful for mapping strings.
patterni = (string) -> pattern(string, 'i')
Add or remove an attribute from a single element. If key and value are provided, then attribute will be set. Otherwise attribute will be removed. Returns the element after mutating.
elAttr = (element, key, value) ->
if key and value then element.setAttribute(key, value) else element.removeAttribute(key)
element
Add or remove an attribute from a collection of elements. Returns the element collection after mutating.
attr = (elements, key, value) ->
reduce(elements, (_, element) -> elAttr(element, key, value))
elements
Get the hash fragment from a URL:
extractHashFragment = (url) -> url.substring(url.indexOf('#'))
Creates an array of case-insensitive RegExp patterns from all search terms. Extract search words. Spaces are considered "and" relationship. double-quoted search literals are not supported right now.
parseSearchWordPatterns = (search) ->
# First remove all double-quoted phrases, then split on one or more
# contiguous space characters.
search.trim().split(/\s+|(?:\%20)+/g).map(patterni)
Determine if url contains a search fragment.
hasSearchFragment = (url) ->
extractHashFragment(url).indexOf('#?') is 0
Get search string from URL. Returns string without any garbage.
extractSearchString = (url) ->
if hasSearchFragment(url) then extractHashFragment(url).replace(/^#\?/, '')
else ''
Extract all search patterns from a URL.
parseUrlToSearchPatterns = (url) ->
if hasSearchFragment(url) then parseSearchWordPatterns(extractSearchString(url))
else []
A function that will hide all elements not having match for pattern and show all elements that do match. If nothing matches, everything is shown. Returns the hidden elements.
showElementsMatching = (elements, patterns) ->
hasPattern = (element) ->
stepHasPattern = (isPassing, pattern) ->
if isPassing then element.textContent.search(pattern) isnt -1 else false
patterns.reduce(stepHasPattern, true)
attr(filter(elements, hasPattern), 'hidden')
attr(reject(elements, hasPattern), 'hidden', 'hidden')
Show everything. A simple wrapper around attr
for now. May get fancier if we
go toward a fancy model.
showAll = (elements) -> attr(elements, 'hidden')
Ok, so here's the goal: a link of form #?some text
will hide everything
that doesn't match, replacing hidden nodes with a ...
.
What we've got now: it hides stuff.
foldable = (elements) ->
patterns = parseUrlToSearchPatterns(window.location.hash)
showElementsMatching(elements, patterns)
onkeydown = (event) ->
# 27 = esc
if event.keyCode == 27 then window.location.hash = ''
onhashchange = (event) ->
patterns = parseUrlToSearchPatterns(event.newURL)
showElementsMatching(elements, patterns)
window.addEventListener('hashchange', onhashchange)
window.addEventListener('keydown', onkeydown)
() ->
window.removeEventListener('hashchange', onhashchange)
window.removeEventListener('keydown', onkeydown)