dynamic_text 0.0.3

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
+ 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: []