polivalente 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/app/assets/build/css/components/_bookmarks.scss +20 -0
  4. data/app/assets/build/css/components/_colors.scss +104 -0
  5. data/app/assets/build/css/components/_forms.scss +23 -0
  6. data/app/assets/build/css/components/_pagination.scss +75 -0
  7. data/app/assets/build/css/components/_player.scss +148 -0
  8. data/app/assets/build/css/components/_tags.scss +29 -0
  9. data/app/assets/build/css/components/_themes.scss +69 -0
  10. data/app/assets/build/css/components/_tribute.scss +25 -0
  11. data/app/assets/build/css/components/plyr.css +1 -0
  12. data/app/assets/build/css/components/trix/_clipboard.scss +27 -0
  13. data/app/assets/build/css/components/trix/_trix.scss +181 -0
  14. data/app/assets/build/css/index.css +19 -0
  15. data/app/assets/build/js/application.js +2 -0
  16. data/app/assets/build/js/controllers/accordion_controller.js +21 -0
  17. data/app/assets/build/js/controllers/dynamic_select_controller.js +22 -0
  18. data/app/assets/build/js/controllers/index.js +33 -0
  19. data/app/assets/build/js/controllers/offline_controller.js +19 -0
  20. data/app/assets/build/js/controllers/reactive_text_controller.js +15 -0
  21. data/app/assets/build/js/controllers/text_counter_controller.js +38 -0
  22. data/app/assets/build/js/controllers/theme_controller.js +36 -0
  23. data/app/assets/build/js/controllers/trix_attachment_blocker_controller.js +15 -0
  24. data/app/assets/build/js/controllers/trix_autocomplete_controller.js +86 -0
  25. data/app/assets/build/js/controllers/trix_blockcode_controller.js +41 -0
  26. data/app/assets/build/js/controllers/trix_clipboard_controller.js +50 -0
  27. data/app/assets/build/js/controllers/trix_color_controller.js +49 -0
  28. data/app/assets/build/js/controllers/trix_highlight_controller.js +55 -0
  29. data/app/assets/build/js/controllers/trix_plyr_controller.js +86 -0
  30. data/app/assets/build/js/controllers/trix_toolbar_controller.js +48 -0
  31. data/app/assets/config/polivalente_manifest.js +1 -0
  32. data/app/assets/javascripts/polivalente/application.js +173 -0
  33. data/app/assets/stylesheets/polivalente/application.css +3 -14
  34. data/app/assets/stylesheets/polivalente/styles.css +1928 -0
  35. data/app/controllers/polivalente/application_controller.rb +2 -0
  36. data/app/controllers/polivalente/archives_controller.rb +18 -0
  37. data/app/controllers/polivalente/autocomplete_controller.rb +2 -3
  38. data/app/controllers/polivalente/comments_controller.rb +27 -0
  39. data/app/controllers/polivalente/trashes_controller.rb +18 -0
  40. data/app/controllers/polivalente/users_controller.rb +42 -0
  41. data/app/helpers/polivalente/gravatar_helper.rb +0 -4
  42. data/app/models/polivalente/tag.rb +1 -0
  43. data/app/views/layouts/polivalente/application.html.erb +9 -3
  44. data/app/views/polivalente/archives/_archive.html.erb +2 -0
  45. data/app/views/polivalente/archives/index.html.erb +3 -0
  46. data/app/views/polivalente/archives/show.html.erb +3 -0
  47. data/app/views/polivalente/autocomplete/tags.json.jbuilder +1 -0
  48. data/app/views/polivalente/autocomplete/users.json.jbuilder +1 -0
  49. data/app/views/polivalente/shared/_notices.html.erb +10 -0
  50. data/app/views/polivalente/shared/_offline_indicator.html.erb +10 -0
  51. data/app/views/polivalente/shared/_theme_toggle.html.erb +1 -0
  52. data/app/views/polivalente/tags/_cloud.html.erb +7 -0
  53. data/app/views/polivalente/tags/_list.html.erb +5 -0
  54. data/app/views/polivalente/tags/_tag.html.erb +1 -0
  55. data/app/views/polivalente/tags/_tag.json.jbuilder +4 -0
  56. data/app/views/polivalente/trashes/_trash.html.erb +2 -0
  57. data/app/views/polivalente/trashes/index.html.erb +3 -0
  58. data/app/views/polivalente/trashes/show.html.erb +3 -0
  59. data/app/views/polivalente/users/_form.html.erb +38 -0
  60. data/app/views/polivalente/users/_user.html.erb +4 -0
  61. data/app/views/polivalente/users/_user.json.jbuilder +4 -0
  62. data/app/views/polivalente/users/activities/_activity.html.erb +12 -0
  63. data/app/views/polivalente/users/activities/_recent.html.erb +17 -0
  64. data/app/views/polivalente/users/edit.html.erb +13 -0
  65. data/app/views/polivalente/users/related/_user.html.erb +14 -0
  66. data/app/views/polivalente/users/related/_users.html.erb +14 -0
  67. data/app/views/polivalente/users/show.html.erb +180 -0
  68. data/config/locales/en.yml +72 -0
  69. data/config/locales/es.yml +72 -0
  70. data/config/locales/fr.yml +74 -0
  71. data/config/locales/pt.yml +72 -0
  72. data/config/routes.rb +6 -0
  73. data/lib/generators/polivalente/install/install_generator.rb +1 -1
  74. data/lib/generators/polivalente/locales/locales_generator.rb +11 -0
  75. data/lib/generators/polivalente/templates/user.rb +3 -0
  76. data/lib/polivalente/engine.rb +1 -0
  77. data/lib/polivalente/version.rb +1 -1
  78. data/lib/polivalente.rb +3 -0
  79. metadata +107 -2
