enchanted_quill 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5730b8d566d7ed7b273cddb1747f52854e5268da
4
+ data.tar.gz: 3393cc15dfcdab0aa9f6db26f507c0fa71601058
5
+ SHA512:
6
+ metadata.gz: cdcb55744c301335f38893f1fa0d69b397c2f4e5c0039c442ff7e3c209103c57d96e2ae824f5959e4f94e397e91bd3c965dd39d4bd94c32989f00126c44551ce
7
+ data.tar.gz: 014b262a9ac5814addab76c5101f967c551a2efe5ef8933edbdedbcc9045c52dcc79c3e36050de720f3a1063210d4618ff0f69088b10ff33a7cce95feafb5b48
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at gomezelom@yahoo.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Elom Gomez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # EnchantedQuill
2
+
3
+ **EnchantedQuill** is a rubymotion port of [ActiveLabel.swift](https://github.com/optonaut/ActiveLabel.swift), just like the maintainer of that gem
4
+ I had also tried different `cocoapods` that were drop in replacements of `UILabel` but they were either a bit complex to implement or just doing too much. I decided to go with [ActiveLabel.swift](https://github.com/optonaut/ActiveLabel.swift) because its API is cleaner and simpler to implement and understand.
5
+
6
+ ## Features
7
+ * Support for **Hashtags, Mentions, Categories and Links**
8
+ * Super easy to use and lightweight
9
+ * Acceptable test coverage :smile:
10
+
11
+ ![Alt Text](https://github.com/paddingtonsbear/enchanted_quill/raw/master/example.gif)
12
+
13
+ ## Installation
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'enchanted_quill'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ ## Usage
25
+
26
+ ```ruby
27
+ label = EnchantedQuill::Label.alloc.init
28
+ label.text = text
29
+ label.textColor = UIColor.blackColor
30
+ label.font = UIFont.systemFontOfSize(12)
31
+ label.mention_color = UIColor.colorWithRed(85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1)
32
+ label.mention_selected_color = UIColor.colorWithRed(85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1).colorWithAlphaComponent(0.5)
33
+ label.hashtag_color = UIColor.colorWithRed(238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1)
34
+ label.hashtag_selected_color = UIColor.colorWithRed(238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1).colorWithAlphaComponent(0.5)
35
+ label.category_color = UIColor.brownColor
36
+ label.category_selected_color = UIColor.brownColor.colorWithAlphaComponent(0.5)
37
+ label.url_color = UIColor.colorWithRed(85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1)
38
+ label.url_selected_color = UIColor.colorWithRed(85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1).colorWithAlphaComponent(0.5)
39
+
40
+ label.handle_mention_tap do |mention|
41
+ p "Mention #{mention}"
42
+ end
43
+
44
+ label.handle_category_tap do |category|
45
+ p "Category #{category}"
46
+ end
47
+
48
+ label.handle_url_tap do |url|
49
+ p "URL #{url.absoluteString}"
50
+ end
51
+
52
+ label.handle_hashtag_tap do |hashtag|
53
+ p "Hashtag #{hashtag}"
54
+ end
55
+ ```
56
+ ## Better Performance
57
+ For better performance use the `customize` block, this basically groups all customizations on the label
58
+ and refreshes the textContainer once instead of refreshing each time an attribute is set.
59
+ ```ruby
60
+ label.customize do
61
+ label.backgroundColor = UIColor.blackColor
62
+ label.text = text
63
+ label.textColor = UIColor.whiteColor
64
+ label.font = UIFont.systemFontOfSize(12)
65
+ label.numberOfLines = 0
66
+ label.mention_color = UIColor.colorWithRed(85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1)
67
+ label.mention_selected_color = UIColor.colorWithRed(85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1).colorWithAlphaComponent(0.5)
68
+ label.hashtag_color = UIColor.colorWithRed(238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1)
69
+ label.hashtag_selected_color = UIColor.colorWithRed(238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1).colorWithAlphaComponent(0.5)
70
+ label.category_color = UIColor.brownColor
71
+ label.category_selected_color = UIColor.brownColor.colorWithAlphaComponent(0.5)
72
+ label.url_color = UIColor.colorWithRed(85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1)
73
+ label.url_selected_color = UIColor.colorWithRed(85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1).colorWithAlphaComponent(0.5)
74
+
75
+ label.handle_mention_tap do |mention|
76
+ alert('Mention', mention)
77
+ end
78
+
79
+ label.handle_category_tap do |category|
80
+ alert('Category', category)
81
+ end
82
+
83
+ label.handle_url_tap do |url|
84
+ alert('URL', url.absoluteString)
85
+ end
86
+
87
+ label.handle_hashtag_tap do |hashtag|
88
+ alert('HashTag', hashtag)
89
+ end
90
+
91
+ label.adjustsFontSizeToFitWidth = true
92
+ end
93
+ ```
94
+
95
+
96
+ ## Credits
97
+ A special thanks to [@optonaut](https://github.com/optonaut/ActiveLabel.swift) for implementing such an easy to use pod for swift. :+1:
98
+
99
+ ## Contributing
100
+ Bug reports and pull requests are welcome on GitHub at https://github.com/paddingtonsbear/enchanted_quill. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
101
+
102
+ ## License
103
+
104
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
105
+
@@ -0,0 +1,81 @@
1
+ module EnchantedQuill
2
+ class ElementCreator
3
+ Element = Struct.new(:type, :word, :range)
4
+
5
+ def self.create_mentions_elements(text, range)
6
+ mentions = EnchantedQuill::Parser.parse_elements_for(:mention, text, range)
7
+ elements = []
8
+
9
+ mentions.each do |mention|
10
+ if mention.range.length > 2
11
+ range = NSMakeRange(mention.range.location + 1, mention.range.length - 1)
12
+ word = text.substringWithRange(range)
13
+
14
+ if word[0] == '@'
15
+ word = word[1..-1]
16
+ end
17
+
18
+ elements << Element.new(:mention, word, range)
19
+ end
20
+ end
21
+
22
+ elements
23
+ end
24
+
25
+ def self.create_hashtag_elements(text, range)
26
+ hashtags = EnchantedQuill::Parser.parse_elements_for(:hashtag, text, range)
27
+ elements = []
28
+
29
+ hashtags.each do |hashtag|
30
+ if hashtag.range.length > 2
31
+ range = NSMakeRange(hashtag.range.location + 1, hashtag.range.length - 1)
32
+ word = text.substringWithRange(range)
33
+
34
+ if word[0] == '#'
35
+ word = word[1..-1]
36
+ end
37
+
38
+ elements << Element.new(:hashtag, word, range)
39
+ end
40
+ end
41
+
42
+ elements
43
+ end
44
+
45
+ def self.create_category_elements(text, range)
46
+ categories = EnchantedQuill::Parser.parse_elements_for(:category, text, range)
47
+ elements = []
48
+
49
+ categories.each do |category|
50
+ if category.range.length > 2
51
+ range = NSMakeRange(category.range.location + 1, category.range.length - 1)
52
+ word = text.substringWithRange(range)
53
+
54
+ if word[0] == '{' && word[-1] == '}'
55
+ word = word[1..-2]
56
+ end
57
+
58
+ elements << Element.new(:category, word, range)
59
+ end
60
+ end
61
+
62
+ elements
63
+ end
64
+
65
+ def self.create_url_elements(text, range)
66
+ urls = EnchantedQuill::Parser.parse_elements_for(:url, text, range)
67
+ elements = []
68
+
69
+ urls.each do |url|
70
+ if url.range.length > 2
71
+ word = text.substringWithRange(url.range)
72
+ .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
73
+
74
+ elements << Element.new(:url, word, url.range)
75
+ end
76
+ end
77
+
78
+ elements
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,13 @@
1
+ class UIGestureRecognizerDelegate
2
+ def gestureRecognizer(gesture_recognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer: other_gesture_recognizer)
3
+ true
4
+ end
5
+
6
+ def gestureRecognizer(gesture_recognizer, shouldRequireFailureOfGestureRecognizer: other_gesture_recognizer)
7
+ true
8
+ end
9
+
10
+ def gestureRecognizer(gesture_recognizer, shouldBeRequiredToFailByGestureRecognizer: other_gesture_recognizer)
11
+ true
12
+ end
13
+ end
@@ -0,0 +1,425 @@
1
+ module EnchantedQuill
2
+ class Label < UILabel
3
+ attr_reader :mention_tap_handler, :url_tap_handler, :hashtag_tap_handler,
4
+ :category_tap_handler, :mention_color, :mention_selected_color,
5
+ :hashtag_color, :hashtag_selected_color, :category_color,
6
+ :category_selected_color, :url_color, :url_selected_color,
7
+ :line_spacing
8
+
9
+ def init
10
+ super.tap do |label|
11
+ label.setup_label
12
+ end
13
+ end
14
+
15
+ def initWithFrame(frame)
16
+ super.tap do |label|
17
+ label.setup_label
18
+ end
19
+ end
20
+
21
+ def drawTextInRect(rect)
22
+ range = NSMakeRange(0, text_storage.length)
23
+ text_container.size = rect.size
24
+ new_origin = text_origin(rect)
25
+
26
+ layout_manager.drawBackgroundForGlyphRange(range, atPoint: new_origin)
27
+ layout_manager.drawGlyphsForGlyphRange(range, atPoint: new_origin)
28
+ end
29
+
30
+ def customize(&block)
31
+ @customizing = true
32
+ block.call(self)
33
+ @customizing = false
34
+ update_text_storage
35
+ self
36
+ end
37
+
38
+ def on_touch(touch)
39
+ location = touch.locationInView(self)
40
+ avoid_super_call = false
41
+
42
+ case touch.phase
43
+ when UITouchPhaseBegan, UITouchPhaseMoved
44
+ if element = element_at_location(location)
45
+ if element.range.location != (selected_element && selected_element.range.location) ||
46
+ element.range.length != (selected_element && selected_element.range.length)
47
+
48
+ update_attributed_when_selected(false)
49
+ @selected_element = element
50
+ update_attributed_when_selected(true)
51
+ end
52
+ avoid_super_call = true
53
+ else
54
+ update_attributed_when_selected(false)
55
+ @selected_element = nil
56
+ end
57
+ when UITouchPhaseEnded
58
+ return avoid_super_call unless selected_element
59
+
60
+ case selected_element.type
61
+ when :category then did_tap_category(selected_element.word)
62
+ when :mention then did_tap_mention(selected_element.word)
63
+ when :hashtag then did_tap_hashtag(selected_element.word)
64
+ when :url then did_tap_string_url(selected_element.word)
65
+ else
66
+ end
67
+
68
+ Dispatch::Queue.concurrent.after(0.1) do
69
+ Dispatch::Queue.main.async do
70
+ update_attributed_when_selected(false)
71
+ @selected_element = nil
72
+ end
73
+ end
74
+ avoid_super_call = true
75
+ when UITouchPhaseCancelled
76
+ @selected_element = nil
77
+ when UITouchPhaseStationary
78
+ end
79
+
80
+ avoid_super_call
81
+ end
82
+
83
+ def delegate=(delegate)
84
+ @delegate = WeakRef.new(delegate)
85
+ end
86
+
87
+ def delegate
88
+ @delegate
89
+ end
90
+
91
+ def handle_mention_tap(&block)
92
+ @mention_tap_handler = block
93
+ end
94
+
95
+ def handle_hashtag_tap(&block)
96
+ @hashtag_tap_handler = block
97
+ end
98
+
99
+ def handle_url_tap(&block)
100
+ @url_tap_handler = block
101
+ end
102
+
103
+ def handle_category_tap(&block)
104
+ @category_tap_handler = block
105
+ end
106
+
107
+ # override UILabel attributes
108
+ def text=(text)
109
+ super
110
+ update_text_storage
111
+ end
112
+
113
+ def attributedText=(attributed_text)
114
+ super
115
+ update_text_storage
116
+ end
117
+
118
+ def font=(font)
119
+ super
120
+ update_text_storage(false)
121
+ end
122
+
123
+ def textColor=(color)
124
+ super
125
+ update_text_storage(false)
126
+ end
127
+
128
+ def textAlignment=(alignment)
129
+ super
130
+ update_text_storage(false)
131
+ end
132
+
133
+ def mention_color=(color)
134
+ @mention_color = color
135
+ update_text_storage(false)
136
+ end
137
+
138
+ def mention_selected_color=(color)
139
+ @mention_selected_color = color
140
+ update_text_storage(false)
141
+ end
142
+
143
+ def hashtag_color=(color)
144
+ @hashtag_color = color
145
+ update_text_storage(false)
146
+ end
147
+
148
+ def hashtag_selected_color=(color)
149
+ @hashtag_selected_color = color
150
+ update_text_storage(false)
151
+ end
152
+
153
+ def category_color=(color)
154
+ @category_color = color
155
+ update_text_storage(false)
156
+ end
157
+
158
+ def category_selected_color=(color)
159
+ @category_selected_color = color
160
+ update_text_storage(false)
161
+ end
162
+
163
+ def url_color=(color)
164
+ @url_color = color
165
+ update_text_storage(false)
166
+ end
167
+
168
+ def url_selected_color=(color)
169
+ @url_selected_color = color
170
+ update_text_storage(false)
171
+ end
172
+
173
+ def line_spacing=(spacing)
174
+ @line_spacing = spacing
175
+ update_text_storage(false)
176
+ end
177
+
178
+ def touchesBegan(touches, withEvent: event)
179
+ touches = touches && touches.allObjects
180
+ touch = touches.first
181
+ return unless touch
182
+ return if on_touch(touch)
183
+ super
184
+ end
185
+
186
+ def touchesCancelled(touches, withEvent: event)
187
+ touches = touches && touches.allObjects
188
+ touch = touches.first
189
+ return unless touch
190
+ on_touch(touch)
191
+ super
192
+ end
193
+
194
+ def touchesEnded(touches, withEvent: event)
195
+ touches = touches && touches.allObjects
196
+ touch = touches.first
197
+ return unless touch
198
+ return if on_touch(touch)
199
+ super
200
+ end
201
+
202
+ # def intrinsicContentSize
203
+ # label_height = sizeThatFits(CGSizeMake(CGRectGetWidth(self.frame), Float::MAX)).height
204
+ # return CGSizeMake(self.frame.size.width, label_height + self.layoutMargins.top + self.layoutMargins.bottom)
205
+ # end
206
+
207
+ def setup_label
208
+ @customizing = false
209
+ @active_elements = {}
210
+ self.textColor = UIColor.blackColor
211
+ self.mention_color = UIColor.blueColor
212
+ self.mention_selected_color = UIColor.blueColor.colorWithAlphaComponent(0.5)
213
+ self.hashtag_color = UIColor.blueColor
214
+ self.hashtag_selected_color = UIColor.blueColor.colorWithAlphaComponent(0.5)
215
+ self.category_color = UIColor.brownColor
216
+ self.category_selected_color = UIColor.brownColor.colorWithAlphaComponent(0.5)
217
+ self.url_color = UIColor.blueColor
218
+ self.url_selected_color = UIColor.blueColor.colorWithAlphaComponent(0.5)
219
+
220
+ text_storage.addLayoutManager(layout_manager)
221
+ layout_manager.addTextContainer(text_container)
222
+ text_container.lineFragmentPadding = 0
223
+ self.userInteractionEnabled = true
224
+ end
225
+
226
+ private
227
+
228
+ attr_reader :customizing, :selected_element, :mention_tap_handler,
229
+ :url_tap_handler, :hashtag_tap_handler, :category_tap_handler,
230
+ :height_correction, :active_elements
231
+
232
+ def layout_manager
233
+ @layout_manager ||= NSLayoutManager.alloc.init
234
+ end
235
+
236
+ def text_container
237
+ @text_container ||= NSTextContainer.alloc.init
238
+ end
239
+
240
+ def text_storage
241
+ @text_storage ||= NSTextStorage.alloc.init
242
+ end
243
+
244
+ def update_text_storage(parse_text = true)
245
+ return if customizing
246
+
247
+ attributed_text = attributedText
248
+ if attributed_text.nil? || attributed_text.length == 0
249
+ text_storage.setAttributedString(NSAttributedString.alloc.init)
250
+ return
251
+ end
252
+
253
+ mut_attr_string = add_line_break(attributed_text)
254
+
255
+ if parse_text
256
+ @selected_element = nil
257
+ active_elements.each do |type, _|
258
+ @active_elements[type] = []
259
+ end
260
+
261
+ parse_text_and_extract_active_elements(mut_attr_string)
262
+ end
263
+
264
+ add_link_attribute(mut_attr_string)
265
+ text_storage.setAttributedString(mut_attr_string)
266
+ setNeedsDisplay
267
+ end
268
+
269
+ def text_origin(rect)
270
+ used_rect = layout_manager.usedRectForTextContainer(text_container)
271
+ @height_correction = (rect.size.height - used_rect.size.height) / 2
272
+ glyph_origin_y = height_correction > 0 ? rect.origin.y + height_correction : rect.origin.y
273
+ CGPointMake(rect.origin.x, glyph_origin_y)
274
+ end
275
+
276
+ # add link attribute
277
+ def add_link_attribute(mut_attr_string)
278
+ range_pointer = Pointer.new(NSRange.type)
279
+ attributes = mut_attr_string.attributesAtIndex(0, effectiveRange: range_pointer).dup
280
+
281
+ attributes[NSFontAttributeName] = self.font
282
+ attributes[NSForegroundColorAttributeName] = self.textColor
283
+
284
+ mut_attr_string.addAttributes(attributes, range: range_pointer[0])
285
+
286
+ attributes[NSForegroundColorAttributeName] = mention_color
287
+
288
+ active_elements.each do |type, elements|
289
+ case type
290
+ when :mention then attributes[NSForegroundColorAttributeName] = mention_color
291
+ when :hashtag then attributes[NSForegroundColorAttributeName] = hashtag_color
292
+ when :url then attributes[NSForegroundColorAttributeName] = url_color
293
+ when :category then attributes[NSForegroundColorAttributeName] = category_color
294
+ end
295
+
296
+ elements.each do |element|
297
+ mut_attr_string.setAttributes(attributes, range: element.range)
298
+ end
299
+ end
300
+ end
301
+
302
+ def parse_text_and_extract_active_elements(attr_string)
303
+ text_string = attr_string.string
304
+ text_length = text_string.length
305
+ text_range = NSMakeRange(0, text_length)
306
+
307
+ # urls
308
+ url_elements = ElementCreator.create_url_elements(text_string, text_range)
309
+ @active_elements[:url] = url_elements
310
+
311
+ # hash_tags
312
+ hashtag_elements = ElementCreator.create_hashtag_elements(text_string, text_range)
313
+ @active_elements[:hashtag] = hashtag_elements
314
+
315
+ # mentions
316
+ mention_elements = ElementCreator.create_mentions_elements(text_string, text_range)
317
+ @active_elements[:mention] = mention_elements
318
+
319
+ # categories
320
+ category_elements = ElementCreator.create_category_elements(text_string, text_range)
321
+ @active_elements[:category] = category_elements
322
+ end
323
+
324
+ def add_line_break(attr_string)
325
+ mut_attr_string = NSMutableAttributedString.alloc.initWithAttributedString(attr_string)
326
+
327
+ range_pointer = Pointer.new(NSRange.type)
328
+ attributes = mut_attr_string.attributesAtIndex(0, effectiveRange: range_pointer).dup
329
+
330
+ current_paragraph_style = attributes[NSParagraphStyleAttributeName] && attributes[NSParagraphStyleAttributeName].mutableCopy
331
+ paragraph_style = current_paragraph_style || NSMutableParagraphStyle.alloc.init
332
+ paragraph_style.lineBreakMode = NSLineBreakByWordWrapping
333
+ paragraph_style.alignment = textAlignment
334
+
335
+ if line_spacing
336
+ paragraph_style.lineSpacing = line_spacing
337
+ end
338
+
339
+ attributes[NSParagraphStyleAttributeName] = paragraph_style
340
+ mut_attr_string.setAttributes(attributes, range: range_pointer[0])
341
+ mut_attr_string
342
+ end
343
+
344
+ def update_attributed_when_selected(selected)
345
+ return unless @selected_element
346
+
347
+ attributes = text_storage.attributesAtIndex(0, effectiveRange: nil).dup
348
+ unless selected
349
+ case @selected_element.type
350
+ when :category then attributes[NSForegroundColorAttributeName] = category_color
351
+ when :hashtag then attributes[NSForegroundColorAttributeName] = hashtag_color
352
+ when :mention then attributes[NSForegroundColorAttributeName] = mention_color
353
+ when :url then attributes[NSForegroundColorAttributeName] = url_color
354
+ end
355
+ else
356
+ case @selected_element.type
357
+ when :category then attributes[NSForegroundColorAttributeName] = category_selected_color || category_color
358
+ when :hashtag then attributes[NSForegroundColorAttributeName] = hashtag_selected_color || hashtag_color
359
+ when :mention then attributes[NSForegroundColorAttributeName] = mention_selected_color || mention_color
360
+ when :url then attributes[NSForegroundColorAttributeName] = url_selected_color || url_color
361
+ end
362
+ end
363
+
364
+ text_storage.addAttributes(attributes, range: @selected_element.range)
365
+ setNeedsDisplay
366
+ end
367
+
368
+ def element_at_location(location)
369
+ return unless text_storage.length > 0
370
+
371
+ y = location.y - height_correction
372
+ correct_location = CGPointMake(location.x, y)
373
+ bounding_rect = layout_manager.boundingRectForGlyphRange(NSMakeRange(0, text_storage.length), inTextContainer: text_container)
374
+
375
+ return unless CGRectContainsPoint(bounding_rect, correct_location)
376
+
377
+ index = layout_manager.glyphIndexForPoint(correct_location, inTextContainer: text_container)
378
+
379
+ active_element = nil
380
+ active_elements.values.flatten.each do |element|
381
+ if index < text_storage.length - 1 &&
382
+ index >= element.range.location &&
383
+ index < element.range.location + element.range.length
384
+ active_element = element
385
+ break
386
+ end
387
+ end
388
+
389
+ active_element
390
+ end
391
+
392
+ def did_tap_mention(username)
393
+ if mention_tap_handler
394
+ mention_tap_handler.call(username)
395
+ elsif delegate && delegate.respond_to?('did_select_text:type:')
396
+ delegate.did_select_text(username, type: :mention)
397
+ end
398
+ end
399
+
400
+ def did_tap_hashtag(hashtag)
401
+ if hashtag_tap_handler
402
+ hashtag_tap_handler.call(hashtag)
403
+ elsif delegate && delegate.respond_to?('did_select_text:type:')
404
+ delegate.did_select_text(hashtag, type: :hashtag)
405
+ end
406
+ end
407
+
408
+ def did_tap_string_url(url)
409
+ url = NSURL.URLWithString(url)
410
+ if url_tap_handler
411
+ url_tap_handler.call(url)
412
+ elsif delegate && delegate.respond_to?('did_select_text:type:')
413
+ delegate.did_select_text(url, type: :url)
414
+ end
415
+ end
416
+
417
+ def did_tap_category(category)
418
+ if category_tap_handler
419
+ category_tap_handler.call(category)
420
+ elsif delegate && delegate.respond_to?('did_select_text:type:')
421
+ delegate.did_select_text(category, type: :category)
422
+ end
423
+ end
424
+ end
425
+ end
@@ -0,0 +1,25 @@
1
+ module EnchantedQuill
2
+ class Parser
3
+ CATEGORY_REGEX = "(?:^|\\s|$)(\\{.*?\\})"
4
+ HASHTAG_REGEX = "(?:^|\\s|$)#[\\p{L}0-9_]*"
5
+ MENTION_REGEX = "(?:^|\\s|$|[.])@[\\p{L}0-9_]*"
6
+ URL_REGEX = "(^|[\\s.:;?\\-\\]<\\(])" +
7
+ "((https?://|www.|pic.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" +
8
+ "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])"
9
+
10
+ TYPE_WITH_REGEX = {
11
+ url: URL_REGEX,
12
+ hashtag: HASHTAG_REGEX,
13
+ mention: MENTION_REGEX,
14
+ category: CATEGORY_REGEX
15
+ }
16
+
17
+ def self.parse_elements_for(type, text, range)
18
+ regex = TYPE_WITH_REGEX[type]
19
+ return [] unless regex
20
+
21
+ regex = NSRegularExpression.alloc.initWithPattern(regex, options: NSRegularExpressionCaseInsensitive, error: nil)
22
+ regex.matchesInString(text, options: 0, range: range)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module EnchantedQuill
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,11 @@
1
+ require "enchanted_quill/version"
2
+
3
+ unless defined?(Motion::Project::App)
4
+ raise "This must be required from within a RubyMotion Rakefile"
5
+ end
6
+
7
+ Motion::Project::App.setup do |app|
8
+ lib_dir = File.join(File.dirname(__FILE__))
9
+ app.files.unshift(Dir.glob(File.join(lib_dir, "enchanted_quill/**/*.rb")))
10
+ app.frameworks += %w{ Foundation UIKit }
11
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: enchanted_quill
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Elom Gomez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Drop in replacement for UILabel with support for mentions, links and
28
+ hashtags for RubyMotion
29
+ email:
30
+ - gomezelom@yahoo.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CODE_OF_CONDUCT.md
36
+ - LICENSE.txt
37
+ - README.md
38
+ - lib/enchanted_quill.rb
39
+ - lib/enchanted_quill/element_creator.rb
40
+ - lib/enchanted_quill/gesture_recognizer_delegate_extension.rb
41
+ - lib/enchanted_quill/label.rb
42
+ - lib/enchanted_quill/parser.rb
43
+ - lib/enchanted_quill/version.rb
44
+ homepage: https://github.com/paddingtonsbear/enchanted_quill
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.6.2
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: RubyMotion port of ActiveLabel.swift
68
+ test_files: []