formstrap 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/assets/javascripts/formstrap/controllers/redactor_controller.js +18 -0
  4. data/app/assets/javascripts/formstrap/controllers/select_controller.js +22 -18
  5. data/app/assets/javascripts/formstrap/index.js +5 -2
  6. data/app/assets/javascripts/formstrap/vendor/redactor/i18n/de.js +214 -0
  7. data/app/assets/javascripts/formstrap/vendor/redactor/i18n/en.js +214 -0
  8. data/app/assets/javascripts/formstrap/vendor/redactor/i18n/fr.js +214 -0
  9. data/app/assets/javascripts/formstrap/vendor/redactor/i18n/nl.js +214 -0
  10. data/app/assets/javascripts/formstrap/vendor/redactor/index.js +5 -0
  11. data/app/assets/javascripts/formstrap/vendor/redactor/plugins/emoji.js +332 -0
  12. data/app/assets/javascripts/formstrap/vendor/redactor/plugins/linkstyles.js +124 -0
  13. data/app/assets/javascripts/formstrap.js +1295 -148
  14. data/app/assets/stylesheets/formstrap/general.scss +0 -6
  15. data/app/assets/stylesheets/formstrap/vendor/overrides/redactor.scss +151 -0
  16. data/app/assets/stylesheets/formstrap/vendor/redactor.css +2535 -0
  17. data/app/assets/stylesheets/formstrap.css +2650 -5
  18. data/app/assets/stylesheets/formstrap.scss +4 -0
  19. data/app/models/formstrap/redactor_view.rb +57 -0
  20. data/app/models/formstrap/wysiwyg_view.rb +9 -1
  21. data/app/views/formstrap/{_redactorx.html.erb → _redactor.html.erb} +4 -7
  22. data/app/views/formstrap/_wysiwyg.html.erb +3 -5
  23. data/lib/formstrap/form_builder.rb +12 -4
  24. data/lib/formstrap/version.rb +1 -1
  25. data/package.json +2 -2
  26. data/yarn.lock +4 -4
  27. metadata +14 -5
  28. data/app/assets/javascripts/formstrap/controllers/redactorx_controller.js +0 -40
  29. data/app/models/formstrap/redactorx_view.rb +0 -57
