actiontext-syntax-highlighter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +20 -0
  3. data/README.md +100 -0
  4. data/Rakefile +38 -0
  5. data/app/assets/javascripts/action_text_syntax_highlighter.js +2 -0
  6. data/app/assets/javascripts/action_text_syntax_highlighter.js.map +1 -0
  7. data/app/controllers/action_text_syntax_highlighter/highlighted_code_blocks_controller.rb +31 -0
  8. data/app/frontend/action_text_syntax_highlighter/css/highlighted_code_block.css +39 -0
  9. data/app/frontend/action_text_syntax_highlighter/index.js +3 -0
  10. data/app/frontend/action_text_syntax_highlighter/javascript/actiontext_syntax_highlighter.js +17 -0
  11. data/app/frontend/action_text_syntax_highlighter/javascript/helpers.js +11 -0
  12. data/app/frontend/action_text_syntax_highlighter/javascript/highlighted_code_block.js +69 -0
  13. data/app/frontend/action_text_syntax_highlighter/javascript/trix_code_block_highlighter.js +81 -0
  14. data/app/jobs/action_text_syntax_highlighter/purge_deleted_highlighted_code_blocks_job.rb +11 -0
  15. data/app/models/action_text_syntax_highlighter/highlighted_code_block.rb +50 -0
  16. data/app/models/action_text_syntax_highlighter/highlighted_code_block/supported_languages.rb +11 -0
  17. data/app/views/action_text_syntax_highlighter/highlighted_code_blocks/_editor.html.erb +8 -0
  18. data/app/views/action_text_syntax_highlighter/highlighted_code_blocks/_highlighted_code_block.html.erb +4 -0
  19. data/config/routes.rb +6 -0
  20. data/db/migrate/20210531195232_create_action_text_highlighted_code_blocks.rb +11 -0
  21. data/lib/action_text_syntax_highlighter.rb +17 -0
  22. data/lib/action_text_syntax_highlighter/engine.rb +32 -0
  23. data/lib/action_text_syntax_highlighter/has_highlighted_code_blocks.rb +14 -0
  24. data/lib/action_text_syntax_highlighter/pre_tags_to_highlighted_code_blocks_conversion.rb +32 -0
  25. data/lib/action_text_syntax_highlighter/version.rb +5 -0
  26. data/lib/tasks/action_text_syntax_highlighter_tasks.rake +18 -0
  27. metadata +99 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 31390bf8597bfd8d545a61a1682161e6e5e7096411cd89e1bcf981aa358eb0b2
