code.cljs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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. pos-meta (:pos-meta state)]
  53. (when (not= value default-value)
  54. (cond
  55. (:block/uuid config)
  56. (let [block (db/pull [:block/uuid (:block/uuid config)])
  57. format (:block/format block)
  58. content (:block/content block)
  59. {:keys [start_pos end_pos]} @pos-meta
  60. prev-content (utf8/substring (utf8/encode content)
  61. 0 start_pos)
  62. value (str (if (not= "\n" (last prev-content))
  63. "\n")
  64. (string/trimr value)
  65. "\n")
  66. content' (utf8/insert! content start_pos end_pos value)]
  67. (editor-handler/save-block-if-changed! block content')
  68. (let [new-pos-meta {:start_pos start_pos
  69. :end_pos (+ start_pos
  70. (utf8/length (utf8/encode value)))}
  71. old-pos-meta @pos-meta]
  72. (reset! pos-meta new-pos-meta)))
  73. (:file-path config)
  74. (let [path (:file-path config)
  75. content (db/get-file-no-sub path)
  76. value (some-> (gdom/getElement path)
  77. (gobj/get "value"))]
  78. (when (and
  79. (not (string/blank? value))
  80. (not= (string/trim value) (string/trim content)))
  81. (file-handler/alter-file (state/get-current-repo) path (string/trim value)
  82. {:re-render-root? true})))
  83. :else
  84. nil))))
  85. (defn- text->cm-mode
  86. [text]
  87. (when text
  88. (let [mode (string/lower-case text)]
  89. (case mode
  90. "html" "text/html"
  91. "c" "text/x-csrc"
  92. "c++" "text/x-c++src"
  93. "java" "text/x-java"
  94. "c#" "text/x-csharp"
  95. "csharp" "text/x-csharp"
  96. "objective-c" "text/x-objectivec"
  97. "scala" "text/x-scala"
  98. "js" "text/javascript"
  99. "typescript" "text/typescript"
  100. "ts" "text/typescript"
  101. "tsx" "text/typescript-jsx"
  102. "scss" "text/x-scss"
  103. "less" "text/x-less"
  104. mode))))
  105. (defn render!
  106. [state]
  107. (let [editor-atom (:editor-atom state)
  108. esc-pressed? (atom nil)]
  109. (if @editor-atom
  110. @editor-atom
  111. (let [[config id attr code] (:rum/args state)
  112. original-mode (get attr :data-lang)
  113. mode (or original-mode "javascript")
  114. clojure? (contains? #{"clojure" "clj" "text/x-clojure" "cljs" "cljc"} mode)
  115. mode (if clojure? "clojure" (text->cm-mode mode))
  116. lisp? (or clojure?
  117. (contains? #{"scheme" "racket" "lisp"} mode))
  118. textarea (gdom/getElement id)
  119. editor (or
  120. @(:editor-atom state)
  121. (when textarea
  122. (from-textarea textarea
  123. #js {:mode mode
  124. :matchBrackets lisp?
  125. :autoCloseBrackets true
  126. :lineNumbers true
  127. :styleActiveLine true
  128. :extraKeys #js {"Esc" (fn [cm]
  129. (let [save! #(save-file-or-block-when-blur-or-esc! cm textarea config state)]
  130. (if-let [block-id (:block/uuid config)]
  131. (let [block (db/pull [:block/uuid block-id])
  132. value (.getValue cm)
  133. textarea-value (gobj/get textarea "value")
  134. changed? (not= value textarea-value)]
  135. (if changed?
  136. (save!)
  137. (editor-handler/edit-block! block :max (:block/format block) block-id)))
  138. (save!)))
  139. ;; TODO: return "handled" or false doesn't always prevent event bubbles
  140. (reset! esc-pressed? true)
  141. (js/setTimeout #(reset! esc-pressed? false) 10))}})))]
  142. (when editor
  143. (let [element (.getWrapperElement editor)]
  144. (.on editor "blur" (fn [_cm e]
  145. (util/stop e)
  146. (when-not @esc-pressed?
  147. (save-file-or-block-when-blur-or-esc! editor textarea config state))))
  148. (.addEventListener element "click"
  149. (fn [e]
  150. (util/stop e)))
  151. (.save editor)
  152. (.refresh editor)))
  153. editor))))
  154. (defn- load-and-render!
  155. [state]
  156. (let [editor-atom (:editor-atom state)]
  157. (let [editor (render! state)]
  158. (reset! editor-atom editor))))
  159. (rum/defcs editor < rum/reactive
  160. {:init (fn [state]
  161. (assoc state
  162. :pos-meta (atom (last (:rum/args state)))
  163. :editor-atom (atom nil)))
  164. :did-mount (fn [state]
  165. (load-and-render! state)
  166. state)
  167. :did-remount (fn [old_state state]
  168. (load-and-render! state)
  169. state)}
  170. [state config id attr code pos-meta]
  171. [:div.extensions__code
  172. {:on-mouse-down (fn [e]
  173. (util/stop e)
  174. (state/set-block-component-editing-mode! true))
  175. :on-blur #(state/set-block-component-editing-mode! false)}
  176. [:div.extensions__code-lang
  177. (let [mode (string/lower-case (get attr :data-lang "javascript"))]
  178. (if (= mode "text/x-clojure")
  179. "clojure"
  180. mode))]
  181. [:textarea (merge {:id id
  182. :default-value code} attr)]])