@@ -0,0 +1,332 @@
1
+ /* global Redactor */
2
+ Redactor.add('plugin', 'emoji', {
3
+ translations: {
4
+ en: {
5
+ emoji: {
6
+ emoji: 'Emoji',
7
+ favorites: 'Favorites',
8
+ smileys: 'Smileys',
9
+ gestures: 'Gestures',
10
+ animals: 'Animals',
11
+ food: 'Food',
12
+ activities: 'Activities',
13
+ travel: 'Travel'
14
+ }
15
+ }
16
+ },
17
+ defaults: {
18
+ context: true,
19
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.17317 2.7612C9.38642 2.25866 10.6868 2 12 2C13.3132 2 14.6136 2.25866 15.8268 2.7612C17.0401 3.26375 18.1425 4.00035 19.0711 4.92893C19.9997 5.85752 20.7362 6.95991 21.2388 8.17317C21.7413 9.38642 22 10.6868 22 12C22 13.3132 21.7413 14.6136 21.2388 15.8268C20.7362 17.0401 19.9997 18.1425 19.0711 19.0711C18.1425 19.9997 17.0401 20.7362 15.8268 21.2388C14.6136 21.7413 13.3132 22 12 22C10.6868 22 9.38642 21.7413 8.17317 21.2388C6.95991 20.7362 5.85752 19.9997 4.92893 19.0711C4.00035 18.1425 3.26375 17.0401 2.7612 15.8268C2.25866 14.6136 2 13.3132 2 12C2 10.6868 2.25866 9.38642 2.7612 8.17317C3.26375 6.95991 4.00035 5.85752 4.92893 4.92893C5.85752 4.00035 6.95991 3.26375 8.17317 2.7612ZM12 4C10.9494 4 9.90914 4.20693 8.93853 4.60896C7.96793 5.011 7.08601 5.60028 6.34315 6.34315C5.60028 7.08601 5.011 7.96793 4.60896 8.93853C4.20693 9.90914 4 10.9494 4 12C4 13.0506 4.20693 14.0909 4.60896 15.0615C5.011 16.0321 5.60028 16.914 6.34315 17.6569C7.08601 18.3997 7.96793 18.989 8.93853 19.391C9.90914 19.7931 10.9494 20 12 20C13.0506 20 14.0909 19.7931 15.0615 19.391C16.0321 18.989 16.914 18.3997 17.6569 17.6569C18.3997 16.914 18.989 16.0321 19.391 15.0615C19.7931 14.0909 20 13.0506 20 12C20 10.9494 19.7931 9.90914 19.391 8.93853C18.989 7.96793 18.3997 7.08602 17.6569 6.34315C16.914 5.60028 16.0321 5.011 15.0615 4.60896C14.0909 4.20693 13.0506 4 12 4ZM8 10C8 9.44772 8.44772 9 9 9H9.01C9.56228 9 10.01 9.44772 10.01 10C10.01 10.5523 9.56228 11 9.01 11H9C8.44772 11 8 10.5523 8 10ZM14 10C14 9.44772 14.4477 9 15 9H15.01C15.5623 9 16.01 9.44772 16.01 10C16.01 10.5523 15.5623 11 15.01 11H15C14.4477 11 14 10.5523 14 10ZM8.80015 14.2857C9.19463 13.8992 9.82777 13.9057 10.2143 14.3001C10.4471 14.5377 10.7249 14.7265 11.0315 14.8553C11.3381 14.9841 11.6674 15.0505 12 15.0505C12.3326 15.0505 12.6619 14.9841 12.9685 14.8553C13.2751 14.7265 13.5529 14.5377 13.7857 14.3001C14.1722 13.9057 14.8054 13.8992 15.1999 14.2857C15.5943 14.6722 15.6008 15.3054 15.2143 15.6999C14.7953 16.1275 14.2952 16.4672 13.7433 16.6991C13.1913 16.931 12.5987 17.0505 12 17.0505C11.4013 17.0505 10.8087 16.931 10.2567 16.6991C9.70481 16.4672 9.2047 16.1275 8.78571 15.6999C8.3992 15.3054 8.40566 14.6722 8.80015 14.2857Z"/></svg>',
20
+ trigger: ':',
21
+ items: {
22
+ favorites: {
23
+ faceTearsJoy: '😂',
24
+ heart: '❤️',
25
+ rollingFloorLaughing: '🤣',
26
+ thumbsUpSign: '👍',
27
+ loudlyCryingFace: '😭',
28
+ foldedHands: '🙏',
29
+ throwingKiss: '😘',
30
+ smilingFaceSmilingEyesThreeHearts: '🥰',
31
+ smilingFaceHeartShapedEyes: '😍',
32
+ partyPopper: '🎉',
33
+ grinningFaceSmilingEyes: '😁',
34
+ fire: '🔥',
35
+ birthdayCake: '🎂',
36
+ flushedFace: '😳',
37
+ smilingFaceSunglasses: '😎',
38
+ sparkles: '✨',
39
+ eyes: '👀',
40
+ rightPointingBackhand: '👉',
41
+ hundredPointsSymbol: '💯',
42
+ poutingFace: '😡'
43
+ },
44
+ smileys: {
45
+ slightlySmilingFace: '🙂',
46
+ smile: '😄',
47
+ laughing: '😆',
48
+ wink: '😉',
49
+ heartEyes: '😍',
50
+ tongueOut: '😛',
51
+ blush: '😊',
52
+ smirk: '😏',
53
+ thinking: '🤔',
54
+ sleepy: '😪'
55
+ },
56
+ gestures: {
57
+ thumbsUp: '👍',
58
+ thumbsDown: '👎',
59
+ peaceSign: '✌️',
60
+ clappingHands: '👏',
61
+ raisingHands: '🙌',
62
+ facepalm: '🤦',
63
+ shrug: '🤷',
64
+ fistBump: '👊',
65
+ wavingHand: '👋',
66
+ okHand: '👌'
67
+ },
68
+ animals: {
69
+ dogFace: '🐶',
70
+ catFace: '🐱',
71
+ mouseFace: '🐭',
72
+ hamsterFace: '🐹',
73
+ rabbitFace: '🐰',
74
+ bearFace: '🐻',
75
+ pandaFace: '🐼',
76
+ lionFace: '🦁',
77
+ pigFace: '🐷',
78
+ frogFace: '🐸'
79
+ },
80
+ food: {
81
+ greenApple: '🍏',
82
+ pizza: '🍕',
83
+ hamburger: '🍔',
84
+ fries: '🍟',
85
+ spaghetti: '🍝',
86
+ sushi: '🍣',
87
+ iceCream: '🍨',
88
+ donut: '🍩',
89
+ cookie: '🍪',
90
+ cake: '🍰'
91
+ },
92
+ activities: {
93
+ soccerBall: '⚽',
94
+ basketball: '🏀',
95
+ football: '🏈',
96
+ baseball: '⚾',
97
+ tennis: '🎾',
98
+ bowling: '🎳',
99
+ golf: '🏌️‍♂️',
100
+ fishingPole: '🎣',
101
+ bicycle: '🚴',
102
+ videoGame: '🎮'
103
+ },
104
+ travel: {
105
+ airplane: '✈️',
106
+ car: '🚗',
107
+ bicycle: '🚲',
108
+ train: '🚆',
109
+ boat: '⛵',
110
+ map: '🗺️',
111
+ beachUmbrella: '🏖️',
112
+ mountain: '⛰️',
113
+ camping: '🏕️',
114
+ suitcase: '🧳'
115
+ }
116
+ }
117
+ },
118
+ subscribe: {
119
+ 'editor.keyup': function (event) {
120
+ if (!this.opts.is('emoji.trigger')) return
121
+ this._handle(event)
122
+ }
123
+ },
124
+ start () {
125
+ const button = {
126
+ title: '## emoji.emoji ##',
127
+ icon: this.opts.get('emoji.icon'),
128
+ command: 'emoji.popup'
129
+ }
130
+
131
+ this.handleStr = ''
132
+ this.handleLen = 1
133
+
134
+ this.app.toolbar.add('emoji', button)
135
+
136
+ if (this.opts.is('emoji.context')) {
137
+ this.app.context.add('emoji', button)
138
+ }
139
+ },
140
+ popup (e, button) {
141
+ const stack = this.app.create('stack')
142
+ stack.create('emoji', { width: '372px' })
143
+ const $modal = stack.getBody()
144
+
145
+ this._buildEmoji($modal)
146
+
147
+ // open
148
+ this.app.modal.open({ name: 'emoji', stack, button })
149
+ },
150
+
151
+ // =private
152
+ _handle (event) {
153
+ const e = event.get('e')
154
+ const key = e.which
155
+ const ctrl = e.ctrlKey || e.metaKey
156
+ const arrows = [37, 38, 39, 40]
157
+ const ks = this.app.keycodes
158
+
159
+ if (key === ks.ESC) {
160
+ this.app.editor.restore()
161
+ return
162
+ }
163
+ if (key === ks.DELETE || key === ks.SPACE || key === ks.SHIFT || ctrl || (arrows.indexOf(key) !== -1)) {
164
+ return
165
+ }
166
+
167
+ if (key === ks.BACKSPACE) {
168
+ this.handleLen = this.handleLen - 2
169
+ if (this.handleLen <= 0) {
170
+ this.handleLen = 1
171
+ this._hide()
172
+ } else if (this.handleLen <= 1) {
173
+ this._hide()
174
+ }
175
+ }
176
+
177
+ this._emit()
178
+ },
179
+ _emit () {
180
+ const selection = this.app.create('selection')
181
+ const trigger = this.opts.get('emoji.trigger')
182
+ const re = new RegExp('^' + trigger)
183
+ this.handleStr = selection.getText('before', this.handleLen)
184
+ this.handleStr2 = selection.getText('before', this.handleLen + 1)
185
+
186
+ // detect
187
+ if (re.test(this.handleStr)) {
188
+ if (this.handleStr2 && (this.handleStr2[0] === ' ' || this.handleStr2[0] === '' || this.handleStr2[0] === trigger)) {
189
+ this.handleStr = this.handleStr.replace(trigger, '')
190
+ this.handleLen++
191
+
192
+ if ((this.handleLen - 1) > 0) {
193
+ this._load()
194
+ }
195
+ }
196
+ }
197
+ },
198
+ _load () {
199
+ this._createPanel()
200
+ const sections = this._buildEmoji(this.$panel, this.handleStr, true)
201
+ if (sections === 0) {
202
+ this._hide()
203
+ }
204
+ },
205
+ _createPanel () {
206
+ this.$panel = this.app.panel.build(this, '_insertFromPanel')
207
+ this.$panel.addClass('rx-panel-emoji').css('max-width', '372px')
208
+
209
+ const scrollTop = this.app.getDoc().scrollTop()
210
+ const selection = this.app.create('selection')
211
+ const pos = selection.getPosition()
212
+
213
+ this.app.panel.open({ top: (pos.bottom + scrollTop), left: pos.left })
214
+ this.app.editor.save()
215
+ },
216
+ _buildEmoji ($modal, filter, panel) {
217
+ const items = this.opts.get('emoji.items')
218
+ let sections = 0
219
+ const type = (panel) ? 'panel' : 'emoji'
220
+
221
+ for (const [name, section] of Object.entries(items)) {
222
+ const $section = this.dom('<div class="rx-' + type + '-section">')
223
+ const $title = this.dom('<div class="rx-' + type + '-title">')
224
+ const $box = this.dom('<div class="rx-' + type + '-box">')
225
+ let size = 0
226
+ const title = (this.lang.has('emoji.' + name)) ? this.lang.get('emoji.' + name) : name.charAt(0).toUpperCase() + name.slice(1)
227
+
228
+ $title.html(title)
229
+ $section.append($title)
230
+ $section.append($box)
231
+
232
+ for (const [key, value] of Object.entries(section)) {
233
+ if (filter && filter !== '' && key.search(filter) === -1) continue
234
+
235
+ const $item = this.dom('<span class="rx-' + type + '-item">')
236
+ $item.html(value)
237
+
238
+ if (panel) {
239
+ $item.on('click', this._insertFromPanel.bind(this))
240
+ } else {
241
+ $item.on('click', this._insert.bind(this))
242
+ }
243
+
244
+ $box.append($item)
245
+ size++
246
+ }
247
+
248
+ if (size > 0) {
249
+ sections++
250
+ $modal.append($section)
251
+ }
252
+ }
253
+
254
+ return sections
255
+ },
256
+ _insert (e) {
257
+ this.app.modal.close()
258
+
259
+ e.preventDefault()
260
+ e.stopPropagation()
261
+
262
+ const $target = this.dom(e.target)
263
+ const value = $target.html()
264
+
265
+ const insertion = this.app.create('insertion')
266
+ insertion.insertText(value, 'after')
267
+ },
268
+ _insertFromPanel (e, $el) {
269
+ this.app.editor.restore()
270
+
271
+ const $item = ($el) || this.dom(e.target)
272
+ const replacement = $item.html()
273
+
274
+ const trigger = this.opts.get('emoji.trigger')
275
+ const offset = this.app.create('offset')
276
+ const selection = this.app.create('selection')
277
+ const current = selection.getCurrent()
278
+ let currentText = current.textContent
279
+ const offsetObj = offset.get()
280
+ const leftFix = (trigger + this.handleStr).length
281
+ const what = trigger + this.handleStr
282
+ const n = currentText.lastIndexOf(what)
283
+ if (n >= 0) {
284
+ currentText = currentText.substring(0, n) + replacement + currentText.substring(n + what.length)
285
+ }
286
+
287
+ current.textContent = currentText
288
+
289
+ offsetObj.start = offsetObj.start - leftFix + replacement.length
290
+ offsetObj.end = offsetObj.end - leftFix + replacement.length
291
+ offset.set(offsetObj)
292
+
293
+ // hide
294
+ this._hideForce()
295
+ },
296
+ _hidePanel (e) {
297
+ let hidable = false
298
+ const key = (e && e.which)
299
+ const ks = this.app.keycodes
300
+
301
+ if (!e) {
302
+ hidable = true
303
+ } else if (e.type === 'click' || key === ks.ESC || key === ks.SPACE) {
304
+ hidable = true
305
+ }
306
+
307
+ if (hidable) {
308
+ this._hideForce()
309
+ }
310
+ },
311
+ _hide () {
312
+ this.app.panel.close()
313
+ this._stopEvents()
314
+ },
315
+ _hideForce () {
316
+ this._hide()
317
+ this.handleStr = ''
318
+ this.handleLen = 1
319
+ },
320
+ _startEvents () {
321
+ const name = 'click.rx-plugin-emoji keydown.rx-plugin-emoji'
322
+
323
+ this.app.getDoc().on(name, this._hidePanel.bind(this))
324
+ this.app.editor.getEditor().on(name, this._hidePanel.bind(this))
325
+ },
326
+ _stopEvents () {
327
+ const name = '.rx-plugin-emoji'
328
+
329
+ this.app.getDoc().off(name)
330
+ this.app.editor.getEditor().off(name)
331
+ }
332
+ })
@@ -0,0 +1,124 @@
1
+ /* global Redactor */
2
+ Redactor.add('plugin', 'linkstyles', {
3
+ translations: {
4
+ en: {
5
+ linkstyles: {
6
+ label: 'Styles',
7
+ link: 'Link',
8
+ primary: 'Primary',
9
+ secondary: 'Secondary'
10
+ }
11
+ }
12
+ },
13
+ defaults: {
14
+ items: [
15
+ { name: 'link', value: '' },
16
+ { name: 'primary', value: 'button button-primary' },
17
+ { name: 'secondary', value: 'button button-secondary' }
18
+ ]
19
+ },
20
+ subscribe: {
21
+ 'modal.before.open': function () {
22
+ const name = this.app.modal.getName()
23
+ if (name === 'link') {
24
+ this.setDefaultValue()
25
+ }
26
+ },
27
+ 'modal.open': function () {
28
+ const name = this.app.modal.getName()
29
+ if (name === 'link') {
30
+ this.prepareModal()
31
+ }
32
+ },
33
+ 'link.change': function (e) {
34
+ const link = e.params.element.nodes[0]
35
+ this.applyStylingToLink(link)
36
+ },
37
+ 'link.add': function (e) {
38
+ const link = e.params.element.nodes[0]
39
+ this.applyStylingToLink(link)
40
+ }
41
+ },
42
+ init () {
43
+ this.selectedValue = ''
44
+ },
45
+ // private
46
+ prepareModal () {
47
+ const stack = this.app.modal.getStack()
48
+
49
+ const item = stack.getFormItem('url')
50
+ const box = this.dom('<div>').addClass('rx-form-item')
51
+
52
+ // Add a select
53
+ box.append(this.buildLabel())
54
+ box.append(this.buildSelect())
55
+
56
+ // Add select to the modal
57
+ item.after(box)
58
+ },
59
+ applyStylingToLink (link) {
60
+ // Clear all classes
61
+ link.classList.remove(...link.classList)
62
+
63
+ // Get styling
64
+ const classNames = this.selectedValue.split(' ')
65
+
66
+ // Apply classes if any
67
+ classNames.forEach((className) => {
68
+ if (className.length === 0) return
69
+ link.classList.add(className)
70
+ })
71
+ },
72
+ buildSelect () {
73
+ // Create a select node
74
+ const select = this.dom('<select>').addClass('rx-form-select')
75
+
76
+ // Populate the select with options
77
+ const items = this.opts.get('linkstyles.items')
78
+ items.forEach((data, index) => {
79
+ // Create an option node
80
+ const option = this.dom('<option>')
81
+
82
+ // Set value and text
83
+ option.val(data.value)
84
+ option.html(this.lang.get('linkstyles.' + data.name))
85
+
86
+ // Append option to select
87
+ select.append(option)
88
+ })
89
+
90
+ // Set the value of the select
91
+ select.val(this.selectedValue)
92
+
93
+ // Listen to select changes
94
+ select.on('change', (e) => {
95
+ this.selectedValue = e.target.value
96
+ })
97
+
98
+ return select
99
+ },
100
+ buildLabel () {
101
+ const label = this.dom('<label>').addClass('rx-form-label')
102
+ label.html(this.lang.get('linkstyles.label'))
103
+
104
+ return label
105
+ },
106
+ setDefaultValue () {
107
+ this.selectedValue = this.getLink().attr('class') || ''
108
+ },
109
+ getLink () {
110
+ const links = this.getLinks()
111
+
112
+ return (links.length !== 0) ? links.eq(0) : this.dom()
113
+ },
114
+ getLinks () {
115
+ const selection = this.app.create('selection')
116
+ if (!selection.is()) {
117
+ return this.dom()
118
+ }
119
+
120
+ const links = selection.getNodes({ tags: ['a'] })
121
+
122
+ return (links.length !== 0) ? this.dom(links) : this.dom()
123
+ }
124
+ })