code.cljs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. [medley.core :as medley]
  7. [frontend.db :as db]
  8. [frontend.state :as state]
  9. [frontend.handler.editor :as editor-handler]
  10. [frontend.handler.file :as file-handler]
  11. [clojure.string :as string]
  12. [frontend.utf8 :as utf8]
  13. ["codemirror" :as cm]
  14. ["codemirror/addon/edit/matchbrackets"]
  15. ["codemirror/addon/edit/closebrackets"]
  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/dart/dart"]
  26. ["codemirror/mode/dockerfile/dockerfile"]
  27. ["codemirror/mode/elm/elm"]
  28. ["codemirror/mode/erlang/erlang"]
  29. ["codemirror/mode/go/go"]
  30. ["codemirror/mode/haskell/haskell"]
  31. ["codemirror/mode/lua/lua"]
  32. ["codemirror/mode/mathematica/mathematica"]
  33. ["codemirror/mode/perl/perl"]
  34. ["codemirror/mode/php/php"]
  35. ["codemirror/mode/python/python"]
  36. ["codemirror/mode/ruby/ruby"]
  37. ["codemirror/mode/rust/rust"]
  38. ["codemirror/mode/scheme/scheme"]
  39. ["codemirror/mode/shell/shell"]
  40. ["codemirror/mode/smalltalk/smalltalk"]
  41. ["codemirror/mode/sql/sql"]
  42. ["codemirror/mode/swift/swift"]
  43. ["codemirror/mode/xml/xml"]))
  44. ;; codemirror
  45. (def from-textarea (gobj/get cm "fromTextArea"))
  46. (defn- save-file-or-block-when-blur-or-esc!
  47. [editor textarea config state]
  48. (.save editor)
  49. (let [value (gobj/get textarea "value")
  50. default-value (gobj/get textarea "defaultValue")]
  51. (cond
  52. (:block/uuid config)
  53. (let [block (db/pull [:block/uuid (:block/uuid config)])
  54. format (:block/format block)
  55. ;; Get newest state
  56. pos-meta (:pos-meta state)
  57. content (:block/content block)
  58. {:keys [start_pos end_pos]} @pos-meta
  59. prev-content (utf8/substring (utf8/encode content)
  60. 0 start_pos)
  61. value (str (if (not= "\n" (last prev-content))
  62. "\n")
  63. (string/trimr value)
  64. "\n")
  65. content' (utf8/insert! content
  66. start_pos
  67. end_pos
  68. value)]
  69. (reset! pos-meta {:start_pos start_pos
  70. :end_pos (+ start_pos
  71. (utf8/length (utf8/encode value)))})
  72. (editor-handler/save-block-if-changed! block content'))
  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. mode))))
  103. (defn render!
  104. [state]
  105. (let [editor-atom (:editor-atom state)]
  106. (if @editor-atom
  107. @editor-atom
  108. (let [[config id attr code pos_meta] (:rum/args state)
  109. original-mode (get attr :data-lang)
  110. mode (or original-mode "javascript")
  111. clojure? (contains? #{"clojure" "clj" "text/x-clojure" "cljs" "cljc"} mode)
  112. mode (if clojure? "clojure" (text->cm-mode mode))
  113. lisp? (or clojure?
  114. (contains? #{"scheme" "racket" "lisp"} mode))
  115. textarea (gdom/getElement id)
  116. editor (or
  117. @(:editor-atom state)
  118. (when textarea
  119. (from-textarea textarea
  120. #js {:mode mode
  121. :matchBrackets lisp?
  122. :autoCloseBrackets true
  123. :lineNumbers true
  124. :extraKeys #js {"Esc" (fn [cm]
  125. (let [save! #(save-file-or-block-when-blur-or-esc! cm textarea config state)]
  126. (if-let [block-id (:block/uuid config)]
  127. (let [block (db/pull [:block/uuid block-id])
  128. value (.getValue cm)
  129. textarea-value (gobj/get textarea "value")
  130. changed? (not= value textarea-value)]
  131. (if changed?
  132. (save!)
  133. (editor-handler/edit-block! block :max (:block/format block) block-id)))
  134. (save!))))}})))]
  135. (when editor
  136. (let [element (.getWrapperElement editor)]
  137. (.on editor "blur" (fn []
  138. (save-file-or-block-when-blur-or-esc! editor textarea config state)))
  139. (.addEventListener element "click"
  140. (fn [e]
  141. (util/stop e)))
  142. (.save editor)
  143. (.refresh editor)))
  144. editor))))
  145. (defn- load-and-render!
  146. [state]
  147. (let [editor-atom (:editor-atom state)]
  148. (let [editor (render! state)]
  149. (reset! editor-atom editor))))
  150. (rum/defcs editor < rum/reactive
  151. {:init (fn [state]
  152. (assoc state
  153. :pos-meta (atom (last (:rum/args state)))
  154. :editor-atom (atom nil)))
  155. :did-mount (fn [state]
  156. (load-and-render! state)
  157. state)
  158. :did-remount (fn [old_state state]
  159. (load-and-render! state)
  160. state)}
  161. [state config id attr code pos_meta]
  162. [:div.extensions__code
  163. [:div.extensions__code-lang
  164. (let [mode (string/lower-case (get attr :data-lang "javascript"))]
  165. (if (= mode "text/x-clojure")
  166. "clojure"
  167. mode))]
  168. [:textarea (merge {:id id
  169. :default-value code} attr)]])