dynamic_text 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3ffc36c55db6cf4d73096ccc3eda363d01d7f671d4b290d499c1c8f150fe83a0
4
+ data.tar.gz: 61ed3bf062c09cd3782aec624ce524d9b7a2b0456ddb9129bf0fd63ef608a62d
5
+ SHA512:
6
+ metadata.gz: 9908a0155a6b19a6d0a1e2a087c4ddd71d49d3d5a1a89d1cb70f0aa4fc3bf17a0fae01c4b3a9f5a4166855ea479f1f1b6025b4ffcf796936651513690d9c07ab
7
+ data.tar.gz: f8e3810e0999b4976851daf160a016e6b61b50492e4ee107c03f4f92324dfea8de165472535dea7f8ea319fd24762448d11ab0e28995f3b0b7fa83bc50826029
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Josh Hadik
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # DynamicText
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'dynamic_text'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install dynamic_text
22
+ ```
23
+
24
+ ## License
25
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'DynamicText'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/dynamic_text .js
2
+ //= link_directory ../stylesheets/dynamic_text .css
@@ -0,0 +1,175 @@
1
+ const DynamicText = {
2
+ // Insert Plain Text rather than HTML when pasting into editable text
3
+ insertPlainTextAtCursor(text) {
4
+ var sel, range, html;
5
+ if (window.getSelection) {
6
+ sel = window.getSelection();
7
+ if (sel.getRangeAt && sel.rangeCount) {
8
+ range = sel.getRangeAt(0);
9
+ range.deleteContents();
10
+ range.insertNode(document.createTextNode(text));
11
+ }
12
+ } else if (document.selection && document.selection.createRange) {
13
+ document.selection.createRange().text = text;
14
+ }
15
+ },
16
+
17
+ // Copy clipboard data from event and paste into editable text.
18
+ setEventClipboardData(e) {
19
+ const clipboardData = e.clipboardData || e.originalEvent.clipboardData
20
+ if (clipboardData && clipboardData.getData) {
21
+ const text = clipboardData.getData("text/plain")
22
+ document.execCommand("insertHTML", false, text)
23
+ return true
24
+ } else {
25
+ return false
26
+ }
27
+ },
28
+
29
+ // Copy clipboard data from window and paste into editable text.
30
+ setWindowClipboardData(e) {
31
+ const clipboardData = window.clipboardData
32
+ if (clipboardData && clipboardData.getData) {
33
+ const text = clipboardData.getData("Text")
34
+ this.insertPlainTextAtCursor(text);
35
+ return true
36
+ } else {
37
+ return false
38
+ }
39
+ },
40
+
41
+ // Copy clipboard data from event OR window and paste into editable text.
42
+ handleContentEditablePaste(e) {
43
+ e.preventDefault();
44
+ this.setEventClipboardData(e) || this.setWindowClipboardData()
45
+ },
46
+
47
+ // Store all ajax response functions
48
+ ajaxResponses: {
49
+ default: (data) => {}
50
+ },
51
+
52
+ // Set default response for ajax requests
53
+ handleDefaultAjaxResponse(func) {
54
+ this.ajaxResponses['default'] = func;
55
+ },
56
+
57
+ // Set response for specific ajax key
58
+ handleAjaxResponseFor(ajaxKey, func) {
59
+ this.ajaxResponses[ajaxKey] = func;
60
+ },
61
+
62
+ // Build ajax request for patch resource function.
63
+ sendPatchRequest(url, resourceType, attribute, updatedValue, ajaxKey) {
64
+ const CSRF_TOKEN =
65
+ document.querySelector('meta[name="csrf-token"]')
66
+ .getAttribute('content');
67
+
68
+ var xhr = new XMLHttpRequest();
69
+ var successCallback;
70
+ var data = {};
71
+
72
+ xhr.open('PATCH', url);
73
+
74
+ xhr.setRequestHeader('Accept', 'application/json; charset=utf-8')
75
+ xhr.setRequestHeader('Content-Type', 'application/json');
76
+ xhr.setRequestHeader('X-CSRF-Token', CSRF_TOKEN);
77
+
78
+ xhr.onload = () => {
79
+ if (xhr.status === 200) {
80
+ successCallback =
81
+ this.ajaxResponses[ajaxKey] || this.ajaxResponses['default']
82
+ successCallback(JSON.parse(xhr.responseText), resourceType)
83
+ }
84
+ };
85
+
86
+ data[resourceType] = {}
87
+ data[resourceType][attribute] = updatedValue
88
+
89
+ xhr.send(JSON.stringify(data));
90
+ },
91
+
92
+ // Update a specific attribute of a resource.
93
+ patchResource(e) {
94
+ const editableText = e.currentTarget;
95
+ const editableTextContainer = editableText.parentNode;
96
+ const action = editableTextContainer.querySelector("[name='_action']");
97
+
98
+ const url = action.getAttribute('url');
99
+ const resourceType = action.getAttribute('resource-type');
100
+ const attribute = action.getAttribute('attribute');
101
+ const ajaxKey = action.getAttribute('ajax-key');
102
+ const updatedValue = editableText.innerText;
103
+
104
+ this.sendPatchRequest(url, resourceType, attribute, updatedValue, ajaxKey)
105
+ }
106
+ }
107
+
108
+ const prepareDynamicText = () => {
109
+ document.querySelectorAll('.editable-text').forEach((editableText) => {
110
+ // Exit editable text editor mode (focus) on enter instead of adding a new line.
111
+ editableText.addEventListener('keydown', (e) => {
112
+ if (e.keyCode === 13) {
113
+ e.preventDefault();
114
+ e.currentTarget.blur();
115
+ }
116
+ });
117
+
118
+ // Properly paste text into editable text
119
+ editableText.addEventListener('paste', (e) => {
120
+ e.preventDefault();
121
+ DynamicText.handleContentEditablePaste(e)
122
+ });
123
+
124
+ // Disable dragging and dropping text/images into editable text.
125
+ ["dragover", "drop"].forEach((evt) => {
126
+ editableText.addEventListener(evt, (e) => {
127
+ e.preventDefault();
128
+ return false;
129
+ });
130
+ });
131
+
132
+ // Store original value when focusing in on specific editable text (used to determine if text was changed and patch request should be sent on focusout.)
133
+ editableText.addEventListener('focus', (e) => {
134
+ const target = e.currentTarget;
135
+ target.setAttribute('data-original-value', target.innerText);
136
+ });
137
+
138
+ // Send patch request for resource if content was changed.
139
+ editableText.addEventListener('focusout', (e) => {
140
+ const target = e.currentTarget
141
+
142
+ // Empty all content of text if editable text is empty (otherwise certain browsers fill in a default <br> or <p> value.)
143
+ if (!target.innerText.trim().length) {
144
+ target.innerText = null;
145
+ }
146
+
147
+ // Send patch request if text was changed.
148
+ if (target.getAttribute('data-original-value') != target.innerText) {
149
+ DynamicText.patchResource(e);
150
+ }
151
+
152
+ target.removeAttribute('data-original-value')
153
+ });
154
+
155
+ // Update all other divs tagged with the same dynamic-tag as the current text being edited. (So if you have two elements on one page that display the same title property of a resource, both will be updated in real time when you edit the text of one.)
156
+ editableText.addEventListener('input', (e) => {
157
+ const target = e.currentTarget;
158
+ const newValue = target.innerText;
159
+ const dynamicTag = target.getAttribute('data-dynamic-tag');
160
+
161
+ document.querySelectorAll(`[data-dynamic-tag='${dynamicTag}']`)
162
+ .forEach((dynamicTextElement) => {
163
+ if(dynamicTextElement !== target) {
164
+ dynamicTextElement.innerText = newValue;
165
+ }
166
+ });
167
+ });
168
+ })
169
+ }
170
+
171
+ const events = ["turbolinks:load", "page:change"];
172
+
173
+ events.forEach((evt) => {
174
+ document.addEventListener(evt, prepareDynamicText)
175
+ });
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require rails-ujs
14
+ //= require activestorage
15
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,22 @@
1
+ .dynamic-text-container {
2
+ display: inline-block;
3
+ }
4
+
5
+ .dynamic-text {
6
+ cursor: url('/assets/dynamic_text/pencil.png'), pointer;
7
+ display: block;
8
+ white-space: pre-line;
9
+ position: relative;
10
+ box-sizing: border-box;
11
+ padding: 1px;
12
+ }
13
+
14
+ .dynamic-text:focus {
15
+ cursor: text;
16
+ }
17
+
18
+ .dynamic-text[placeholder]:empty::before {
19
+ content: attr(placeholder);
20
+ color: #878787;
21
+ font-style: italic;
22
+ }
@@ -0,0 +1,99 @@
1
+ module DynamicText
2
+ module ViewHelper
3
+ def dynamic_text(text="", tag: "default", filler: "N/A")
4
+ text = filler if !text || text.empty?
5
+ content_tag(:span, text, class: "dynamic-text", data: {"dynamic-tag": tag})
6
+ end
7
+
8
+ def editable_text(text="", tag: "default", placeholder: "Enter text...")
9
+ content_tag(:span, class: "editable-text-container") do
10
+ content_tag(:span, text, class: "editable-text", contenteditable: true, placeholder: placeholder, data: {"dynamic-tag": tag})
11
+ end
12
+ end
13
+
14
+ def dynamic_text_for(resource, attribute, options={})
15
+ options = dynamic_attributes(resource, attribute, options)
16
+ dynamic_text_tag(options)
17
+ end
18
+
19
+ def editable_text_for(resource, attribute, options = {})
20
+ options = editable_attributes(resource, attribute, options)
21
+
22
+ content_tag(:span, class: "editable-text-container") do
23
+ editable_text_tag(options) +
24
+ patch_request_tag(options)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def dynamic_attributes(resource, attribute, options={})
31
+ Hash.new.tap do |hash|
32
+ hash[:resource] = resource
33
+ hash[:attribute] = attribute
34
+ hash[:value] = resource.send(attribute)
35
+ hash[:placeholder] = options[:placeholder] || "Enter #{attribute}..."
36
+ hash[:resource_type] = expected_resource_type(resource)
37
+ hash[:dynamic_tag] =
38
+ default_dynamic_tag(hash[:resource_type], resource.id, attribute)
39
+ end
40
+ end
41
+
42
+ def editable_attributes(resource, attribute, options={})
43
+ dynamic_attributes(resource, attribute, options={}).tap do |hash|
44
+ hash[:url] =
45
+ options[:url] || send("#{DynamicText.configuration.path_prefix}#{hash[:resource_type]}_path", resource)
46
+ hash[:ajax_key] =
47
+ options[:ajax_key] || default_ajax_key(hash[:resource_type], attribute)
48
+ end
49
+ end
50
+
51
+ def dynamic_text_tag(attributes)
52
+ text = attributes[:value] || attributes[:placeholder]
53
+ dynamic_text_attributes = {
54
+ class: "dynamic-text",
55
+ data: {"dynamic-tag": attributes[:dynamic_tag]}
56
+ }
57
+
58
+ content_tag(:span, text, dynamic_text_attributes)
59
+ end
60
+
61
+ def editable_text_tag(attributes)
62
+ editable_text_attributes = {
63
+ class: "editable-text",
64
+ contenteditable: true,
65
+ placeholder: attributes[:placeholder],
66
+ data: {"dynamic-tag": attributes[:dynamic_tag]}
67
+ }
68
+
69
+ content_tag(:span, attributes[:value], editable_text_attributes)
70
+ end
71
+
72
+ def patch_request_tag(attributes)
73
+ patch_request_attributes = {
74
+ 'url' => attributes[:url],
75
+ 'attribute' => attributes[:attribute],
76
+ 'resource-type' => attributes[:resource_type],
77
+ 'ajax-key' => attributes[:ajax_key]
78
+ }
79
+
80
+ hidden_field_tag("_action", "patch", patch_request_attributes)
81
+ end
82
+
83
+ def expected_resource_type(resource)
84
+ resource.class.name.downcase
85
+ end
86
+
87
+ def expected_path(resource, resource_type=expected_type_for(resource))
88
+ send("#{resource_type}_path", resource)
89
+ end
90
+
91
+ def default_dynamic_tag(resource_type, resource_id, attribute)
92
+ "#{resource_type}:#{resource_id}:#{attribute}"
93
+ end
94
+
95
+ def default_ajax_key(resource_type, attribute)
96
+ "#{resource_type}:#{attribute}"
97
+ end
98
+ end
99
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ DynamicText::Engine.routes.draw do
2
+ end
@@ -0,0 +1,11 @@
1
+ class DynamicText::Configuration
2
+ attr_reader :path_prefix
3
+
4
+ def initialize
5
+ @path_prefix = nil
6
+ end
7
+
8
+ def path_prefix=(prefix)
9
+ @path_prefix = "#{prefix}_"
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module DynamicText
2
+ class Engine < ::Rails::Engine
3
+ config.assets.precompile += %w(dynamic_text)
4
+ isolate_namespace DynamicText
5
+
6
+ initializer 'local_helper.action_controller' do
7
+ ActiveSupport.on_load :action_controller do
8
+ helper DynamicText::ViewHelper
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module DynamicText
2
+ VERSION = '0.0.3'
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'dynamic_text/engine'
2
+ require 'configuron'
3
+
4
+ module DynamicText
5
+ extend Configuron::Configurable
6
+ require_relative 'dynamic_text/configuration'
7
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamic_text
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Josh Hadik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: configuron
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: dynamic_text allows you to easily integrate content-editable HTML divs
56
+ for a specific attribute of a specific resource.
57
+ email:
58
+ - josh.hadik@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - MIT-LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - app/assets/config/dynamic_text_manifest.js
67
+ - app/assets/images/dynamic_text/pencil.png
68
+ - app/assets/javascripts/dynamic_text/dynamic_text.js
69
+ - app/assets/javascripts/dynamic_text/js.js
70
+ - app/assets/stylesheets/dynamic_text/css.css
71
+ - app/assets/stylesheets/dynamic_text/dynamic_text.scss
72
+ - app/helpers/dynamic_text/view_helper.rb
73
+ - config/routes.rb
74
+ - lib/dynamic_text.rb
75
+ - lib/dynamic_text/configuration.rb
76
+ - lib/dynamic_text/engine.rb
77
+ - lib/dynamic_text/version.rb
78
+ homepage:
79
+ licenses:
80
+ - MIT
81
+ metadata:
82
+ allowed_push_host: https://rubygems.org
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.0.2
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: A light-weight gem for adding content editable text to a rails project.
102
+ test_files: []