FileEditor.coffee 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. class FileEditor extends Class
  2. constructor: (@inner_path) ->
  3. @need_update = true
  4. @on_loaded = new Promise()
  5. @is_loading = false
  6. @content = ""
  7. @node_cm = null
  8. @cm = null
  9. @error = null
  10. @is_loaded = false
  11. @is_modified = false
  12. @is_saving = false
  13. @mode = "Loading"
  14. update: ->
  15. is_required = Page.url_params.get("edit_mode") != "new"
  16. Page.cmd "fileGet", {inner_path: @inner_path, required: is_required}, (res) =>
  17. if res?.error
  18. @error = res.error
  19. @content = res.error
  20. @log "Error loading: #{@error}"
  21. else
  22. if res
  23. @content = res
  24. else
  25. @content = ""
  26. @mode = "Create"
  27. if not @content
  28. @cm.getDoc().clearHistory()
  29. @cm.setValue(@content)
  30. if not @error
  31. @is_loaded = true
  32. Page.projector.scheduleRender()
  33. isModified: =>
  34. return @content != @cm.getValue()
  35. storeCmNode: (node) =>
  36. @node_cm = node
  37. getMode: (inner_path) ->
  38. ext = inner_path.split(".").pop()
  39. types = {
  40. "py": "python",
  41. "json": "application/json",
  42. "js": "javascript",
  43. "coffee": "coffeescript",
  44. "html": "htmlmixed",
  45. "htm": "htmlmixed",
  46. "php": "htmlmixed",
  47. "rs": "rust",
  48. "css": "css",
  49. "md": "markdown",
  50. "xml": "xml",
  51. "svg": "xml"
  52. }
  53. return types[ext]
  54. foldJson: (from, to) =>
  55. @log "foldJson", from, to
  56. # Get open / close token
  57. startToken = '{'
  58. endToken = '}'
  59. prevLine = @cm.getLine(from.line)
  60. if prevLine.lastIndexOf('[') > prevLine.lastIndexOf('{')
  61. startToken = '['
  62. endToken = ']'
  63. # Get json content
  64. internal = @cm.getRange(from, to)
  65. toParse = startToken + internal + endToken
  66. #Get key count
  67. try
  68. parsed = JSON.parse(toParse)
  69. count = Object.keys(parsed).length
  70. catch e
  71. null
  72. return if count then "\u21A4#{count}\u21A6" else "\u2194"
  73. createCodeMirror: ->
  74. mode = @getMode(@inner_path)
  75. @log "Creating CodeMirror", @inner_path, mode
  76. options = {
  77. value: "Loading...",
  78. mode: mode,
  79. lineNumbers: true,
  80. styleActiveLine: true,
  81. matchBrackets: true,
  82. keyMap: "sublime",
  83. theme: "mdn-like",
  84. extraKeys: {"Ctrl-Space": "autocomplete"},
  85. foldGutter: true,
  86. gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
  87. }
  88. if mode == "application/json"
  89. options.gutters.unshift("CodeMirror-lint-markers")
  90. options.lint = true
  91. options.foldOptions = { widget: @foldJson }
  92. @cm = CodeMirror(@node_cm, options)
  93. @cm.on "changes", (changes) =>
  94. if @is_loaded and not @is_modified
  95. @is_modified = true
  96. Page.projector.scheduleRender()
  97. loadEditor: ->
  98. if not @is_loading
  99. document.getElementsByTagName("head")[0].insertAdjacentHTML(
  100. "beforeend",
  101. """<link rel="stylesheet" href="codemirror/all.css" />"""
  102. )
  103. script = document.createElement('script')
  104. script.src = "codemirror/all.js"
  105. script.onload = =>
  106. @createCodeMirror()
  107. @on_loaded.resolve()
  108. document.head.appendChild(script)
  109. return @on_loaded
  110. handleSidebarButtonClick: =>
  111. Page.is_sidebar_closed = not Page.is_sidebar_closed
  112. return false
  113. handleSaveClick: =>
  114. num_errors = (mark for mark in Page.file_editor.cm.getAllMarks() when mark.className == "CodeMirror-lint-mark-error").length
  115. if num_errors > 0
  116. Page.cmd "wrapperConfirm", ["<b>Warning:</b> The file looks invalid.", "Save anyway"], @save
  117. else
  118. @save()
  119. return false
  120. save: =>
  121. Page.projector.scheduleRender()
  122. @is_saving = true
  123. Page.cmd "fileWrite", [@inner_path, Text.fileEncode(@cm.getValue())], (res) =>
  124. @is_saving = false
  125. if res.error
  126. Page.cmd "wrapperNotification", ["error", "Error saving #{res.error}"]
  127. else
  128. @is_save_done = true
  129. setTimeout (() =>
  130. @is_save_done = false
  131. Page.projector.scheduleRender()
  132. ), 2000
  133. @content = @cm.getValue()
  134. @is_modified = false
  135. if @mode == "Create"
  136. @mode = "Edit"
  137. Page.file_list.need_update = true
  138. Page.projector.scheduleRender()
  139. render: ->
  140. if @need_update
  141. @loadEditor().then =>
  142. @update()
  143. @need_update = false
  144. h("div.editor", {afterCreate: @storeCmNode, classes: {error: @error, loaded: @is_loaded}}, [
  145. h("a.sidebar-button", {href: "#Sidebar", onclick: @handleSidebarButtonClick}, h("span", "\u2039")),
  146. h("div.editor-head", [
  147. if @mode in ["Edit", "Create"]
  148. h("a.save.button",
  149. {href: "#Save", classes: {loading: @is_saving, done: @is_save_done, disabled: not @is_modified}, onclick: @handleSaveClick},
  150. if @is_save_done then "Save: done!" else "Save"
  151. )
  152. h("span.title", @mode, ": ", @inner_path)
  153. ]),
  154. if @error
  155. h("div.error-message",
  156. h("h2", "Unable to load the file: #{@error}")
  157. h("a", {href: Page.file_list.getHref(@inner_path)}, "View in browser")
  158. )
  159. ])
  160. window.FileEditor = FileEditor