code.cljs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. (ns frontend.extensions.code
  2. (:require [rum.core :as rum]
  3. [frontend.util :as util]
  4. [goog.dom :as gdom]
  5. [goog.object :as gobj]
  6. [frontend.db :as db]
  7. [frontend.state :as state]
  8. [frontend.handler.editor :as editor-handler]
  9. [frontend.handler.file :as file-handler]
  10. [clojure.string :as string]
  11. [frontend.utf8 :as utf8]
  12. ["codemirror" :as cm]
  13. ["codemirror/addon/edit/matchbrackets"]
  14. ["codemirror/addon/edit/closebrackets"]
  15. ["codemirror/addon/selection/active-line"]
  16. ["codemirror/mode/clojure/clojure"]
  17. ["codemirror/mode/powershell/powershell"]
  18. ["codemirror/mode/javascript/javascript"]
  19. ["codemirror/mode/jsx/jsx"]
  20. ["codemirror/mode/clike/clike"]
  21. ["codemirror/mode/vue/vue"]
  22. ["codemirror/mode/commonlisp/commonlisp"]
  23. ["codemirror/mode/coffeescript/coffeescript"]
  24. ["codemirror/mode/css/css"]
  25. ["codemirror/mode/sass/sass"]
  26. ["codemirror/mode/dart/dart"]
  27. ["codemirror/mode/dockerfile/dockerfile"]
  28. ["codemirror/mode/elm/elm"]
  29. ["codemirror/mode/erlang/erlang"]
  30. ["codemirror/mode/go/go"]
  31. ["codemirror/mode/haskell/haskell"]
  32. ["codemirror/mode/lua/lua"]
  33. ["codemirror/mode/mathematica/mathematica"]
  34. ["codemirror/mode/perl/perl"]
  35. ["codemirror/mode/php/php"]
  36. ["codemirror/mode/python/python"]
  37. ["codemirror/mode/ruby/ruby"]
  38. ["codemirror/mode/rust/rust"]
  39. ["codemirror/mode/scheme/scheme"]
  40. ["codemirror/mode/shell/shell"]
  41. ["codemirror/mode/smalltalk/smalltalk"]
  42. ["codemirror/mode/sql/sql"]
  43. ["codemirror/mode/swift/swift"]
  44. ["codemirror/mode/xml/xml"]))
  45. ;; codemirror
  46. (def from-textarea (gobj/get cm "fromTextArea"))
  47. (defn- save-file-or-block-when-blur-or-esc!
  48. [editor textarea config state]
  49. (.save editor)
  50. (let [value (gobj/get textarea "value")
  51. default-value (gobj/get textarea "defaultValue")]
  52. (when (not= value default-value)
  53. (cond
  54. (:block/uuid config)
  55. (let [block (db/pull [:block/uuid (:block/uuid config)])
  56. format (:block/format block)
  57. content (:block/content block)
  58. full-content (:full_content (last (:rum/args state)))]
  59. (when (and full-content (string/includes? content full-content))
  60. (let [lines (string/split-lines full-content)
  61. fl (first lines)
  62. ll (last lines)]
  63. (when (and fl ll)
  64. (let [value' (str fl "\n" value "\n" ll)
  65. ;; FIXME: What if there're multiple code blocks with the same value?
  66. content' (string/replace-first content full-content value')]
  67. (editor-handler/save-block-if-changed! block content'))))))
  68. (:file-path config)
  69. (let [path (:file-path config)
  70. content (db/get-file-no-sub path)
  71. value (some-> (gdom/getElement path)
  72. (gobj/get "value"))]
  73. (when (and
  74. (not (string/blank? value))
  75. (not= (string/trim value) (string/trim content)))
  76. (file-handler/alter-file (state/get-current-repo) path (string/trim value)
  77. {:re-render-root? true})))
  78. :else
  79. nil))))
  80. (defn- text->cm-mode
  81. [text]
  82. (when text
  83. (let [mode (string/lower-case text)]
  84. (case mode
  85. "html" "text/html"
  86. "c" "text/x-csrc"
  87. "c++" "text/x-c++src"
  88. "java" "text/x-java"
  89. "c#" "text/x-csharp"
  90. "csharp" "text/x-csharp"
  91. "objective-c" "text/x-objectivec"
  92. "scala" "text/x-scala"
  93. "js" "text/javascript"
  94. "typescript" "text/typescript"
  95. "ts" "text/typescript"
  96. "tsx" "text/typescript-jsx"
  97. "scss" "text/x-scss"
  98. "less" "text/x-less"
  99. mode))))
  100. (defn render!
  101. [state]
  102. (let [editor-atom (:editor-atom state)
  103. esc-pressed? (atom nil)]
  104. (if @editor-atom
  105. (let [editor @editor-atom
  106. doc (.getDoc editor)
  107. code (nth (:rum/args state) 3)]
  108. (.setValue doc code))
  109. (let [[config id attr code] (:rum/args state)
  110. original-mode (get attr :data-lang)
  111. mode (or original-mode "javascript")
  112. clojure? (contains? #{"clojure" "clj" "text/x-clojure" "cljs" "cljc"} mode)
  113. mode (if clojure? "clojure" (text->cm-mode mode))
  114. lisp? (or clojure?
  115. (contains? #{"scheme" "racket" "lisp"} mode))
  116. textarea (gdom/getElement id)
  117. editor (or
  118. @(:editor-atom state)
  119. (when textarea
  120. (from-textarea textarea
  121. #js {:mode mode
  122. :matchBrackets lisp?
  123. :autoCloseBrackets true
  124. :lineNumbers true
  125. :styleActiveLine true
  126. :extraKeys #js {"Esc" (fn [cm]
  127. (save-file-or-block-when-blur-or-esc! cm textarea config state)
  128. (when-let [block-id (:block/uuid config)]
  129. (let [block (db/pull [:block/uuid block-id])
  130. value (.getValue cm)
  131. textarea-value (gobj/get textarea "value")]
  132. (editor-handler/edit-block! block :max (:block/format block) block-id)))
  133. ;; TODO: return "handled" or false doesn't always prevent event bubbles
  134. (reset! esc-pressed? true)
  135. (js/setTimeout #(reset! esc-pressed? false) 10))}})))]
  136. (when editor
  137. (let [element (.getWrapperElement editor)]
  138. (.on editor "blur" (fn [_cm e]
  139. (util/stop e)
  140. (state/set-block-component-editing-mode! false)
  141. (when-not @esc-pressed?
  142. (save-file-or-block-when-blur-or-esc! editor textarea config state))))
  143. (.addEventListener element "mousedown"
  144. (fn [e]
  145. (state/clear-selection!)
  146. (util/stop e)
  147. (state/set-block-component-editing-mode! true)))
  148. (.save editor)
  149. (.refresh editor)))
  150. editor))))
  151. (defn- load-and-render!
  152. [state]
  153. (let [editor-atom (:editor-atom state)
  154. editor (render! state)]
  155. (reset! editor-atom editor)))
  156. (rum/defcs editor < rum/reactive
  157. {:init (fn [state]
  158. (assoc state :editor-atom (atom nil)))
  159. :did-mount (fn [state]
  160. (load-and-render! state)
  161. state)
  162. :did-update (fn [state]
  163. (load-and-render! state)
  164. state)}
  165. [state config id attr code options]
  166. [:div.extensions__code
  167. [:div.extensions__code-lang
  168. (let [mode (string/lower-case (get attr :data-lang "javascript"))]
  169. (if (= mode "text/x-clojure")
  170. "clojure"
  171. mode))]
  172. [:textarea (merge {:id id
  173. :default-value code} attr)]])