docks_theme_api 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.babelrc +4 -0
- data/.editorconfig +8 -0
- data/.eslintrc +115 -0
- data/.gitignore +24 -0
- data/.rubocop.yml +20 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/README.md +5 -0
- data/Rakefile +3 -0
- data/assets/images/icons.svg +63 -0
- data/assets/scripts/coffeescript/pattern_library_helpers.coffee +8 -0
- data/assets/scripts/javascript/pattern_library_helpers.js +11 -0
- data/assets/scripts/pattern_library.js +10380 -0
- data/assets/scripts/pattern_library_demo.js +0 -0
- data/assets/styles/less/pattern-library-helpers.less +103 -0
- data/assets/styles/pattern-library-demo.css +1882 -0
- data/assets/styles/pattern-library.css +1882 -0
- data/assets/styles/sass/pattern-library-helpers.sass +90 -0
- data/assets/styles/scss/pattern-library-helpers.scss +99 -0
- data/assets/styles/stylus/pattern-library-helpers.styl +90 -0
- data/assets/templates/erb/demo.erb +26 -0
- data/assets/templates/erb/layouts/demo.erb +17 -0
- data/assets/templates/erb/layouts/pattern.erb +76 -0
- data/assets/templates/erb/partials/sidebar.erb +124 -0
- data/assets/templates/erb/partials/symbols/class.erb +1 -0
- data/assets/templates/erb/partials/symbols/demo.erb +40 -0
- data/assets/templates/erb/partials/symbols/factory.erb +70 -0
- data/assets/templates/erb/partials/symbols/function.erb +103 -0
- data/assets/templates/erb/partials/symbols/mixin.erb +62 -0
- data/assets/templates/erb/partials/symbols/variable.erb +59 -0
- data/assets/templates/erb/pattern.erb +102 -0
- data/assets/templates/haml/demo.haml +14 -0
- data/assets/templates/haml/layouts/demo.haml +6 -0
- data/assets/templates/haml/layouts/pattern.haml +38 -0
- data/assets/templates/haml/partials/sidebar.haml +68 -0
- data/assets/templates/haml/partials/symbols/class.haml +1 -0
- data/assets/templates/haml/partials/symbols/demo.haml +23 -0
- data/assets/templates/haml/partials/symbols/factory.haml +38 -0
- data/assets/templates/haml/partials/symbols/function.haml +54 -0
- data/assets/templates/haml/partials/symbols/mixin.haml +31 -0
- data/assets/templates/haml/partials/symbols/variable.haml +22 -0
- data/assets/templates/haml/pattern.haml +54 -0
- data/assets/templates/slim/demo.slim +24 -0
- data/assets/templates/slim/layouts/demo.slim +5 -0
- data/assets/templates/slim/layouts/pattern.slim +48 -0
- data/assets/templates/slim/partials/sidebar.slim +112 -0
- data/assets/templates/slim/partials/symbols/class.slim +1 -0
- data/assets/templates/slim/partials/symbols/demo.slim +30 -0
- data/assets/templates/slim/partials/symbols/factory.slim +57 -0
- data/assets/templates/slim/partials/symbols/function.slim +81 -0
- data/assets/templates/slim/partials/symbols/mixin.slim +45 -0
- data/assets/templates/slim/partials/symbols/variable.slim +35 -0
- data/assets/templates/slim/pattern.slim +63 -0
- data/docks_config.rb +32 -0
- data/docks_theme_api.gemspec +37 -0
- data/gulpfile.js +88 -0
- data/karma.conf.js +6 -0
- data/lib/docks_theme_api/components/base_component.rb +99 -0
- data/lib/docks_theme_api/components/code_block_component.rb +10 -0
- data/lib/docks_theme_api/components/popover_component.rb +15 -0
- data/lib/docks_theme_api/components/table_component.rb +34 -0
- data/lib/docks_theme_api/components/tablist_component.rb +11 -0
- data/lib/docks_theme_api/components.rb +21 -0
- data/lib/docks_theme_api/helpers/ui_helper.rb +69 -0
- data/lib/docks_theme_api/theme.rb +21 -0
- data/lib/docks_theme_api.rb +1 -0
- data/package.json +60 -0
- data/source/behaviors/filterable/filterable.coffee +353 -0
- data/source/behaviors/filterable/filterable.js +0 -0
- data/source/behaviors/filterable/filterable.scss +34 -0
- data/source/behaviors/filterable/package.json +3 -0
- data/source/behaviors/index.js +0 -0
- data/source/components/avatar/avatar.erb +20 -0
- data/source/components/avatar/avatar.js +142 -0
- data/source/components/avatar/avatar.scss +200 -0
- data/source/components/avatar/avatar_container.erb +13 -0
- data/source/components/avatar/package.json +3 -0
- data/source/components/avatar/spec/avatar_spec.js +81 -0
- data/source/components/badge/badge.scss +158 -0
- data/source/components/button/button.scss +213 -0
- data/source/components/card/card.scss +32 -0
- data/source/components/code_block/code-block.scss +353 -0
- data/source/components/code_block/code_block.erb +95 -0
- data/source/components/code_block/code_block.js +444 -0
- data/source/components/code_block/package.json +3 -0
- data/source/components/code_block/spec/code_block_spec.js +10 -0
- data/source/components/demo/demo.js +244 -0
- data/source/components/demo/demo.scss +90 -0
- data/source/components/demo/package.json +3 -0
- data/source/components/exploded/exploded.erb +25 -0
- data/source/components/exploded/exploded.js +694 -0
- data/source/components/exploded/exploded.scss +166 -0
- data/source/components/exploded/package.json +3 -0
- data/source/components/field/field.js +24 -0
- data/source/components/field/field.scss +101 -0
- data/source/components/field/package.json +3 -0
- data/source/components/header/header.scss +33 -0
- data/source/components/iframe/iframe.erb +12 -0
- data/source/components/iframe/iframe.js +381 -0
- data/source/components/iframe/package.json +3 -0
- data/source/components/index.js +37 -0
- data/source/components/inline_group/inline-group.scss +14 -0
- data/source/components/internal_link/internal_link.js +49 -0
- data/source/components/internal_link/package.json +3 -0
- data/source/components/list/list.scss +230 -0
- data/source/components/modal/modal.coffee +84 -0
- data/source/components/modal/modal.erb +19 -0
- data/source/components/modal/modal.js +0 -0
- data/source/components/modal/modal.scss +57 -0
- data/source/components/modal/package.json +3 -0
- data/source/components/notice/notice.scss +48 -0
- data/source/components/popover/package.json +3 -0
- data/source/components/popover/popover.coffee +562 -0
- data/source/components/popover/popover.erb +21 -0
- data/source/components/popover/popover.js +0 -0
- data/source/components/popover/popover.scss +139 -0
- data/source/components/range/range.scss +78 -0
- data/source/components/resizable/package.json +3 -0
- data/source/components/resizable/resizable.erb +30 -0
- data/source/components/resizable/resizable.js +250 -0
- data/source/components/resizable/resizable.scss +245 -0
- data/source/components/resizable/size_buttons.js +249 -0
- data/source/components/scroll_container/package.json +3 -0
- data/source/components/scroll_container/scroll-container.scss +4 -0
- data/source/components/scroll_container/scroll_container.js +24 -0
- data/source/components/section/section.scss +99 -0
- data/source/components/select/package.json +3 -0
- data/source/components/select/select.erb +21 -0
- data/source/components/select/select.js +35 -0
- data/source/components/select/select.scss +163 -0
- data/source/components/table/package.json +3 -0
- data/source/components/table/table.erb +16 -0
- data/source/components/table/table.js +351 -0
- data/source/components/table/table.scss +236 -0
- data/source/components/tablist/package.json +3 -0
- data/source/components/tablist/tablist.erb +13 -0
- data/source/components/tablist/tablist.js +246 -0
- data/source/components/tablist/tablist.scss +191 -0
- data/source/components/tablist/tablist_panel.erb +14 -0
- data/source/components/tablist/tablist_tab.erb +20 -0
- data/source/components/toggle/package.json +3 -0
- data/source/components/toggle/toggle.erb +11 -0
- data/source/components/toggle/toggle.js +211 -0
- data/source/components/toggle/toggle_container.erb +30 -0
- data/source/components/vertical_spacer/vertical-spacer.scss +3 -0
- data/source/components/vertical_stack/vertical-stack.scss +19 -0
- data/source/components/xray/package.json +3 -0
- data/source/components/xray/xray.erb +50 -0
- data/source/components/xray/xray.js +123 -0
- data/source/components/xray/xray.scss +79 -0
- data/source/foundation/app/app.js +15 -0
- data/source/foundation/app/package.json +3 -0
- data/source/pattern-library-demo.scss +13 -0
- data/source/pattern-library.scss +13 -0
- data/source/pattern_library.js +8 -0
- data/source/pattern_library_demo.js +8 -0
- data/source/structures/index.js +11 -0
- data/source/structures/sidebar/package.json +3 -0
- data/source/structures/sidebar/sidebar.js +69 -0
- data/source/structures/sidebar/sidebar.scss +79 -0
- data/source/utilities/builder/builder.js +138 -0
- data/source/utilities/builder/package.json +3 -0
- data/source/utilities/client/client.js +7 -0
- data/source/utilities/client/package.json +3 -0
- data/source/utilities/colors/colors.scss +112 -0
- data/source/utilities/defaults/defaults.scss +38 -0
- data/source/utilities/dom_cache/dom_cache.js +24 -0
- data/source/utilities/dom_cache/package.json +3 -0
- data/source/utilities/events/events.js +25 -0
- data/source/utilities/events/package.json +3 -0
- data/source/utilities/font_sizes/font-sizes.scss +85 -0
- data/source/utilities/foundation/a11y.scss +10 -0
- data/source/utilities/foundation/base.scss +29 -0
- data/source/utilities/foundation/icon.scss +114 -0
- data/source/utilities/foundation/layout.scss +67 -0
- data/source/utilities/foundation/page.scss +39 -0
- data/source/utilities/foundation/type.scss +208 -0
- data/source/utilities/functions/functions.scss +127 -0
- data/source/utilities/keycodes/keycodes.js +23 -0
- data/source/utilities/keycodes/package.json +3 -0
- data/source/utilities/markup/markup.js +90 -0
- data/source/utilities/markup/package.json +3 -0
- data/source/utilities/media/media.scss +172 -0
- data/source/utilities/mixins/mixins.scss +89 -0
- data/source/utilities/naming_convention/naming_convention.js +3 -0
- data/source/utilities/naming_convention/package.json +3 -0
- data/source/utilities/numbers/numbers.js +14 -0
- data/source/utilities/numbers/package.json +3 -0
- data/source/utilities/painting/package.json +3 -0
- data/source/utilities/painting/painting.js +7 -0
- data/source/utilities/pattern/package.json +3 -0
- data/source/utilities/pattern/pattern.js +50 -0
- data/source/utilities/query_string/package.json +3 -0
- data/source/utilities/query_string/query_string.js +24 -0
- data/source/utilities/template/package.json +3 -0
- data/source/utilities/template/template.js +10 -0
- data/source/utilities/text_range/package.json +3 -0
- data/source/utilities/text_range/text_range.js +30 -0
- data/source/utilities/ui_events/package.json +3 -0
- data/source/utilities/ui_events/ui_events.js +85 -0
- data/source/utilities/variables/variables.scss +18 -0
- data/source/utilities/z_indexes/z-indexes.scss +88 -0
- data/source/vendor/array_includes.js +28 -0
- data/source/vendor/highlight.js +1142 -0
- data/source/vendor/index.js +1 -0
- data/source/vendor/matrix.js +399 -0
- data/source/vendor/query_string.js +66 -0
- data/spec/assets/.eslintrc +9 -0
- data/spec/assets/spec_fixture.js +33 -0
- data/spec/assets/spec_helper.js +19 -0
- data/spec/lib/components/base_component_spec.rb +156 -0
- data/spec/lib/components_spec.rb +30 -0
- data/spec/lib/helpers/ui_helper_spec.rb +62 -0
- data/spec/lib/theme_spec.rb +25 -0
- data/spec/spec_helper.rb +15 -0
- data/tasks/gulp/.eslintrc +6 -0
- data/tasks/gulp/browser_sync.js +8 -0
- data/tasks/gulp/code_quality/scripts.js +10 -0
- data/tasks/gulp/config/index.js +116 -0
- data/tasks/gulp/minify/scripts.js +13 -0
- data/tasks/gulp/minify/styles.js +13 -0
- data/tasks/gulp/pattern_library/index.js +5 -0
- data/tasks/gulp/pattern_library/scripts.js +10 -0
- data/tasks/gulp/pattern_library/styles.js +10 -0
- data/tasks/gulp/scripts.js +8 -0
- data/tasks/gulp/spec/scripts.js +11 -0
- data/tasks/gulp/styles.js +17 -0
- data/tasks/gulp/utilities/babel/relative_require.js +22 -0
- data/tasks/gulp/utilities/babel/spec_helper.js +20 -0
- data/tasks/gulp/utilities/browserify_bundler.js +22 -0
- data/tasks/gulp/utilities/handle_errors.js +13 -0
- data/tasks/gulp/watch.js +9 -0
- data/tasks/rake/rspec.rake +7 -0
- data/tasks/rake/rubocop.rake +8 -0
- data/tasks/rake/templates.rake +50 -0
- metadata +470 -0
@@ -0,0 +1,353 @@
|
|
1
|
+
#*
|
2
|
+
# @page Filterable
|
3
|
+
# @group Behavior
|
4
|
+
#
|
5
|
+
# This behavior provides a set of attributes that allow you to establish an
|
6
|
+
# `input`, which acts as the [`Filter`](@link), and any number of connected
|
7
|
+
# [`Filterable`](@link) components. The filter, for its part, specifies
|
8
|
+
# what notes it filters (through the `data-filters` attribute), on what
|
9
|
+
# attribute of the items contained in the filterable it should compare against
|
10
|
+
# (through the `data-filter-attribute` attribute). `checkbox` inputs may also
|
11
|
+
# provide some additional attributes to specify the meaning of checked/
|
12
|
+
# unchecked.
|
13
|
+
#
|
14
|
+
# The `filterable` specifies which of its items should be filtered using its
|
15
|
+
# `data-filter-items` attribute. Filterable containers can have any number of
|
16
|
+
# nested groups, each of which will be independently hidden when all of its
|
17
|
+
# contained items are filtered out.
|
18
|
+
#
|
19
|
+
# This behavior is most commonly used in conjunction with the [`List`](@link list::list)
|
20
|
+
# component, but is generic enough to work with any container that has children.
|
21
|
+
|
22
|
+
CLASSES =
|
23
|
+
HIDDEN: "filter--is-hidden"
|
24
|
+
HIGHLIGHT: "filter--highlight"
|
25
|
+
|
26
|
+
ATTRS =
|
27
|
+
FILTERS: "data-filters"
|
28
|
+
ATTRIBUTE: "data-filter-attribute"
|
29
|
+
ITEMS: "data-filter-items"
|
30
|
+
STATUS: "data-filtered-status"
|
31
|
+
CHECKED_VALUE: "data-checked-value"
|
32
|
+
UNCHECKED_VALUE: "data-unchecked-value"
|
33
|
+
|
34
|
+
STATUS =
|
35
|
+
HIDDEN: "hidden"
|
36
|
+
VISIBLE: "visible"
|
37
|
+
|
38
|
+
#*
|
39
|
+
# Wraps the matching characters in `text` with a span that adds the highlighting
|
40
|
+
# class.
|
41
|
+
#
|
42
|
+
# @private
|
43
|
+
# @param {String} text - The text to match within.
|
44
|
+
# @param {String} match_text - The sequence of strings that show be highlighted
|
45
|
+
# (characters do not to be contiguous in `text`).
|
46
|
+
#
|
47
|
+
# @returns String - The string with highlights applied.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# highlight_characters("foo bar", "o")
|
51
|
+
# # => "f<span class='filter--highlight'>o</span>o bar"
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# highlight_characters("foo bar", "obr")
|
55
|
+
# # => "f<span class='filter--highlight'>o</span>o <span class='filter--highlight'>b</span>a<span class='filter--highlight'>r</span>"
|
56
|
+
|
57
|
+
highlight_characters = (text, match_text) ->
|
58
|
+
return text unless match_text.length
|
59
|
+
|
60
|
+
[start_tag, end_tag] = ["<span class='#{CLASSES.HIGHLIGHT}'>", "</span>"]
|
61
|
+
[match_text_index, match_text_char] = [0, match_text[0]]
|
62
|
+
last_index = 0
|
63
|
+
new_text = ""
|
64
|
+
old_text = text.trim()
|
65
|
+
|
66
|
+
for char, index in old_text
|
67
|
+
if char.toLowerCase() == match_text_char.toLowerCase()
|
68
|
+
new_text += "#{old_text[last_index...index]}#{start_tag}#{char}#{end_tag}"
|
69
|
+
last_index = index + 1
|
70
|
+
match_text_index += 1
|
71
|
+
match_text_char = match_text[match_text_index]
|
72
|
+
|
73
|
+
break unless match_text_char?
|
74
|
+
|
75
|
+
new_text += old_text[last_index..-1]
|
76
|
+
new_text
|
77
|
+
|
78
|
+
|
79
|
+
#*
|
80
|
+
# An individual item that can, conditional on all of the filters that have been
|
81
|
+
# applied against it, be either hidden (at least one filter does not match) or
|
82
|
+
# visible (all filters currently match).
|
83
|
+
#
|
84
|
+
# [`Filter`s](@link Filter) can be applied with the
|
85
|
+
# [`filter_against`](@link FilterableItem#filter_against) method. The last
|
86
|
+
# matching state of all applied filters are recorded, so multiple filters can
|
87
|
+
# be applied to an item and the item will respect all of them.
|
88
|
+
#
|
89
|
+
# @factory
|
90
|
+
#
|
91
|
+
# @param {HTMLElement} node - The node that will be visually hidden/ visible
|
92
|
+
# based on the matching status of all applied filters.
|
93
|
+
|
94
|
+
FilterableItem = (node) ->
|
95
|
+
filters = {}
|
96
|
+
|
97
|
+
Object.create {},
|
98
|
+
#*
|
99
|
+
# The DOM element that this `FilterableItem` represents.
|
100
|
+
#
|
101
|
+
# @type HTMLElement
|
102
|
+
# @property
|
103
|
+
node: value: node
|
104
|
+
|
105
|
+
#*
|
106
|
+
# Indicates whether or not the DOM element does not match any filters it has
|
107
|
+
# been compared against (in which case, the item should be hidden).
|
108
|
+
#
|
109
|
+
# @type Boolean
|
110
|
+
# @property
|
111
|
+
is_hidden: get: -> !@is_visible
|
112
|
+
|
113
|
+
#*
|
114
|
+
# Indicates whether or not the DOM element matches all filters it has
|
115
|
+
# been compared against (in which case, the item should be visible).
|
116
|
+
#
|
117
|
+
# @type Boolean
|
118
|
+
# @property
|
119
|
+
is_visible: get: ->
|
120
|
+
for id, filter_matches of filters
|
121
|
+
return false unless filter_matches
|
122
|
+
|
123
|
+
true
|
124
|
+
|
125
|
+
#*
|
126
|
+
# Checks this item against the passed filter. After the matching status of
|
127
|
+
# the passed filter is updated, it checks all matching states and updates
|
128
|
+
# the `data-filter-status` attribute appropriately.
|
129
|
+
#
|
130
|
+
# @method
|
131
|
+
#
|
132
|
+
# @param {Filter} filter - The filter to apply to this item.
|
133
|
+
# @returns Boolean - The matching state of the passed filter.
|
134
|
+
filter_against: value: (filter) ->
|
135
|
+
matches = filter.test(this)
|
136
|
+
filters[filter.id] = matches
|
137
|
+
node.setAttribute(ATTRS.STATUS, if @is_visible then STATUS.VISIBLE else STATUS.HIDDEN)
|
138
|
+
matches
|
139
|
+
|
140
|
+
#*
|
141
|
+
# Removes the effects of the passed filter and updates the filtered status
|
142
|
+
# of the item accordingly.
|
143
|
+
#
|
144
|
+
# @method
|
145
|
+
#
|
146
|
+
# @param {Filter} filter - The filter to remove.
|
147
|
+
remove_filter: value: (filter) -> filters.delete(filter.id)
|
148
|
+
|
149
|
+
#*
|
150
|
+
# A wrapper around a set of filterable items. The root of this object can have
|
151
|
+
# a `data-filter-items` attribute, which specifies which of its descendants
|
152
|
+
# should be filtered against attached [`Filter`s](@link Filter). If no such
|
153
|
+
# attribute is provided, the container's direct children will be used as the
|
154
|
+
# items.
|
155
|
+
#
|
156
|
+
# When a custom selector is provided via `data-filter-items`, this object will
|
157
|
+
# go through all of its subcomponents, collecting (on each level of the DOM)
|
158
|
+
# items that need to be filtered (those matching the selector) and "nested"
|
159
|
+
# `Filterable`s. The nested components will independently be hideable, and
|
160
|
+
# `Filterable`s higher up the chain will check whether their contained
|
161
|
+
# groups are fully hidden before fully hiding themselves.
|
162
|
+
#
|
163
|
+
# @factory
|
164
|
+
#
|
165
|
+
# @param {HTMLElement} root - The node wrapping all filterable items.
|
166
|
+
# @param {Object} options - The options for initializing this component.
|
167
|
+
#
|
168
|
+
# @param {String} options.selector - The selector for filterable items. This is
|
169
|
+
# used for nested groups inside a root `Filterable` so that they can properly
|
170
|
+
# specify their contained filterable items.
|
171
|
+
#
|
172
|
+
# @param {Boolean} options.nested - Indicates whether the `Filterable` is
|
173
|
+
# nested inside a root `Filterable`. This must be specified in order to prevent
|
174
|
+
# cacheing the `Filterable` on its node.
|
175
|
+
|
176
|
+
|
177
|
+
Filterable = do ->
|
178
|
+
get_items_and_groups = (root, selector) ->
|
179
|
+
children = Array::slice.call(root.children)
|
180
|
+
|
181
|
+
if selector
|
182
|
+
items = []
|
183
|
+
groups = []
|
184
|
+
|
185
|
+
for child in children
|
186
|
+
if $(child).is(selector)
|
187
|
+
items.push(FilterableItem(child))
|
188
|
+
else
|
189
|
+
group = Filterable(child, selector: selector, nested: true)
|
190
|
+
groups.push(group) if group.is_valid
|
191
|
+
else
|
192
|
+
items = (FilterableItem(child) for child in children)
|
193
|
+
groups = []
|
194
|
+
|
195
|
+
[items, groups]
|
196
|
+
|
197
|
+
(root, options = {}) ->
|
198
|
+
selector = options.selector || root.getAttribute(ATTRS.ITEMS)
|
199
|
+
[items, groups] = get_items_and_groups(root, selector)
|
200
|
+
|
201
|
+
api = Object.create {},
|
202
|
+
#*
|
203
|
+
# The node that represents this component in the DOM.
|
204
|
+
#
|
205
|
+
# @property
|
206
|
+
# @type HTMLElement
|
207
|
+
root: get: -> root
|
208
|
+
|
209
|
+
#*
|
210
|
+
# Specifies whether or not this `Filterable`, or any nested `Filterable`s,
|
211
|
+
# have filterable items. It is used to prevent storing references to nodes
|
212
|
+
# that are neither filterable items, nor nested containers of filterable
|
213
|
+
# items (for example, headings in lists).
|
214
|
+
#
|
215
|
+
# @property
|
216
|
+
# @type Boolean
|
217
|
+
is_valid: get: -> items.length > 0 || groups.length > 0
|
218
|
+
|
219
|
+
#*
|
220
|
+
# Specifies whether or not there are any filterable items, either in this
|
221
|
+
# `Filterable` or in any nested `Filterable`s, which are still visible
|
222
|
+
# based on the active filters.
|
223
|
+
#
|
224
|
+
# @property
|
225
|
+
# @type Boolean
|
226
|
+
has_visible_items: get: ->
|
227
|
+
items.some((item) -> item.is_visible) ||
|
228
|
+
groups.some((group) -> group.has_visible_items)
|
229
|
+
|
230
|
+
#*
|
231
|
+
# Applies a filter against all contained items and nested `Filterables`.
|
232
|
+
# Once the filter has been applied, this method also sets the current
|
233
|
+
# filtered state in the `data-filter-status` on the
|
234
|
+
# [`root`](@link Filterable#root) node.
|
235
|
+
#
|
236
|
+
# @method
|
237
|
+
#
|
238
|
+
# @param {Filter} filter - The filter against which all contained items
|
239
|
+
# should be compared.
|
240
|
+
filter_against: value: (filter) ->
|
241
|
+
group.filter_against(filter) for group in groups
|
242
|
+
item.filter_against(filter) for item in items
|
243
|
+
root.setAttribute(ATTRS.STATUS, if @has_visible_items then STATUS.VISIBLE else STATUS.HIDDEN)
|
244
|
+
return
|
245
|
+
|
246
|
+
$(root).data("filterable", api) unless options.nested
|
247
|
+
api
|
248
|
+
|
249
|
+
#*
|
250
|
+
# Returns the cached [`Filterable`](@link) for the passed node, or nothing if
|
251
|
+
# the cached node is not the *root* of a `Filterable`.
|
252
|
+
#
|
253
|
+
# @method
|
254
|
+
# @static
|
255
|
+
#
|
256
|
+
# @param {HTMLElement} node - The node for which a `Filterable` should be
|
257
|
+
# retrieved.
|
258
|
+
#
|
259
|
+
# @returns Filterable
|
260
|
+
|
261
|
+
Filterable.for = (node) -> $(node).data("filterable")
|
262
|
+
|
263
|
+
#*
|
264
|
+
# @factory
|
265
|
+
#
|
266
|
+
# @param {HTMLElement} node - The `input` that filters some set of
|
267
|
+
# [`Filterable`s](@link Filterable). This node **must** have:
|
268
|
+
#
|
269
|
+
# - A `data-filters` attribute, specifying selectors that are the root of
|
270
|
+
# a set of filterable items.
|
271
|
+
#
|
272
|
+
# - A `data-filter-attribute` attribute, specyfing the attribute of filterable
|
273
|
+
# items inside the [`Filterable`](@link) that should be compared against this
|
274
|
+
# filter. This can either be an attribute on all filterable items (i.e.,
|
275
|
+
# the private member toggle in the base theme compates against `data-private`)
|
276
|
+
# or the special word `"content"`, which will compare against a filterable
|
277
|
+
# item's `textContent` property.
|
278
|
+
|
279
|
+
Filter = do ->
|
280
|
+
filter_id = 1
|
281
|
+
|
282
|
+
matcher_for = (node) ->
|
283
|
+
if node.type == "checkbox"
|
284
|
+
if node.checked
|
285
|
+
value = node.getAttribute(ATTRS.CHECKED_VALUE)
|
286
|
+
if value then new RegExp(value) else /true/
|
287
|
+
else
|
288
|
+
value = node.getAttribute(ATTRS.UNCHECKED_VALUE)
|
289
|
+
if value then new RegExp(value) else /false/
|
290
|
+
|
291
|
+
else
|
292
|
+
value = node.value.split("")
|
293
|
+
if value.length == 0 then { test: -> true } else new RegExp(value.join(".*?"), "i")
|
294
|
+
|
295
|
+
filterables_for = (node) ->
|
296
|
+
filter_selectors = node.getAttribute(ATTRS.FILTERS).split(/\s*,\s*/)
|
297
|
+
|
298
|
+
filterables = Array::reduce.call filter_selectors, (objects, selector) ->
|
299
|
+
filterable = Filterable.for(document.querySelector(selector))
|
300
|
+
# What if not filterable'd?
|
301
|
+
objects.push(filterable) if filterable && filterable.is_valid
|
302
|
+
objects
|
303
|
+
, []
|
304
|
+
|
305
|
+
(node) ->
|
306
|
+
matcher = null
|
307
|
+
# Set a default?
|
308
|
+
filters_on = node.getAttribute(ATTRS.ATTRIBUTE)
|
309
|
+
filters_on_contents = filters_on == "contents"
|
310
|
+
filterables = filterables_for(node)
|
311
|
+
|
312
|
+
api =
|
313
|
+
#*
|
314
|
+
# The unique identifier for the filter. This is used for each
|
315
|
+
# [`FilterableItem`'s](@link FilterableItem) to create an internal list
|
316
|
+
# of all attached filters and whether or not they currently match.
|
317
|
+
#
|
318
|
+
# @property
|
319
|
+
# @type String
|
320
|
+
id: node.id || "filter-#{filter_id++}"
|
321
|
+
|
322
|
+
#*
|
323
|
+
# Tests a node against this `Filter`. If the method returns `true`, then
|
324
|
+
# the item passes the filter and should be shown (assuming no other
|
325
|
+
# filters are not passed by the item). If it returns `false`, the item
|
326
|
+
# should be hidden.
|
327
|
+
#
|
328
|
+
# @method
|
329
|
+
# @param {FilterableItem} item - The filterable item to test against this
|
330
|
+
# filter.
|
331
|
+
#
|
332
|
+
# @returns Boolean
|
333
|
+
test: (item) ->
|
334
|
+
matcher ||= matcher_for(node)
|
335
|
+
compare = if filters_on_contents then item.node.textContent else item.node.getAttribute(filters_on)
|
336
|
+
matches = matcher.test(compare)
|
337
|
+
highlight_match(item.node) if filters_on_contents && matches
|
338
|
+
matches
|
339
|
+
|
340
|
+
highlight_match = (item) ->
|
341
|
+
item.innerHTML = highlight_characters(item.textContent, node.value)
|
342
|
+
|
343
|
+
update = ->
|
344
|
+
matcher = null
|
345
|
+
filterable.filter_against(api) for filterable in filterables
|
346
|
+
return
|
347
|
+
|
348
|
+
$(node).on("change input", update)
|
349
|
+
update()
|
350
|
+
api
|
351
|
+
|
352
|
+
Lemon.make(Filterable, selector: "[#{ATTRS.ITEMS}]")
|
353
|
+
Lemon.make(Filter, selector: "[#{ATTRS.FILTERS}]")
|
File without changes
|
@@ -0,0 +1,34 @@
|
|
1
|
+
//*
|
2
|
+
// This mixin allows you to specify custom styling for items that should be
|
3
|
+
// hidden based on the attached filters. By default, filtered items are set to
|
4
|
+
// `display: none;` — use this mixin to provide custom styles.
|
5
|
+
//
|
6
|
+
// Note that this mixin can't be used at the root level — it must be nested
|
7
|
+
// under another selector.
|
8
|
+
//
|
9
|
+
// @example
|
10
|
+
// .foo {
|
11
|
+
// @include when-filtered {
|
12
|
+
// background: red;
|
13
|
+
// }
|
14
|
+
// }
|
15
|
+
//
|
16
|
+
// // yields:
|
17
|
+
//
|
18
|
+
// .foo[data-filtered-status = "hidden"] {
|
19
|
+
// display: block !important; // overrides the default hidden state
|
20
|
+
// background: red;
|
21
|
+
// }
|
22
|
+
|
23
|
+
@mixin when-filtered {
|
24
|
+
&[data-filtered-status = "hidden"] {
|
25
|
+
display: block !important;
|
26
|
+
@content;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
.filter--highlight {
|
31
|
+
background-color: color(yellow);
|
32
|
+
}
|
33
|
+
|
34
|
+
[data-filtered-status = "hidden"] { display: none !important; }
|
File without changes
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%
|
2
|
+
avatar.configure do |config|
|
3
|
+
config.defaults(author: OpenStruct.new(name: "", github: "", twitter: "", email: ""))
|
4
|
+
end
|
5
|
+
|
6
|
+
service, profile_name, url = if avatar.author.github
|
7
|
+
["github", avatar.author.github, "https://github.com/#{avatar.author.github}"]
|
8
|
+
elsif avatar.author.twitter
|
9
|
+
["twitter", avatar.author.twitter, "https://twitter.com/#{avatar.author.twitter}"]
|
10
|
+
elsif avatar.author.email
|
11
|
+
["email", avatar.author.email, "mailto:#{avatar.author.email}"]
|
12
|
+
else
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
%>
|
16
|
+
|
17
|
+
<a class="avatar" data-service="<%= service %>" data-profile-name="<%= profile_name %>" href="<%= url.nil? ? "javascript:;" : url %>">
|
18
|
+
<span class="avatar__initials"><%= avatar.author.name.split(/\s+/).map { |name| name[0] }.join("") %></span>
|
19
|
+
<div class="avatar__image"></div>
|
20
|
+
</a>
|
@@ -0,0 +1,142 @@
|
|
1
|
+
// ___ ___ ___ ___
|
2
|
+
// / /\ ___ / /\ ___ / /\ / /\
|
3
|
+
// / /::\ /__/\ / /::\ / /\ / /::\ / /::\
|
4
|
+
// / /:/\:\ \ \:\ / /:/\:\ / /:/ / /:/\:\ / /:/\:\
|
5
|
+
// / /:/~/::\ \ \:\ / /:/~/::\ / /:/ / /:/~/::\ / /:/~/:/
|
6
|
+
// /__/:/ /:/\:\___ \__\:\/__/:/ /:/\:\/ /::\/__/:/ /:/\:\/__/:/ /:/___
|
7
|
+
// \ \:\/:/__\/__/\ | |:|\ \:\/:/__\/__/:/\:\ \:\/:/__\/\ \:\/:::::/
|
8
|
+
// \ \::/ \ \:\| |:| \ \::/ \__\/ \:\ \::/ \ \::/~~~~
|
9
|
+
// \ \:\ \ \:\__|:| \ \:\ \ \:\ \:\ \ \:\
|
10
|
+
// \ \:\ \__\::::/ \ \:\ \__\/\ \:\ \ \:\
|
11
|
+
// \__\/ ~~~~ \__\/ \__\/ \__\/
|
12
|
+
|
13
|
+
import Builder from "~utilities/builder";
|
14
|
+
import "whatwg-fetch";
|
15
|
+
|
16
|
+
//*
|
17
|
+
// The name of classes relevant to `Avatar`.
|
18
|
+
// @object
|
19
|
+
|
20
|
+
const classes = {
|
21
|
+
//*
|
22
|
+
// @property
|
23
|
+
root: "avatar",
|
24
|
+
|
25
|
+
//*
|
26
|
+
// @property
|
27
|
+
image: "avatar__image",
|
28
|
+
|
29
|
+
//*
|
30
|
+
// @property
|
31
|
+
initials: "avatar__initials"
|
32
|
+
};
|
33
|
+
|
34
|
+
//*
|
35
|
+
// The name of states relevant to `Avatar`.
|
36
|
+
// @object
|
37
|
+
|
38
|
+
const states = {
|
39
|
+
//*
|
40
|
+
// @property
|
41
|
+
image: {
|
42
|
+
visible: `${classes.image}--is-visible`
|
43
|
+
}
|
44
|
+
};
|
45
|
+
|
46
|
+
//*
|
47
|
+
// The name of attributes relevant to `Avatar`.
|
48
|
+
// @object
|
49
|
+
|
50
|
+
const attrs = {
|
51
|
+
//*
|
52
|
+
// @property
|
53
|
+
profile_name: "data-profile-name",
|
54
|
+
|
55
|
+
//*
|
56
|
+
// @property
|
57
|
+
service: "data-service"
|
58
|
+
};
|
59
|
+
|
60
|
+
//*
|
61
|
+
// The minimum time, in milliseconds, before the background images for avatars
|
62
|
+
// should be faded into view. This is done to prevent any sudden visual changes
|
63
|
+
// immediately after page load.
|
64
|
+
//
|
65
|
+
// @value 200
|
66
|
+
// @type Number
|
67
|
+
// @private
|
68
|
+
|
69
|
+
const MIN_TIME_TO_LOAD = 200;
|
70
|
+
|
71
|
+
var Avatar, show_image;
|
72
|
+
|
73
|
+
//*
|
74
|
+
// Fades the image into view smoothly. To prevent sudden appearance of images
|
75
|
+
// immediately after page load, this function stores the time when it was
|
76
|
+
// initialized and waits at least `MIN_TIME_TO_LOAD` after that before applying
|
77
|
+
// the required classes.
|
78
|
+
//
|
79
|
+
// @private
|
80
|
+
// @param {DOMElement} image - The image to reveal.
|
81
|
+
|
82
|
+
show_image = (() => {
|
83
|
+
var start = Date.now();
|
84
|
+
|
85
|
+
return (image) => {
|
86
|
+
setTimeout(() => {
|
87
|
+
image.classList.add(states.image.visible);
|
88
|
+
}, Math.max(0, MIN_TIME_TO_LOAD - (Date.now() - start)));
|
89
|
+
};
|
90
|
+
})();
|
91
|
+
|
92
|
+
//*
|
93
|
+
// The constructor around an avatar DOM node. This constructor will check for
|
94
|
+
// the service from which the avatar image should be fetched and do its best to
|
95
|
+
// grab that image.
|
96
|
+
//
|
97
|
+
// Because there is no way to interact with an `Avatar`, there is no public
|
98
|
+
// interface for this component.
|
99
|
+
//
|
100
|
+
// @factory
|
101
|
+
//
|
102
|
+
// @param {DOMElement} node - The root of an Avatar component.
|
103
|
+
|
104
|
+
Avatar = (node) => {
|
105
|
+
var profile_name = node.getAttribute(attrs.profile_name),
|
106
|
+
image = node.querySelector(`.${classes.image}`),
|
107
|
+
service = node.getAttribute(attrs.service);
|
108
|
+
|
109
|
+
switch(service) {
|
110
|
+
case "github":
|
111
|
+
fetch(`https://api.github.com/users/${profile_name}`)
|
112
|
+
.then((response) => { return response.json(); })
|
113
|
+
.then((response) => {
|
114
|
+
image.style.backgroundImage = `url(${response.avatar_url})`;
|
115
|
+
show_image(image);
|
116
|
+
});
|
117
|
+
break;
|
118
|
+
|
119
|
+
case "twitter":
|
120
|
+
case "email":
|
121
|
+
image.style.backgroundImage = `url(http://avatars.io/${service}/${profile_name})`;
|
122
|
+
show_image(image);
|
123
|
+
break;
|
124
|
+
}
|
125
|
+
};
|
126
|
+
|
127
|
+
//*
|
128
|
+
// Initializes the `Avatar` component.
|
129
|
+
//
|
130
|
+
// @method
|
131
|
+
// @static
|
132
|
+
//
|
133
|
+
// @requires builder::Builder
|
134
|
+
//
|
135
|
+
// @arg {HTMLElement} [context = document] - The context in which to search
|
136
|
+
// for DOM nodes that should be used as the root of an [`Avatar`](@link)
|
137
|
+
// component.
|
138
|
+
|
139
|
+
Avatar.init = Builder.initialize_once(Avatar, { name: classes.root });
|
140
|
+
|
141
|
+
export { classes, states, attrs };
|
142
|
+
export default Avatar;
|