@@ -0,0 +1,181 @@
1
+ /* Configuration and General Appearance */
2
+
3
+ trix-toolbar {
4
+ .trix-button--icon {
5
+ @apply text-primary;
6
+ @apply bg-gray-100;
7
+ }
8
+
9
+ @apply py-2 px-2 rounded-sm rounded-b-none;
10
+ }
11
+
12
+ /* Editor */
13
+
14
+ trix-editor {
15
+ @apply rounded shadow-sm;
16
+ @apply w-full overflow-x-auto overflow-y-scroll;
17
+ max-height: 500px;
18
+
19
+ code {
20
+ @apply dark:bg-gray-700 bg-gray-200;
21
+ @apply py-0.5 px-1 rounded-sm;
22
+ }
23
+
24
+ pre {
25
+ @apply border border-tertiary bg-tertiary;
26
+ @apply p-4 my-2 rounded;
27
+ }
28
+
29
+ figure {
30
+ all: initial;
31
+ line-height: 0;
32
+ color: inherit;
33
+ font-family: inherit;
34
+
35
+ .mention {
36
+ @apply text-white no-underline;
37
+ @apply inline-flex space-x-1 p-0.5 items-center rounded;
38
+ @apply bg-blue-600 hover:bg-blue-500;
39
+ @apply border border-blue-600 hover:border-blue-600;
40
+
41
+ & + figcaption, & .attachment__caption {
42
+ line-height: 0;
43
+ @apply hidden;
44
+ }
45
+
46
+ img {
47
+ @apply w-4 h-4 rounded-full border border-tertiary p-0 m-0;
48
+ line-height: 0;
49
+ }
50
+
51
+ p {
52
+ @apply p-0 m-0;
53
+ line-height: 0;
54
+ }
55
+ }
56
+ }
57
+
58
+ .attachment__caption-editor {
59
+ height: 20px !important;
60
+ }
61
+
62
+ /* file attachment previews */
63
+ .attachment-gallery {
64
+ @apply grid grid-cols-4 gap-3;
65
+
66
+ [data-trix-cursor-target] { display: none !important; }
67
+
68
+ figcaption.attachment__caption { @apply text-xs; }
69
+
70
+ .attachment--preview {
71
+ @apply border border-gray-50;
72
+ img { @apply rounded-md shadow; }
73
+ }
74
+ }
75
+ }
76
+
77
+ /* Content Viewer */
78
+
79
+ .trix-content {
80
+ figure {
81
+ &.attachment.attachment--file {
82
+ @apply py-2 w-full border-none;
83
+ }
84
+ }
85
+
86
+ code {
87
+ @apply rounded bg-tertiary text-primary p-0.5 font-mono font-thin text-sm;
88
+ }
89
+
90
+ .tooltip {
91
+ @apply relative py-1 rounded-sm w-7 h-5 inline-block;
92
+
93
+ .tooltip-text {
94
+ @apply absolute;
95
+ @apply text-center font-mono text-sm;
96
+ @apply p-1 right-0 rounded-l rounded-tr;
97
+ visibility: hidden;
98
+ bottom: 90%;
99
+ margin-left: -60px;
100
+ z-index: 1;
101
+ }
102
+
103
+ /* Show the tooltip text when you mouse over the tooltip container */
104
+ &:hover {
105
+ .tooltip-text {
106
+ visibility: visible;
107
+ }
108
+ }
109
+ }
110
+
111
+ /* @mentions */
112
+ action-text-attachment {
113
+ all: initial;
114
+ font-family: inherit;
115
+
116
+ p, a, div, img {
117
+ line-height: 0;
118
+ padding: 0;
119
+ margin: 0;
120
+ }
121
+
122
+ .mention {
123
+ @apply text-white no-underline;
124
+ @apply inline-flex space-x-1 p-0.5 items-center rounded;
125
+ @apply bg-blue-600 hover:bg-blue-500;
126
+ @apply border border-blue-600 hover:border-blue-600;
127
+
128
+ img {
129
+ @apply w-4 h-4 rounded-full border border-tertiary;
130
+ }
131
+ }
132
+ }
133
+
134
+ /* file attachment previews */
135
+ .attachment-gallery {
136
+ all: initial;
137
+ color: inherit;
138
+ * {
139
+ all: initial;
140
+ color: inherit;
141
+ font-family: inherit;
142
+ }
143
+
144
+ @apply grid gap-4 grid-cols-3 py-8 m-0;
145
+
146
+ .attachment {
147
+ @apply border border-gray-50;
148
+
149
+ img {
150
+ @apply w-full h-auto rounded-md;
151
+ @apply hover:scale-105 transform transition shadow;
152
+ }
153
+ }
154
+ }
155
+
156
+ figure.attachment {
157
+ img {
158
+ @apply border border-tertiary rounded-md shadow object-cover;
159
+ }
160
+ }
161
+
162
+ .attachment--preview {
163
+ img { @apply w-full h-full !important; }
164
+ }
165
+ }
166
+
167
+ trix-editor, .trix-content {
168
+ /* Media: Audio and Video */
169
+ .plyr {
170
+ @apply rounded-md;
171
+ }
172
+
173
+ .attachment {
174
+ &--mp3, &--mp4 {
175
+ img { @apply hidden; }
176
+ .attachment__caption {
177
+ @apply bg-inverted text-inverted border border-primary rounded p-1.5;
178
+ }
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,19 @@
1
+ @import "tailwindcss/base";
2
+ @import "tailwindcss/components";
3
+ @import "tailwindcss/utilities";
4
+
5
+ /*! purgecss start ignore */
6
+ @import "./components/_themes.scss";
7
+ @import "./components/_tribute.scss";
8
+
9
+ @import "./components/trix/_clipboard.scss";
10
+ @import "./components/trix/_trix.scss";
11
+
12
+ @import "./components/_player.scss";
13
+ @import "./components/_pagination.scss";
14
+ @import "./components/_forms.scss";
15
+ @import "./components/_colors.scss";
16
+ @import "./components/_tags.scss";
17
+
18
+ @import "./components/plyr.css";
19
+ /*! purgecss end ignore */
@@ -0,0 +1,2 @@
1
+ import "@rails/actiontext"
2
+ import "./controllers"
@@ -0,0 +1,21 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ // Connects to data-controller="accordion"
4
+ export default class extends Controller {
5
+ static targets = []
6
+
7
+ toggle(event) {
8
+ let accordion = event.currentTarget.parentNode
9
+ let body = accordion.querySelectorAll('[data-role=description]')[0]
10
+ let icons = accordion.querySelectorAll('[data-role=icon]')
11
+
12
+ // Toggle body
13
+ body.classList.toggle('hidden')
14
+
15
+ // Toggle Icon
16
+ icons.forEach(icon => icon.classList.toggle('hidden'))
17
+
18
+ // Highlight
19
+ accordion.classList.toggle('border-l-2')
20
+ }
21
+ }
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { get as request } from "@rails/request.js"
3
+
4
+ // Connects to data-controller="dynamic-select"
5
+ export default class extends Controller {
6
+ static targets = ['select']
7
+
8
+ static values = {
9
+ url: String,
10
+ param: String
11
+ }
12
+
13
+ change(event) {
14
+ let params = new URLSearchParams()
15
+ params.append(this.paramValue, event.target.selectedOptions[0].value)
16
+ params.append('target', this.selectTarget.id)
17
+
18
+ request(`${this.urlValue}?${params}`, {
19
+ responseKind: 'turbo-stream'
20
+ })
21
+ }
22
+ }
@@ -0,0 +1,33 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+ window.Stimulus = Application.start()
3
+
4
+ // Application
5
+ import AccordionController from "./accordion_controller"
6
+ import DynamicSelectController from "./dynamic_select_controller"
7
+ import OfflineController from "./offline_controller"
8
+ import ReactiveTextController from "./reactive_text_controller"
9
+ import TextCounterController from "./text_counter_controller"
10
+ import ThemeController from "./theme_controller"
11
+
12
+ // Trix Text Editor (ActionText)
13
+ import TrixAttachmentBlockerController from "./trix_attachment_blocker_controller"
14
+ import TrixAutocompleteController from "./trix_autocomplete_controller"
15
+ import TrixBlockcodeController from "./trix_blockcode_controller"
16
+ import TrixClipboardController from "./trix_clipboard_controller"
17
+ import TrixColorController from "./trix_color_controller"
18
+ import TrixHighlightController from "./trix_highlight_controller"
19
+
20
+ Stimulus.register("accordion", AccordionController)
21
+ Stimulus.register("dynamic-select", DynamicSelectController)
22
+ Stimulus.register("offline", OfflineController)
23
+ Stimulus.register("reactive-text", ReactiveTextController)
24
+ Stimulus.register("text-counter", TextCounterController)
25
+ Stimulus.register("theme", ThemeController)
26
+
27
+ // Trix configuration and behavior
28
+ Stimulus.register("trix-attachment-blocker", TrixAttachmentBlockerController)
29
+ Stimulus.register("trix-autocomplete", TrixAutocompleteController)
30
+ Stimulus.register("trix-blockcode", TrixBlockcodeController)
31
+ Stimulus.register("trix-clipboard", TrixClipboardController)
32
+ Stimulus.register("trix-color", TrixColorController)
33
+ Stimulus.register("trix-highlight", TrixHighlightController)
@@ -0,0 +1,19 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ // Connects to data-controller="offline"
4
+ export default class extends Controller {
5
+ static targets = ['indicator']
6
+
7
+ connect() {
8
+ // This forces an offline message even if the browser does not fire an event.
9
+ // Like when the page is loaded from cache, for example.
10
+ this.setNetworkStatus()
11
+
12
+ window.addEventListener('offline', () => this.setNetworkStatus())
13
+ window.addEventListener('online', () => this.setNetworkStatus())
14
+ }
15
+
16
+ setNetworkStatus() {
17
+ this.indicatorTarget.classList.toggle('hidden', navigator.onLine)
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="reactive-text"
4
+ export default class extends Controller {
5
+ static targets = ["source", "output"]
6
+
7
+ connect() {
8
+ this.sourceTarget.addEventListener('input', this.sync.bind(this))
9
+ this.outputTargets.forEach(t => t.textContent = this.sourceTarget.value)
10
+ }
11
+
12
+ sync(event) {
13
+ this.outputTargets.forEach(t => t.textContent = event.target.value)
14
+ }
15
+ }
@@ -0,0 +1,38 @@
1
+ import {Controller} from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="text-counter"
4
+ export default class extends Controller {
5
+ static targets = ["source", "output"]
6
+ static classes = ["error"]
7
+ static values = {
8
+ limit: Number,
9
+ wordMode: Boolean
10
+ }
11
+
12
+ connect() {
13
+ this.sourceTarget.addEventListener('input', this.sync.bind(this))
14
+ this.sync()
15
+ }
16
+
17
+ _contentLength(content, wordMode) {
18
+ if (wordMode) content = content.split(" ")
19
+
20
+ if (typeof content === "object" && content.length === 1 && content[0] === "")
21
+ return 0
22
+
23
+ return content.length
24
+ }
25
+
26
+ sync(_) {
27
+ this.outputTargets.forEach(t => {
28
+ let length = this._contentLength(this.sourceTarget.value, this.wordModeValue)
29
+ t.textContent = `${length} ${this.wordModeValue ? "words" : "characters"}`
30
+
31
+ if (this.limitValue)
32
+ if (length > this.limitValue)
33
+ t.classList.add(...this.errorClasses)
34
+ else
35
+ t.classList.remove(...this.errorClasses)
36
+ })
37
+ }
38
+ }
@@ -0,0 +1,36 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ["theme", "toggle"]
5
+
6
+ get shouldEnable() {
7
+ if (!this['hasToggleTarget']) {
8
+ console.debug('No toggle component found for theme controller');
9
+ return false;
10
+ }
11
+
12
+ return true;
13
+ }
14
+
15
+ connect() {
16
+ this.loadTheme();
17
+
18
+ if (!this.shouldEnable) return;
19
+
20
+ this.theme = localStorage.theme;
21
+ this['themeTarget'].classList = [this.theme];
22
+ }
23
+
24
+ toggleTheme(event) {
25
+ this.theme = event.target.value;
26
+ localStorage.theme = this.theme;
27
+ this['themeTarget'].classList = [this.theme];
28
+ }
29
+
30
+ loadTheme() {
31
+ this.theme = localStorage.theme;
32
+ this['themeTarget'].classList = [this.theme];
33
+
34
+ if (this['hasToggleTarget']) this['toggleTarget'].value = this.theme;
35
+ }
36
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="trix-attachment-blocker"
4
+ export default class extends Controller {
5
+ static targets = []
6
+
7
+ connect() {
8
+ console.log("trix-attachment-blocker")
9
+
10
+ window.addEventListener('trix-file-accept', (event) => {
11
+ event.preventDefault()
12
+ alert("File attachments are not supported at this time.")
13
+ })
14
+ }
15
+ }
@@ -0,0 +1,86 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import Tribute from "tributejs"
3
+ import Trix from "trix"
4
+
5
+ // Connects to data-controller="trix-autocomplete"
6
+ export default class extends Controller {
7
+ static values = {
8
+ url: String,
9
+ searchKey: String,
10
+ mode: String,
11
+ trigger: String,
12
+ auto: Boolean
13
+ }
14
+
15
+ connect() {
16
+ console.debug("trix-autocomplete")
17
+
18
+ if (!this.searchKeyValue)
19
+ this.searchKeyValue = 'query'
20
+
21
+ if (!this.modeValue)
22
+ this.modeValue = this.element.editor ? 'trix' : 'input'
23
+
24
+ if (!this.urlValue) {
25
+ console.debug("Autocomplete needs both URL value (i.e. 'https://autocomplete.com')")
26
+ return this.disconnect()
27
+ }
28
+
29
+ this.initializeTribute()
30
+ this.listen()
31
+ }
32
+
33
+ disconnect() {
34
+ this.tribute.detach(this.element)
35
+ }
36
+
37
+ initializeTribute() {
38
+ this.tribute = new Tribute({
39
+ trigger: this.triggerValue || '@',
40
+ allowSpaces: true,
41
+ lookup: "name",
42
+ fillAttr: this.modeValue === 'trix' ? 'value' : 'name',
43
+ autocompleteMode: this.autoValue,
44
+ replaceTextSuffix: ', ',
45
+ values: this._fetch.bind(this)
46
+ })
47
+ this.tribute.attach((this.element))
48
+ this.tribute.range.pasteHtml = this._pasteHTML.bind(this)
49
+ }
50
+
51
+ listen() {
52
+ this.element.addEventListener("tribute-replaced", (event) => {
53
+ let completion = event.detail.item.original
54
+
55
+ if (this.modeValue === 'trix')
56
+ this._insertTrixAttachment(completion)
57
+ })
58
+ }
59
+
60
+ _fetch(value, callback) {
61
+ let params = new URLSearchParams()
62
+ params.append(this.searchKeyValue, value)
63
+
64
+ fetch(`${this.urlValue}?${params}`)
65
+ .then(response => response.json())
66
+ .then(data => callback(data))
67
+ .catch(callback([]))
68
+ }
69
+
70
+ _insertTrixAttachment(completion) {
71
+ let attachment = new Trix.Attachment({
72
+ sgid: completion.sgid,
73
+ content: completion.content
74
+ })
75
+ this.element.editor.insertAttachment(attachment)
76
+ this.element.editor.insertString("")
77
+ }
78
+
79
+ _pasteHTML(_, start, end) {
80
+ let range = this.element.editor.getSelectedRange()
81
+ let position = range[0]
82
+ let length = end - start
83
+ this.element.editor.setSelectedRange([position - length, position])
84
+ this.element.editor.deleteInDirection('backward')
85
+ }
86
+ }
@@ -0,0 +1,41 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import Trix from "trix"
3
+
4
+ // Connects to data-controller="trix-blockcode"
5
+ export default class extends Controller {
6
+ connect() {
7
+ Trix.config.textAttributes.inlineCode = {
8
+ tagName: "code",
9
+ inheritable: true
10
+ }
11
+
12
+ window.addEventListener("trix-initialize", event => {
13
+ const element = event.target
14
+ const { toolbarElement, editor } = element
15
+
16
+ const blockCodeButton = toolbarElement.querySelector("[data-trix-attribute=code]")
17
+ const inlineCodeButton = blockCodeButton.cloneNode(true)
18
+
19
+ inlineCodeButton.hidden = true
20
+ inlineCodeButton.dataset.trixAttribute = "inlineCode"
21
+ blockCodeButton.insertAdjacentElement("afterend", inlineCodeButton)
22
+
23
+ element.addEventListener("trix-selection-change", _ => {
24
+ const type = getCodeFormattingType()
25
+ blockCodeButton.hidden = type == "inline"
26
+ inlineCodeButton.hidden = type == "block"
27
+ })
28
+
29
+ const getCodeFormattingType = () => {
30
+ if (editor.attributeIsActive("code")) return "block"
31
+ if (editor.attributeIsActive("inlineCode")) return "inline"
32
+
33
+ const range = editor.getSelectedRange()
34
+ if (range[0] == range[1]) return "block"
35
+
36
+ const text = editor.getSelectedDocument().toString().trim()
37
+ return /\n/.test(text) ? "block" : "inline"
38
+ }
39
+ })
40
+ }
41
+ }
@@ -0,0 +1,50 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="trix-clipboard"
4
+ export default class extends Controller {
5
+ static targets = ['code']
6
+
7
+ get defaults() {
8
+ return {
9
+ actionElements: ['BUTTON'],
10
+ codeElements: ['PRE'],
11
+ codeClasses: ['hljs']
12
+ }
13
+ }
14
+
15
+ get trix() {
16
+ return this.element.children[0];
17
+ }
18
+
19
+ connect() {
20
+ if (document.queryCommandSupported("copy")) {
21
+ this.element.classList.add("clipboard--supported")
22
+
23
+ this.element.addEventListener('click', (event) => {
24
+ const {target} = event
25
+
26
+ if (this._isActionTarget(target)) {
27
+ const code = this._getCodeSibling(target)
28
+ if (code) this.copy(code)
29
+ }
30
+ })
31
+ }
32
+ }
33
+
34
+ _isActionTarget(element) {
35
+ return this.defaults.actionElements.includes(element.tagName)
36
+ }
37
+
38
+ _getCodeSibling(element) {
39
+ const snippet = element.nextSibling
40
+ const isCode = this.defaults.codeElements.includes(snippet.tagName)
41
+ const isHljs = snippet.classList.contains(this.defaults.codeClasses)
42
+
43
+ return (isCode && isHljs) ? snippet.innerText : false
44
+ }
45
+
46
+ copy(code) {
47
+ navigator.clipboard.writeText(code)
48
+ .then(() => { console.log('Copied code snippet!') })
49
+ }
50
+ }
@@ -0,0 +1,49 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+ import { ntc as nameThatColor } from '@cosmicice/namethatcolor'
3
+
4
+ // Connects to data-controller="trix-color"
5
+ export default class extends Controller {
6
+ get trix() {
7
+ return this.element.children[0]
8
+ }
9
+
10
+ get colorTest() {
11
+ return /color#[0-9a-f]{3,6}/gi
12
+ }
13
+
14
+ connect() {
15
+ let content = this.trix.innerHTML
16
+ this.trix.innerHTML = content.replace(this.colorTest, this._color)
17
+ }
18
+
19
+ _color(value, position, offset) {
20
+ // Drop the color prefix
21
+ const backgroundColor = value.replace('color', '')
22
+ let c = backgroundColor.replace('#', '')
23
+
24
+ if (c.length === 3) {
25
+ // Support shorthand notation. i.e #c0f
26
+ // c0f => cc00ff;
27
+ c = `${c[0]}${c[0]}${c[1]}${c[1]}${c[2]}${c[2]}`
28
+ }
29
+
30
+ // YIQ color contrast calculation:
31
+ // https://24ways.org/2010/calculating-color-contrast
32
+ // https://gorails.com/episodes/contrasting-colors-with-yiq
33
+ const r = parseInt(c.substr(0,2),16)
34
+ const g = parseInt(c.substr(2,2),16)
35
+ const b = parseInt(c.substr(4,2),16)
36
+ const yiq = ((r*299) + (g*587) + (b*114)) / 1000
37
+ const foregroundColor = (yiq >= 128) ? 'black' : 'white'
38
+
39
+ const colorName = nameThatColor.name(backgroundColor).name
40
+
41
+ return `
42
+ <span class="tooltip border" style="background: ${backgroundColor} !important;">
43
+ <span sr-only="${colorName}" class="tooltip-text" style="background: ${backgroundColor} !important; color: ${foregroundColor} !important;">
44
+ ${backgroundColor} (${colorName})
45
+ </span>
46
+ </span>
47
+ `
48
+ }
49
+ }