4
+ data.tar.gz: be598fa01d142a066f9c736594f7b58e67cd26338f1349f4ae60ba531003c4d4
5
+ SHA512:
6
+ metadata.gz: 05bf47fa9c3a47e153b51a63a532e806fe18d6b3aa8d68a49c0ad9c7d04613f794971f2bd3efa81020c0204a3a8900fbabc75187e430905195a98e9e375d3d78
7
+ data.tar.gz: 3d8c27f3a029bb7b13ec6e4b95ad1a3a39150632a41cc0e388f732a2b5348f8f9f61a0fa1c577d3e9c80b52c68401653886c5dc34f7b3590b1a5d62a46455feb
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Ayush Newatia
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,100 @@
1
+ # Description
2
+
3
+ `actiontext-syntax-highlighter` is an extension for `rails/actiontext` to allow the user to add language specified code blocks that are then highlighted on the server using [Rouge](http://github.com/rouge-ruby/rouge).
4
+
5
+ [**Check out the demo app to see how it works!**](https://actiontext-syntax-highlighter.herokuapp.com).
6
+
7
+ > Note: The experience is a bit flaky, especially on Safari. If you have any suggestions for how to improve it, please file an issue; or even better, open a Pull Request!
8
+
9
+ ## How it works
10
+
11
+ This library intercepts the toolbar code button in the Trix editor and creates an attachment containing the HTML for an editable code block and language selection dropdown.
12
+
13
+ The changes to the code block are saved to the server every time edits are made. When the code block is being rendered out for viewing, it uses Rouge to highlight the content using the language selected in the dropdown; or attempting to guess the language if it's missing.
14
+
15
+
16
+ ## Installation
17
+
18
+ Install ActionText if you haven't already done so:
19
+
20
+ ```shell
21
+ $ bin/rails action_text:install
22
+ ```
23
+
24
+ Then add this gem to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'actiontext-syntax-highlighter', require: 'action_text_syntax_highlighter/engine'
28
+ ```
29
+
30
+ And bundle and install it:
31
+
32
+ ```bash
33
+ $ bundle
34
+ $ bin/rails action_text_syntax_highlighter:install
35
+ ```
36
+
37
+ This will copy the database migrations into place and install the Yarn module for the frontend code.
38
+
39
+
40
+ ## Usage
41
+
42
+ Add the following lines to your `application.js`:
43
+
44
+ ```javascript
45
+ import { ActionTextSyntaxHighlighter } from "@ayushn21/actiontext-syntax-highlighter"
46
+ ActionTextSyntaxHighlighter.start()
47
+ ```
48
+
49
+ Import the CSS for a theme from the `themes` directory:
50
+
51
+ ```javascript
52
+ import "@ayushn21/actiontext-syntax-highlighter/themes/github.css"
53
+ ```
54
+
55
+ The default theme is set to GitHub. If you wish to use another theme, import its CSS file and set the following config option in your `application.rb`:
56
+
57
+ ```ruby
58
+ config.action_text_syntax_highlighter.default_theme = :base16
59
+ ```
60
+
61
+ You can override the default theme by setting `@highlighted_code_block_theme` in your controller action where you're rendering out the rich text.
62
+
63
+ This plugin requires a global `Trix` variable to create attachments, so require Trix in your application as below:
64
+
65
+ ```javascript
66
+ window.Trix = require("trix")
67
+ ```
68
+
69
+ Finally, add a `data-highlights-code-blocks='true'` attribute to the Trix editor where you'd like to use this plugin:
70
+
71
+ ```erb
72
+ <%= f.rich_text_area :content, data: { highlights_code_blocks: true } %>
73
+ ```
74
+
75
+ ### Bundled JavaScript
76
+
77
+ If you wish, you can import pre-bundled JavaScript instead of the module as described above:
78
+
79
+ ```javascript
80
+ import "@ayushn21/actiontext-syntax-highlighter/dist"
81
+ ```
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it (https://github.com/ayushn21/actiontext-syntax-highlighter/fork)
86
+ 2. Clone the fork using `git clone` to your local development machine.
87
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
89
+ 5. Push to the branch (`git push origin my-new-feature`)
90
+ 6. Create a new Pull Request
91
+
92
+ > If you've made a change to the frontend code, please also run `rake frontend:build` before opening your PR!
93
+
94
+ ## Info
95
+
96
+ This gem was extracted from [chapter24.app](https://chapter24.app)
97
+
98
+
99
+ ## License
100
+ 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,38 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+ load "rails/tasks/statistics.rake"
6
+
7
+ require "bundler/gem_tasks"
8
+ require "rake/testtask"
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = false
14
+ t.warning = false
15
+ end
16
+
17
+ task default: :test
18
+
19
+ namespace :frontend do
20
+ task :transpile do
21
+ `bin/transpile`
22
+ end
23
+
24
+ task :bundle do
25
+ p "Bundling frontend for use in browser..."
26
+
27
+ `yarn webpack --mode production`
28
+ `cp dist/index.js app/assets/javascripts/action_text_syntax_highlighter.js`
29
+ `cp dist/index.js.map app/assets/javascripts/action_text_syntax_highlighter.js.map`
30
+
31
+ p "Done!"
32
+ end
33
+
34
+ task :build do
35
+ Rake::Task["frontend:transpile"].execute
36
+ Rake::Task["frontend:bundle"].execute
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ (()=>{"use strict";var t={990:(t,e,n)=>{n.d(e,{Z:()=>r});var o=n(268),i=n.n(o)()(!0);i.push([t.id,"trix-editor pre[contenteditable] {\n -webkit-user-select: text;\n user-select: text;\n}\n\ntrix-editor pre[contenteditable]::selection {\n background: highlight;\n color: highlightText;\n}\n\ntrix-editor select[name='language'] {\n margin: 0.4em 0;\n font-size: 0.8em;\n padding: 0.4em 0;\n}\n\nhighlighted-code-block > pre {\n text-align: left;\n outline: none;\n}\n\nhighlighted-code-block > select {\n display: inline-block;\n float: right;\n}\n\nfigure[data-trix-mutable][data-trix-highlighted-code-block] pre {\n box-shadow: 0 0 0 1px highlight;\n}\n\nfigure[data-trix-highlighted-code-block] {\n width: 100%;\n}\n\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--text-tools > button,\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--block-tools > button,\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--file-tools > button {\n pointer-events: none;\n}\n","",{version:3,sources:["webpack://app/frontend/action_text_syntax_highlighter/css/highlighted_code_block.css"],names:[],mappings:"AAAA;EACE,yBAAyB;EACzB,iBAAiB;AACnB;;AAEA;EACE,qBAAqB;EACrB,oBAAoB;AACtB;;AAEA;EACE,eAAe;EACf,gBAAgB;EAChB,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;EAChB,aAAa;AACf;;AAEA;EACE,qBAAqB;EACrB,YAAY;AACd;;AAEA;EACE,+BAA+B;AACjC;;AAEA;EACE,WAAW;AACb;;AAEA;;;EAGE,oBAAoB;AACtB",sourcesContent:["trix-editor pre[contenteditable] {\n -webkit-user-select: text;\n user-select: text;\n}\n\ntrix-editor pre[contenteditable]::selection {\n background: highlight;\n color: highlightText;\n}\n\ntrix-editor select[name='language'] {\n margin: 0.4em 0;\n font-size: 0.8em;\n padding: 0.4em 0;\n}\n\nhighlighted-code-block > pre {\n text-align: left;\n outline: none;\n}\n\nhighlighted-code-block > select {\n display: inline-block;\n float: right;\n}\n\nfigure[data-trix-mutable][data-trix-highlighted-code-block] pre {\n box-shadow: 0 0 0 1px highlight;\n}\n\nfigure[data-trix-highlighted-code-block] {\n width: 100%;\n}\n\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--text-tools > button,\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--block-tools > button,\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--file-tools > button {\n pointer-events: none;\n}\n"],sourceRoot:""}]);const r=i},268:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n=function(t,e){var n,o,i,r=t[1]||"",a=t[3];if(!a)return r;if(e&&"function"==typeof btoa){var s=(n=a,o=btoa(unescape(encodeURIComponent(JSON.stringify(n)))),i="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(o),"/*# ".concat(i," */")),l=a.sources.map((function(t){return"/*# sourceURL=".concat(a.sourceRoot||"").concat(t," */")}));return[r].concat(l).concat([s]).join("\n")}return[r].join("\n")}(e,t);return e[2]?"@media ".concat(e[2]," {").concat(n,"}"):n})).join("")},e.i=function(t,n,o){"string"==typeof t&&(t=[[null,t,""]]);var i={};if(o)for(var r=0;r<this.length;r++){var a=this[r][0];null!=a&&(i[a]=!0)}for(var s=0;s<t.length;s++){var l=[].concat(t[s]);o&&i[l[0]]||(n&&(l[2]?l[2]="".concat(n," and ").concat(l[2]):l[2]=n),e.push(l))}},e}},379:(t,e,n)=>{var o,i=function(){var t={};return function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(t){n=null}t[e]=n}return t[e]}}(),r=[];function a(t){for(var e=-1,n=0;n<r.length;n++)if(r[n].identifier===t){e=n;break}return e}function s(t,e){for(var n={},o=[],i=0;i<t.length;i++){var s=t[i],l=e.base?s[0]+e.base:s[0],c=n[l]||0,d="".concat(l," ").concat(c);n[l]=c+1;var h=a(d),u={css:s[1],media:s[2],sourceMap:s[3]};-1!==h?(r[h].references++,r[h].updater(u)):r.push({identifier:d,updater:p(u,e),references:1}),o.push(d)}return o}function l(t){var e=document.createElement("style"),o=t.attributes||{};if(void 0===o.nonce){var r=n.nc;r&&(o.nonce=r)}if(Object.keys(o).forEach((function(t){e.setAttribute(t,o[t])})),"function"==typeof t.insert)t.insert(e);else{var a=i(t.insert||"head");if(!a)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");a.appendChild(e)}return e}var c,d=(c=[],function(t,e){return c[t]=e,c.filter(Boolean).join("\n")});function h(t,e,n,o){var i=n?"":o.media?"@media ".concat(o.media," {").concat(o.css,"}"):o.css;if(t.styleSheet)t.styleSheet.cssText=d(e,i);else{var r=document.createTextNode(i),a=t.childNodes;a[e]&&t.removeChild(a[e]),a.length?t.insertBefore(r,a[e]):t.appendChild(r)}}function u(t,e,n){var o=n.css,i=n.media,r=n.sourceMap;if(i?t.setAttribute("media",i):t.removeAttribute("media"),r&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),t.styleSheet)t.styleSheet.cssText=o;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(o))}}var g=null,b=0;function p(t,e){var n,o,i;if(e.singleton){var r=b++;n=g||(g=l(e)),o=h.bind(null,n,r,!1),i=h.bind(null,n,r,!0)}else n=l(e),o=u.bind(null,n,e),i=function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(n)};return o(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;o(t=e)}else i()}}t.exports=function(t,e){(e=e||{}).singleton||"boolean"==typeof e.singleton||(e.singleton=(void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o));var n=s(t=t||[],e);return function(t){if(t=t||[],"[object Array]"===Object.prototype.toString.call(t)){for(var o=0;o<n.length;o++){var i=a(n[o]);r[i].references--}for(var l=s(t,e),c=0;c<n.length;c++){var d=a(n[c]);0===r[d].references&&(r[d].updater(),r.splice(d,1))}n=l}}}}},e={};function n(o){var i=e[o];if(void 0!==i)return i.exports;var r=e[o]={id:o,exports:{}};return t[o](r,r.exports,n),r.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{class t extends HTMLElement{connectedCallback(){var t;(null==(t=this.closest("trix-editor"))?void 0:t.editor)&&this.setup()}setup(){this.editor=this.closest("trix-editor").editor,this.closest("figure").dataset.trixHighlightedCodeBlock=!0,this.trixId=parseInt(this.parentElement.dataset.trixId),this.attachment=this.editor.getDocument().getAttachmentById(this.trixId),this.attachment.type="HighlightedCodeBlock",this.configureEventListeners()}save(){let t=this.attachment.getAttributes().sgid;this.editor.syntaxHighlighter.saveCodeBlock(this.toJSON(),t),this.render()}render(){this.attachment.setAttributes({content:this.outerHTML})}languageChanged(t){this.selectLanguage(t.target.value),this.save()}selectLanguage(t){this.languageSelector.querySelector("option[selected]").removeAttribute("selected"),this.languageSelector.querySelector(`option[value='${t}']`).setAttribute("selected","")}configureEventListeners(){this.codeBlock.addEventListener("blur",this.save.bind(this)),this.languageSelector.addEventListener("change",this.languageChanged.bind(this))}toJSON(){return JSON.stringify({highlighted_code_block:{content:this.codeBlock.innerHTML,language:this.languageSelector.value}})}get codeBlock(){return this.querySelector("pre")}get languageSelector(){return this.querySelector("select")}}function e(){return{"Content-Type":"application/json",Accept:"application/json","X-CSRF-Token":document.head.querySelector("meta[name='csrf-token']").content}}class o{constructor(t){this.element=t,this.editor=t.editor,this.setup()}addCodeBlock(t){fetch(c.basePath,{method:"POST",headers:e()}).then((t=>t.json())).then((t=>this.insertCodeBlock(t)))}saveCodeBlock(t,n){fetch(`${c.basePath}/${n}`,{method:"PATCH",headers:e(),body:t})}insertCodeBlock(t){let e=new Trix.Attachment(t);this.editor.insertAttachment(e),this.editor.composition.editAttachment(e)}setup(){var t;this.editorToolbar.setAttribute("data-highlights-code-blocks",""),this.editor.syntaxHighlighter=this,this.interceptToolbarCodeButton(),this.watchCursor(),null==(t=this.element.querySelector("highlighted-code-block"))||t.setup()}interceptToolbarCodeButton(){this.editorToolbarCodeButton.addEventListener("mousedown",(t=>{this.addCodeBlock(),t.preventDefault(),t.stopImmediatePropagation()}))}watchCursor(){this.element.addEventListener("trix-selection-change",(t=>{this.updateToolbarState(this.editor.getPosition())}))}updateToolbarState(t){var e;"HighlightedCodeBlock"==(null==(e=this.editor.getDocument().getPieceAtPosition(t).attachment)?void 0:e.type)?(this.editorToolbarCodeButton.classList.add("trix-active"),this.editorToolbar.dataset.disableStylingInteraction=!0):(this.editorToolbarCodeButton.classList.remove("trix-active"),this.editorToolbar.removeAttribute("data-disable-styling-interaction"))}get editorToolbar(){return this.element.toolbarElement}get editorToolbarCodeButton(){return this.editorToolbar.querySelector("[data-trix-attribute='code']")}}var i=n(379),r=n.n(i),a=n(990);r()(a.Z,{insert:"head",singleton:!1}),a.Z.locals;var s,l=Object.defineProperty;class c{static start(){customElements.define("highlighted-code-block",t),document.addEventListener("trix-initialize",(t=>{t.target.dataset.highlightsCodeBlocks&&new o(t.target)}))}}((t,e,n)=>{e in t?l(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n})(c,"symbol"!=typeof(s="basePath")?s+"":s,"/rails/action_text/highlighted_code_blocks"),c.start()})()})();
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/css/highlighted_code_block.css","webpack://@ayushn21/actiontext-syntax-highlighter/./node_modules/css-loader/dist/runtime/api.js","webpack://@ayushn21/actiontext-syntax-highlighter/./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js","webpack://@ayushn21/actiontext-syntax-highlighter/webpack/bootstrap","webpack://@ayushn21/actiontext-syntax-highlighter/webpack/runtime/compat get default export","webpack://@ayushn21/actiontext-syntax-highlighter/webpack/runtime/define property getters","webpack://@ayushn21/actiontext-syntax-highlighter/webpack/runtime/hasOwnProperty shorthand","webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/javascript/highlighted_code_block.js","webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/javascript/helpers.js","webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/javascript/trix_code_block_highlighter.js","webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/css/highlighted_code_block.css?925a","webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/javascript/actiontext_syntax_highlighter.js","webpack://@ayushn21/actiontext-syntax-highlighter/./app/frontend/action_text_syntax_highlighter/index.js"],"names":["___CSS_LOADER_EXPORT___","push","module","id","exports","useSourceMap","list","toString","this","map","item","content","sourceMap","base64","data","cssMapping","btoa","sourceMapping","unescape","encodeURIComponent","JSON","stringify","concat","sourceURLs","sources","source","sourceRoot","join","cssWithMappingToString","i","modules","mediaQuery","dedupe","alreadyImportedModules","length","_i","memo","getTarget","target","styleTarget","document","querySelector","window","HTMLIFrameElement","contentDocument","head","e","stylesInDom","getIndexByIdentifier","identifier","result","modulesToDom","options","idCountMap","identifiers","base","count","index","obj","css","media","references","updater","addStyle","insertStyleElement","style","createElement","attributes","nonce","Object","keys","forEach","key","setAttribute","insert","Error","appendChild","textStore","replaceText","replacement","filter","Boolean","applyToSingletonTag","remove","styleSheet","cssText","cssNode","createTextNode","childNodes","removeChild","insertBefore","applyToTag","removeAttribute","firstChild","singleton","singletonCounter","update","styleIndex","bind","parentNode","removeStyleElement","newObj","all","atob","lastIdentifiers","newList","prototype","call","newLastIdentifiers","_index","splice","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","prop","hasOwnProperty","HighlightedCodeBlock","HTMLElement","closest","editor","setup","dataset","trixHighlightedCodeBlock","trixId","parseInt","parentElement","attachment","getDocument","getAttachmentById","type","configureEventListeners","sgid","getAttributes","syntaxHighlighter","saveCodeBlock","toJSON","render","setAttributes","outerHTML","event","selectLanguage","value","save","language","languageSelector","codeBlock","addEventListener","languageChanged","highlighted_code_block","innerHTML","getDefaultHeaders","TrixCodeBlockHighlighter","element","fetch","ActionTextSyntaxHighlighter","basePath","method","headers","then","response","json","insertCodeBlock","block","body","Trix","Attachment","insertAttachment","composition","editAttachment","editorToolbar","interceptToolbarCodeButton","watchCursor","editorToolbarCodeButton","addCodeBlock","preventDefault","stopImmediatePropagation","updateToolbarState","getPosition","position","getPieceAtPosition","classList","add","disableStylingInteraction","toolbarElement","customElements","define","highlightsCodeBlocks","start"],"mappings":"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","file":"index.js","sourcesContent":["// Imports\nimport ___CSS_LOADER_API_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(true);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"trix-editor pre[contenteditable] {\\n -webkit-user-select: text;\\n user-select: text;\\n}\\n\\ntrix-editor pre[contenteditable]::selection {\\n background: highlight;\\n color: highlightText;\\n}\\n\\ntrix-editor select[name='language'] {\\n margin: 0.4em 0;\\n font-size: 0.8em;\\n padding: 0.4em 0;\\n}\\n\\nhighlighted-code-block > pre {\\n text-align: left;\\n outline: none;\\n}\\n\\nhighlighted-code-block > select {\\n display: inline-block;\\n float: right;\\n}\\n\\nfigure[data-trix-mutable][data-trix-highlighted-code-block] pre {\\n box-shadow: 0 0 0 1px highlight;\\n}\\n\\nfigure[data-trix-highlighted-code-block] {\\n width: 100%;\\n}\\n\\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--text-tools > button,\\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--block-tools > button,\\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--file-tools > button {\\n pointer-events: none;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://app/frontend/action_text_syntax_highlighter/css/highlighted_code_block.css\"],\"names\":[],\"mappings\":\"AAAA;EACE,yBAAyB;EACzB,iBAAiB;AACnB;;AAEA;EACE,qBAAqB;EACrB,oBAAoB;AACtB;;AAEA;EACE,eAAe;EACf,gBAAgB;EAChB,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;EAChB,aAAa;AACf;;AAEA;EACE,qBAAqB;EACrB,YAAY;AACd;;AAEA;EACE,+BAA+B;AACjC;;AAEA;EACE,WAAW;AACb;;AAEA;;;EAGE,oBAAoB;AACtB\",\"sourcesContent\":[\"trix-editor pre[contenteditable] {\\n -webkit-user-select: text;\\n user-select: text;\\n}\\n\\ntrix-editor pre[contenteditable]::selection {\\n background: highlight;\\n color: highlightText;\\n}\\n\\ntrix-editor select[name='language'] {\\n margin: 0.4em 0;\\n font-size: 0.8em;\\n padding: 0.4em 0;\\n}\\n\\nhighlighted-code-block > pre {\\n text-align: left;\\n outline: none;\\n}\\n\\nhighlighted-code-block > select {\\n display: inline-block;\\n float: right;\\n}\\n\\nfigure[data-trix-mutable][data-trix-highlighted-code-block] pre {\\n box-shadow: 0 0 0 1px highlight;\\n}\\n\\nfigure[data-trix-highlighted-code-block] {\\n width: 100%;\\n}\\n\\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--text-tools > button,\\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--block-tools > button,\\ntrix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--file-tools > button {\\n pointer-events: none;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","\"use strict\";\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\n// css base code, injected by the css-loader\n// eslint-disable-next-line func-names\nmodule.exports = function (useSourceMap) {\n var list = []; // return the list of modules as css string\n\n list.toString = function toString() {\n return this.map(function (item) {\n var content = cssWithMappingToString(item, useSourceMap);\n\n if (item[2]) {\n return \"@media \".concat(item[2], \" {\").concat(content, \"}\");\n }\n\n return content;\n }).join('');\n }; // import a list of modules into the list\n // eslint-disable-next-line func-names\n\n\n list.i = function (modules, mediaQuery, dedupe) {\n if (typeof modules === 'string') {\n // eslint-disable-next-line no-param-reassign\n modules = [[null, modules, '']];\n }\n\n var alreadyImportedModules = {};\n\n if (dedupe) {\n for (var i = 0; i < this.length; i++) {\n // eslint-disable-next-line prefer-destructuring\n var id = this[i][0];\n\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n\n for (var _i = 0; _i < modules.length; _i++) {\n var item = [].concat(modules[_i]);\n\n if (dedupe && alreadyImportedModules[item[0]]) {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (mediaQuery) {\n if (!item[2]) {\n item[2] = mediaQuery;\n } else {\n item[2] = \"\".concat(mediaQuery, \" and \").concat(item[2]);\n }\n }\n\n list.push(item);\n }\n };\n\n return list;\n};\n\nfunction cssWithMappingToString(item, useSourceMap) {\n var content = item[1] || ''; // eslint-disable-next-line prefer-destructuring\n\n var cssMapping = item[3];\n\n if (!cssMapping) {\n return content;\n }\n\n if (useSourceMap && typeof btoa === 'function') {\n var sourceMapping = toComment(cssMapping);\n var sourceURLs = cssMapping.sources.map(function (source) {\n return \"/*# sourceURL=\".concat(cssMapping.sourceRoot || '').concat(source, \" */\");\n });\n return [content].concat(sourceURLs).concat([sourceMapping]).join('\\n');\n }\n\n return [content].join('\\n');\n} // Adapted from convert-source-map (MIT)\n\n\nfunction toComment(sourceMap) {\n // eslint-disable-next-line no-undef\n var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));\n var data = \"sourceMappingURL=data:application/json;charset=utf-8;base64,\".concat(base64);\n return \"/*# \".concat(data, \" */\");\n}","\"use strict\";\n\nvar isOldIE = function isOldIE() {\n var memo;\n return function memorize() {\n if (typeof memo === 'undefined') {\n // Test for IE <= 9 as proposed by Browserhacks\n // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805\n // Tests for existence of standard globals is to allow style-loader\n // to operate correctly into non-standard environments\n // @see https://github.com/webpack-contrib/style-loader/issues/177\n memo = Boolean(window && document && document.all && !window.atob);\n }\n\n return memo;\n };\n}();\n\nvar getTarget = function getTarget() {\n var memo = {};\n return function memorize(target) {\n if (typeof memo[target] === 'undefined') {\n var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself\n\n if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n try {\n // This will throw an exception if access to iframe is blocked\n // due to cross-origin restrictions\n styleTarget = styleTarget.contentDocument.head;\n } catch (e) {\n // istanbul ignore next\n styleTarget = null;\n }\n }\n\n memo[target] = styleTarget;\n }\n\n return memo[target];\n };\n}();\n\nvar stylesInDom = [];\n\nfunction getIndexByIdentifier(identifier) {\n var result = -1;\n\n for (var i = 0; i < stylesInDom.length; i++) {\n if (stylesInDom[i].identifier === identifier) {\n result = i;\n break;\n }\n }\n\n return result;\n}\n\nfunction modulesToDom(list, options) {\n var idCountMap = {};\n var identifiers = [];\n\n for (var i = 0; i < list.length; i++) {\n var item = list[i];\n var id = options.base ? item[0] + options.base : item[0];\n var count = idCountMap[id] || 0;\n var identifier = \"\".concat(id, \" \").concat(count);\n idCountMap[id] = count + 1;\n var index = getIndexByIdentifier(identifier);\n var obj = {\n css: item[1],\n media: item[2],\n sourceMap: item[3]\n };\n\n if (index !== -1) {\n stylesInDom[index].references++;\n stylesInDom[index].updater(obj);\n } else {\n stylesInDom.push({\n identifier: identifier,\n updater: addStyle(obj, options),\n references: 1\n });\n }\n\n identifiers.push(identifier);\n }\n\n return identifiers;\n}\n\nfunction insertStyleElement(options) {\n var style = document.createElement('style');\n var attributes = options.attributes || {};\n\n if (typeof attributes.nonce === 'undefined') {\n var nonce = typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;\n\n if (nonce) {\n attributes.nonce = nonce;\n }\n }\n\n Object.keys(attributes).forEach(function (key) {\n style.setAttribute(key, attributes[key]);\n });\n\n if (typeof options.insert === 'function') {\n options.insert(style);\n } else {\n var target = getTarget(options.insert || 'head');\n\n if (!target) {\n throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\n }\n\n target.appendChild(style);\n }\n\n return style;\n}\n\nfunction removeStyleElement(style) {\n // istanbul ignore if\n if (style.parentNode === null) {\n return false;\n }\n\n style.parentNode.removeChild(style);\n}\n/* istanbul ignore next */\n\n\nvar replaceText = function replaceText() {\n var textStore = [];\n return function replace(index, replacement) {\n textStore[index] = replacement;\n return textStore.filter(Boolean).join('\\n');\n };\n}();\n\nfunction applyToSingletonTag(style, index, remove, obj) {\n var css = remove ? '' : obj.media ? \"@media \".concat(obj.media, \" {\").concat(obj.css, \"}\") : obj.css; // For old IE\n\n /* istanbul ignore if */\n\n if (style.styleSheet) {\n style.styleSheet.cssText = replaceText(index, css);\n } else {\n var cssNode = document.createTextNode(css);\n var childNodes = style.childNodes;\n\n if (childNodes[index]) {\n style.removeChild(childNodes[index]);\n }\n\n if (childNodes.length) {\n style.insertBefore(cssNode, childNodes[index]);\n } else {\n style.appendChild(cssNode);\n }\n }\n}\n\nfunction applyToTag(style, options, obj) {\n var css = obj.css;\n var media = obj.media;\n var sourceMap = obj.sourceMap;\n\n if (media) {\n style.setAttribute('media', media);\n } else {\n style.removeAttribute('media');\n }\n\n if (sourceMap && typeof btoa !== 'undefined') {\n css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\n } // For old IE\n\n /* istanbul ignore if */\n\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n while (style.firstChild) {\n style.removeChild(style.firstChild);\n }\n\n style.appendChild(document.createTextNode(css));\n }\n}\n\nvar singleton = null;\nvar singletonCounter = 0;\n\nfunction addStyle(obj, options) {\n var style;\n var update;\n var remove;\n\n if (options.singleton) {\n var styleIndex = singletonCounter++;\n style = singleton || (singleton = insertStyleElement(options));\n update = applyToSingletonTag.bind(null, style, styleIndex, false);\n remove = applyToSingletonTag.bind(null, style, styleIndex, true);\n } else {\n style = insertStyleElement(options);\n update = applyToTag.bind(null, style, options);\n\n remove = function remove() {\n removeStyleElement(style);\n };\n }\n\n update(obj);\n return function updateStyle(newObj) {\n if (newObj) {\n if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {\n return;\n }\n\n update(obj = newObj);\n } else {\n remove();\n }\n };\n}\n\nmodule.exports = function (list, options) {\n options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style>\n // tags it will allow on a page\n\n if (!options.singleton && typeof options.singleton !== 'boolean') {\n options.singleton = isOldIE();\n }\n\n list = list || [];\n var lastIdentifiers = modulesToDom(list, options);\n return function update(newList) {\n newList = newList || [];\n\n if (Object.prototype.toString.call(newList) !== '[object Array]') {\n return;\n }\n\n for (var i = 0; i < lastIdentifiers.length; i++) {\n var identifier = lastIdentifiers[i];\n var index = getIndexByIdentifier(identifier);\n stylesInDom[index].references--;\n }\n\n var newLastIdentifiers = modulesToDom(newList, options);\n\n for (var _i = 0; _i < lastIdentifiers.length; _i++) {\n var _identifier = lastIdentifiers[_i];\n\n var _index = getIndexByIdentifier(_identifier);\n\n if (stylesInDom[_index].references === 0) {\n stylesInDom[_index].updater();\n\n stylesInDom.splice(_index, 1);\n }\n }\n\n lastIdentifiers = newLastIdentifiers;\n };\n};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","export class HighlightedCodeBlock extends HTMLElement {\n connectedCallback() {\n if (this.closest(\"trix-editor\")?.editor) {\n this.setup()\n }\n }\n\n setup() {\n this.editor = this.closest(\"trix-editor\").editor\n this.closest(\"figure\").dataset.trixHighlightedCodeBlock = true\n\n this.trixId = parseInt(this.parentElement.dataset.trixId)\n this.attachment = this.editor.getDocument().getAttachmentById(this.trixId)\n this.attachment.type = \"HighlightedCodeBlock\"\n\n this.configureEventListeners()\n }\n\n save() {\n let sgid = this.attachment.getAttributes().sgid\n this.editor.syntaxHighlighter.saveCodeBlock(this.toJSON(), sgid)\n this.render()\n }\n\n render() {\n this.attachment.setAttributes({\n \"content\": this.outerHTML\n })\n }\n\n languageChanged(event) {\n this.selectLanguage(event.target.value)\n this.save()\n }\n\n selectLanguage(language) {\n this.languageSelector\n .querySelector(\"option[selected]\")\n .removeAttribute(\"selected\")\n\n this.languageSelector\n .querySelector(`option[value='${language}']`)\n .setAttribute(\"selected\", \"\")\n }\n\n configureEventListeners() {\n this.codeBlock\n .addEventListener(\"blur\", this.save.bind(this))\n this.languageSelector\n .addEventListener(\"change\", this.languageChanged.bind(this))\n }\n\n toJSON() {\n return JSON.stringify({\n highlighted_code_block: {\n content: this.codeBlock.innerHTML,\n language: this.languageSelector.value\n }\n })\n }\n\n get codeBlock() {\n return this.querySelector(\"pre\")\n }\n\n get languageSelector() {\n return this.querySelector(\"select\")\n }\n}","export function getDefaultHeaders() {\n return {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n \"X-CSRF-Token\": getCsrfToken()\n }\n}\n\nexport function getCsrfToken() {\n return document.head.querySelector(\"meta[name='csrf-token']\").content\n}","import { ActionTextSyntaxHighlighter } from \"./actiontext_syntax_highlighter\"\nimport { getDefaultHeaders } from \"./helpers\"\n\nexport class TrixCodeBlockHighlighter {\n constructor(element) {\n this.element = element\n this.editor = element.editor\n\n this.setup()\n }\n\n addCodeBlock(event) {\n fetch(ActionTextSyntaxHighlighter.basePath, {\n method: \"POST\",\n headers: getDefaultHeaders()\n })\n .then(response => response.json())\n .then(result => this.insertCodeBlock(result))\n }\n\n saveCodeBlock(block, sgid) {\n fetch(`${ActionTextSyntaxHighlighter.basePath}/${sgid}`, {\n method: \"PATCH\",\n headers: getDefaultHeaders(),\n body: block\n })\n }\n\n insertCodeBlock(block) {\n let attachment = new Trix.Attachment(block)\n this.editor.insertAttachment(attachment)\n this.editor.composition.editAttachment(attachment)\n }\n\n // Private\n\n setup() {\n this.editorToolbar.setAttribute(\"data-highlights-code-blocks\", \"\")\n this.editor.syntaxHighlighter = this\n\n this.interceptToolbarCodeButton()\n this.watchCursor()\n\n // Setup the first highlighted code block which triggers a re-render\n // and sets them all up via the connectedCallback\n this.element.querySelector(\"highlighted-code-block\")?.setup()\n }\n\n interceptToolbarCodeButton() {\n this.editorToolbarCodeButton.addEventListener('mousedown', (event) => {\n this.addCodeBlock()\n event.preventDefault()\n event.stopImmediatePropagation()\n })\n }\n\n watchCursor() {\n this.element.addEventListener('trix-selection-change', event => {\n this.updateToolbarState(this.editor.getPosition())\n })\n }\n\n updateToolbarState(position) {\n let attachmentType = this.editor.getDocument().getPieceAtPosition(position).attachment?.type\n if (attachmentType == \"HighlightedCodeBlock\") {\n this.editorToolbarCodeButton.classList.add(\"trix-active\")\n this.editorToolbar.dataset.disableStylingInteraction = true\n } else {\n this.editorToolbarCodeButton.classList.remove(\"trix-active\")\n this.editorToolbar.removeAttribute(\"data-disable-styling-interaction\")\n }\n }\n\n get editorToolbar() {\n return this.element.toolbarElement\n }\n\n get editorToolbarCodeButton() {\n return this.editorToolbar.querySelector(\"[data-trix-attribute='code']\")\n }\n}","import api from \"!../../../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../node_modules/css-loader/dist/cjs.js!./highlighted_code_block.css\";\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import { HighlightedCodeBlock } from \"./highlighted_code_block\"\nimport { TrixCodeBlockHighlighter } from \"./trix_code_block_highlighter\"\nimport css from \"../css/highlighted_code_block.css\"\n\nexport class ActionTextSyntaxHighlighter {\n static basePath = \"/rails/action_text/highlighted_code_blocks\"\n\n static start() {\n customElements.define('highlighted-code-block', HighlightedCodeBlock)\n\n document.addEventListener(\"trix-initialize\", (event) => {\n if (event.target.dataset.highlightsCodeBlocks) {\n new TrixCodeBlockHighlighter(event.target)\n }\n })\n }\n}\n","import { ActionTextSyntaxHighlighter } from \"./javascript/actiontext_syntax_highlighter\"\n\nActionTextSyntaxHighlighter.start()"],"sourceRoot":""}
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionTextSyntaxHighlighter
4
+ class HighlightedCodeBlocksController < ApplicationController
5
+ def create
6
+ @code_block = HighlightedCodeBlock.create
7
+ render json: {
8
+ sgid: @code_block.attachable_sgid,
9
+ contentType: "text/html",
10
+ content: render_to_string(
11
+ partial: @code_block.to_trix_content_attachment_partial_path,
12
+ locals: { highlighted_code_block: @code_block },
13
+ formats: [:html]
14
+ )
15
+ }, status: :created
16
+ end
17
+
18
+ def update
19
+ @code_block = ActionText::Attachable.from_attachable_sgid params[:id]
20
+ @code_block.update(code_block_params)
21
+ head :no_content
22
+ rescue ActiveRecord::RecordNotFound
23
+ head :not_found
24
+ end
25
+
26
+ private
27
+ def code_block_params
28
+ params.require(:highlighted_code_block).permit(:content, :language)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ trix-editor pre[contenteditable] {
2
+ -webkit-user-select: text;
3
+ user-select: text;
4
+ }
5
+
6
+ trix-editor pre[contenteditable]::selection {
7
+ background: highlight;
8
+ color: highlightText;
9
+ }
10
+
11
+ trix-editor select[name='language'] {
12
+ margin: 0.4em 0;
13
+ font-size: 0.8em;
14
+ padding: 0.4em 0;
15
+ }
16
+
17
+ highlighted-code-block > pre {
18
+ text-align: left;
19
+ outline: none;
20
+ }
21
+
22
+ highlighted-code-block > select {
23
+ display: inline-block;
24
+ float: right;
25
+ }
26
+
27
+ figure[data-trix-mutable][data-trix-highlighted-code-block] pre {
28
+ box-shadow: 0 0 0 1px highlight;
29
+ }
30
+
31
+ figure[data-trix-highlighted-code-block] {
32
+ width: 100%;
33
+ }
34
+
35
+ trix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--text-tools > button,
36
+ trix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--block-tools > button,
37
+ trix-toolbar[data-highlights-code-blocks][data-disable-styling-interaction] .trix-button-group--file-tools > button {
38
+ pointer-events: none;
39
+ }
@@ -0,0 +1,3 @@
1
+ import { ActionTextSyntaxHighlighter } from "./javascript/actiontext_syntax_highlighter"
2
+
3
+ ActionTextSyntaxHighlighter.start()
@@ -0,0 +1,17 @@
1
+ import { HighlightedCodeBlock } from "./highlighted_code_block"
2
+ import { TrixCodeBlockHighlighter } from "./trix_code_block_highlighter"
3
+ import css from "../css/highlighted_code_block.css"
4
+
5
+ export class ActionTextSyntaxHighlighter {
6
+ static basePath = "/rails/action_text/highlighted_code_blocks"
7
+
8
+ static start() {
9
+ customElements.define('highlighted-code-block', HighlightedCodeBlock)
10
+
11
+ document.addEventListener("trix-initialize", (event) => {
12
+ if (event.target.dataset.highlightsCodeBlocks) {
13
+ new TrixCodeBlockHighlighter(event.target)
14
+ }
15
+ })
16
+ }
17
+ }
@@ -0,0 +1,11 @@
1
+ export function getDefaultHeaders() {
2
+ return {
3
+ "Content-Type": "application/json",
4
+ "Accept": "application/json",
5
+ "X-CSRF-Token": getCsrfToken()
6
+ }
7
+ }
8
+
9
+ export function getCsrfToken() {
10
+ return document.head.querySelector("meta[name='csrf-token']").content
11
+ }
@@ -0,0 +1,69 @@
1
+ export class HighlightedCodeBlock extends HTMLElement {
2
+ connectedCallback() {
3
+ if (this.closest("trix-editor")?.editor) {
4
+ this.setup()
5
+ }
6
+ }
7
+
8
+ setup() {
9
+ this.editor = this.closest("trix-editor").editor
10
+ this.closest("figure").dataset.trixHighlightedCodeBlock = true
11
+
12
+ this.trixId = parseInt(this.parentElement.dataset.trixId)
13
+ this.attachment = this.editor.getDocument().getAttachmentById(this.trixId)
14
+ this.attachment.type = "HighlightedCodeBlock"
15
+
16
+ this.configureEventListeners()
17
+ }
18
+
19
+ save() {
20
+ let sgid = this.attachment.getAttributes().sgid
21
+ this.editor.syntaxHighlighter.saveCodeBlock(this.toJSON(), sgid)
22
+ this.render()
23
+ }
24
+
25
+ render() {
26
+ this.attachment.setAttributes({
27
+ "content": this.outerHTML
28
+ })
29
+ }
30
+
31
+ languageChanged(event) {
32
+ this.selectLanguage(event.target.value)
33
+ this.save()
34
+ }
35
+
36
+ selectLanguage(language) {
37
+ this.languageSelector
38
+ .querySelector("option[selected]")
39
+ .removeAttribute("selected")
40
+
41
+ this.languageSelector
42
+ .querySelector(`option[value='${language}']`)
43
+ .setAttribute("selected", "")
44
+ }
45
+
46
+ configureEventListeners() {
47
+ this.codeBlock
48
+ .addEventListener("blur", this.save.bind(this))
49
+ this.languageSelector
50
+ .addEventListener("change", this.languageChanged.bind(this))
51
+ }
52
+
53
+ toJSON() {
54
+ return JSON.stringify({
55
+ highlighted_code_block: {
56
+ content: this.codeBlock.innerHTML,
57
+ language: this.languageSelector.value
58
+ }
59
+ })
60
+ }
61
+
62
+ get codeBlock() {
63
+ return this.querySelector("pre")
64
+ }
65
+
66
+ get languageSelector() {
67
+ return this.querySelector("select")
68
+ }
69
+ }
@@ -0,0 +1,81 @@
1
+ import { ActionTextSyntaxHighlighter } from "./actiontext_syntax_highlighter"
2
+ import { getDefaultHeaders } from "./helpers"
3
+
4
+ export class TrixCodeBlockHighlighter {
5
+ constructor(element) {
6
+ this.element = element
7
+ this.editor = element.editor
8
+
9
+ this.setup()
10
+ }
11
+
12
+ addCodeBlock(event) {
13
+ fetch(ActionTextSyntaxHighlighter.basePath, {
14
+ method: "POST",
15
+ headers: getDefaultHeaders()
16
+ })
17
+ .then(response => response.json())
18
+ .then(result => this.insertCodeBlock(result))
19
+ }
20
+
21
+ saveCodeBlock(block, sgid) {
22
+ fetch(`${ActionTextSyntaxHighlighter.basePath}/${sgid}`, {
23
+ method: "PATCH",
24
+ headers: getDefaultHeaders(),
25
+ body: block
26
+ })
27
+ }
28
+
29
+ insertCodeBlock(block) {
30
+ let attachment = new Trix.Attachment(block)
31
+ this.editor.insertAttachment(attachment)
32
+ this.editor.composition.editAttachment(attachment)
33
+ }
34
+
35
+ // Private
36
+
37
+ setup() {
38
+ this.editorToolbar.setAttribute("data-highlights-code-blocks", "")
39
+ this.editor.syntaxHighlighter = this
40
+
41
+ this.interceptToolbarCodeButton()
42
+ this.watchCursor()
43
+
44
+ // Setup the first highlighted code block which triggers a re-render
45
+ // and sets them all up via the connectedCallback
46
+ this.element.querySelector("highlighted-code-block")?.setup()
47
+ }
48
+
49
+ interceptToolbarCodeButton() {
50
+ this.editorToolbarCodeButton.addEventListener('mousedown', (event) => {
51
+ this.addCodeBlock()
52
+ event.preventDefault()
53
+ event.stopImmediatePropagation()
54
+ })
55
+ }
56
+
57
+ watchCursor() {
58
+ this.element.addEventListener('trix-selection-change', event => {
59
+ this.updateToolbarState(this.editor.getPosition())
60
+ })
61
+ }
62
+
63
+ updateToolbarState(position) {
64
+ let attachmentType = this.editor.getDocument().getPieceAtPosition(position).attachment?.type
65
+ if (attachmentType == "HighlightedCodeBlock") {
66
+ this.editorToolbarCodeButton.classList.add("trix-active")
67
+ this.editorToolbar.dataset.disableStylingInteraction = true
68
+ } else {
69
+ this.editorToolbarCodeButton.classList.remove("trix-active")
70
+ this.editorToolbar.removeAttribute("data-disable-styling-interaction")
71
+ }
72
+ }
73
+
74
+ get editorToolbar() {
75
+ return this.element.toolbarElement
76
+ }
77
+
78
+ get editorToolbarCodeButton() {
79
+ return this.editorToolbar.querySelector("[data-trix-attribute='code']")
80
+ }
81
+ }
@@ -0,0 +1,11 @@
1
+ module ActionTextSyntaxHighlighter
2
+ class PurgeDeletedHighlightedCodeBlocksJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform
6
+ HighlightedCodeBlock.deleted.find_each do |code_block|
7
+ code_block.destroy
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActionTextSyntaxHighlighter::HighlightedCodeBlock < ApplicationRecord
4
+ include ActionText::Attachable, SupportedLanguages
5
+
6
+ self.table_name = "action_text_highlighted_code_blocks"
7
+
8
+ belongs_to :rich_text, class_name: "ActionText::RichText", optional: true
9
+
10
+ before_save :process_content
11
+
12
+ scope :deleted, -> { where(rich_text_id: nil).and(where(updated_at: ..1.day.ago)) }
13
+
14
+ def language
15
+ super || ""
16
+ end
17
+
18
+ def highlighted_content
19
+ return content unless lexer.present?
20
+
21
+ highlighter.format(lexer.lex(content)).html_safe
22
+ end
23
+
24
+ def content_type
25
+ "text/html"
26
+ end
27
+
28
+ def to_trix_content_attachment_partial_path
29
+ "action_text_syntax_highlighter/highlighted_code_blocks/editor"
30
+ end
31
+
32
+ private
33
+ def process_content
34
+ self.content&.gsub!(/<br>/, "\n")
35
+ end
36
+
37
+ def highlighter
38
+ @highlighter ||= Rouge::Formatters::HTML.new
39
+ end
40
+
41
+ def lexer
42
+ @lexer ||= begin
43
+ if language.present?
44
+ Rouge::Lexer.find(language.downcase).new
45
+ else
46
+ Rouge::Lexer.guesses(source: content).first&.new
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ module ActionTextSyntaxHighlighter::HighlightedCodeBlock::SupportedLanguages
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def supported_languages
6
+ @supported_languages ||= Rouge::Lexer.all.map do |lexer|
7
+ lexer.to_s.split("::").last
8
+ end.sort.unshift("")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ <highlighted-code-block>
2
+ <%= select_tag :language,
3
+ options_for_select(
4
+ ActionTextSyntaxHighlighter::HighlightedCodeBlock.supported_languages,
5
+ highlighted_code_block.language
6
+ ) %>
7
+ <pre contenteditable=""><%= highlighted_code_block.content %></pre>
8
+ </highlighted-code-block>
@@ -0,0 +1,4 @@
1
+ <% @highlighted_code_block_theme ||= ActionTextSyntaxHighlighter.default_theme %>
2
+ <%= tag.pre highlighted_code_block.highlighted_content, data: {
3
+ theme: @highlighted_code_block_theme
4
+ } %>
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ Rails.application.routes.draw do
2
+ scope ActionTextSyntaxHighlighter.routes_prefix do
3
+ post "/highlighted_code_blocks", to: "action_text_syntax_highlighter/highlighted_code_blocks#create"
4
+ patch "/highlighted_code_blocks/:id", to: "action_text_syntax_highlighter/highlighted_code_blocks#update", as: "highlighted_code_block"
5
+ end
6
+ end if ActionTextSyntaxHighlighter.draw_routes
@@ -0,0 +1,11 @@
1
+ class CreateActionTextHighlightedCodeBlocks < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :action_text_highlighted_code_blocks do |t|
4
+ t.text :content, size: :medium
5
+ t.string :language
6
+ t.references :rich_text, foreign_key: { to_table: :action_text_rich_texts, on_delete: :cascade }
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/rails"
5
+ require "nokogiri"
6
+ require "rouge"
7
+
8
+ module ActionTextSyntaxHighlighter
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :HasHighlightedCodeBlocks
12
+ autoload :PreTagsToHighlightedCodeBlocksConversion
13
+
14
+ mattr_accessor :draw_routes, default: true
15
+ mattr_accessor :routes_prefix, default: "/rails/action_text"
16
+ mattr_accessor :default_theme, default: :github
17
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_text_syntax_highlighter"
4
+
5
+ module ActionTextSyntaxHighlighter
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace ActionTextSyntaxHighlighter
8
+
9
+ config.action_text_syntax_highlighter = ActiveSupport::OrderedOptions.new
10
+
11
+ initializer "action_text_syntax_highlighter.allow_theme_attribute" do
12
+ Rails.application.config.to_prepare do
13
+ ActionText::ContentHelper.allowed_attributes << "data-theme"
14
+ end
15
+ end
16
+
17
+ initializer "action_text_syntax_highlighter.configs" do
18
+ config.after_initialize do |app|
19
+ ActionTextSyntaxHighlighter.draw_routes = app.config.action_text_syntax_highlighter.draw_routes != false
20
+ ActionTextSyntaxHighlighter.routes_prefix = app.config.action_text_syntax_highlighter.routes_prefix || "/rails/action_text"
21
+ ActionTextSyntaxHighlighter.default_theme = app.config.action_text_syntax_highlighter.default_theme || :github
22
+ end
23
+ end
24
+
25
+ initializer "action_text_syntax_highlighter.rich_text_includes" do
26
+ ActiveSupport.on_load(:action_text_rich_text) do
27
+ include ActionTextSyntaxHighlighter::HasHighlightedCodeBlocks
28
+ include ActionTextSyntaxHighlighter::PreTagsToHighlightedCodeBlocksConversion
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionTextSyntaxHighlighter
4
+ module HasHighlightedCodeBlocks
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :highlighted_code_blocks, class_name: "ActionTextSyntaxHighlighter::HighlightedCodeBlock"
9
+ before_save do
10
+ self.highlighted_code_blocks = body.attachables.grep(ActionTextSyntaxHighlighter::HighlightedCodeBlock).uniq if body.present?
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionTextSyntaxHighlighter
4
+ module PreTagsToHighlightedCodeBlocksConversion
5
+ def convert_pre_tags_to_highlighted_code_blocks
6
+ @document = Nokogiri::HTML::DocumentFragment.parse(body.to_html)
7
+ @document.css("pre").each do |node|
8
+ code_block_content = sanitizer.sanitize node.inner_html
9
+ code_block = ActionTextSyntaxHighlighter::HighlightedCodeBlock.create(content: code_block_content)
10
+ node.swap attachment_node_for(code_block)
11
+ end
12
+
13
+ update(body: @document.to_s)
14
+ end
15
+
16
+ private
17
+ def attachment_node_for(code_block)
18
+ attachment_node = Nokogiri::XML::Node.new(attachment_tag_name, @document)
19
+ attachment_node[:"content-type"] = "text/html"
20
+ attachment_node[:sgid] = code_block.attachable_sgid
21
+ attachment_node
22
+ end
23
+
24
+ def attachment_tag_name
25
+ ActionText::Attachment.try(:tag_name) || ActionText::Attachment::TAG_NAME
26
+ end
27
+
28
+ def sanitizer
29
+ @sanitizer ||= Rails::Html::FullSanitizer.new
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionTextSyntaxHighlighter
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :action_text_syntax_highlighter do
4
+ task :install do
5
+ Rake::Task["action_text_syntax_highlighter:install:migrations"].execute
6
+ puts "Install yarn package..."
7
+ # `yarn add @ayushn21/action_text_syntax_highlighter`
8
+
9
+ puts "All done! Just add the following lines to your JavaScript application:"
10
+ puts ""
11
+ puts <<~JAVASCRIPT
12
+ import { ActionTextSyntaxHighlighter } from "@ayushn21/actiontext-syntax-highlighter"
13
+ ActionTextSyntaxHighlighter.start()
14
+ JAVASCRIPT
15
+
16
+ puts ""
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: actiontext-syntax-highlighter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ayush Newatia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-01 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: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rouge
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - ayush@hey.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE.md
49
+ - README.md
50
+ - Rakefile
51
+ - app/assets/javascripts/action_text_syntax_highlighter.js
52
+ - app/assets/javascripts/action_text_syntax_highlighter.js.map
53
+ - app/controllers/action_text_syntax_highlighter/highlighted_code_blocks_controller.rb
54
+ - app/frontend/action_text_syntax_highlighter/css/highlighted_code_block.css
55
+ - app/frontend/action_text_syntax_highlighter/index.js
56
+ - app/frontend/action_text_syntax_highlighter/javascript/actiontext_syntax_highlighter.js
57
+ - app/frontend/action_text_syntax_highlighter/javascript/helpers.js
58
+ - app/frontend/action_text_syntax_highlighter/javascript/highlighted_code_block.js
59
+ - app/frontend/action_text_syntax_highlighter/javascript/trix_code_block_highlighter.js
60
+ - app/jobs/action_text_syntax_highlighter/purge_deleted_highlighted_code_blocks_job.rb
61
+ - app/models/action_text_syntax_highlighter/highlighted_code_block.rb
62
+ - app/models/action_text_syntax_highlighter/highlighted_code_block/supported_languages.rb
63
+ - app/views/action_text_syntax_highlighter/highlighted_code_blocks/_editor.html.erb
64
+ - app/views/action_text_syntax_highlighter/highlighted_code_blocks/_highlighted_code_block.html.erb
65
+ - config/routes.rb
66
+ - db/migrate/20210531195232_create_action_text_highlighted_code_blocks.rb
67
+ - lib/action_text_syntax_highlighter.rb
68
+ - lib/action_text_syntax_highlighter/engine.rb
69
+ - lib/action_text_syntax_highlighter/has_highlighted_code_blocks.rb
70
+ - lib/action_text_syntax_highlighter/pre_tags_to_highlighted_code_blocks_conversion.rb
71
+ - lib/action_text_syntax_highlighter/version.rb
72
+ - lib/tasks/action_text_syntax_highlighter_tasks.rake
73
+ homepage: https://github.com/ayushn21/actiontext-syntax-highlighter
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ homepage_uri: https://github.com/ayushn21/actiontext-syntax-highlighter
78
+ source_code_uri: https://github.com/ayushn21/actiontext-syntax-highlighter
79
+ changelog_uri: https://github.com/ayushn21/actiontext-syntax-highlighter/blob/main/CHANGELOG.md
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.1.4
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Extends ActionText to support highlighted code blocks
99
+ test_files: []