docks_theme_api 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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;
|