locomotive_cms 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (607) hide show
  1. data/Gemfile +45 -0
  2. data/README.textile +23 -0
  3. data/app/controllers/admin/accounts_controller.rb +24 -0
  4. data/app/controllers/admin/asset_collections_controller.rb +66 -0
  5. data/app/controllers/admin/assets_controller.rb +48 -0
  6. data/app/controllers/admin/base_controller.rb +55 -0
  7. data/app/controllers/admin/content_types_controller.rb +52 -0
  8. data/app/controllers/admin/contents_controller.rb +72 -0
  9. data/app/controllers/admin/current_sites_controller.rb +33 -0
  10. data/app/controllers/admin/layouts_controller.rb +56 -0
  11. data/app/controllers/admin/memberships_controller.rb +39 -0
  12. data/app/controllers/admin/my_accounts_controller.rb +21 -0
  13. data/app/controllers/admin/page_parts_controller.rb +12 -0
  14. data/app/controllers/admin/pages_controller.rb +70 -0
  15. data/app/controllers/admin/passwords_controller.rb +11 -0
  16. data/app/controllers/admin/rendering_controller.rb +15 -0
  17. data/app/controllers/admin/sessions_controller.rb +17 -0
  18. data/app/controllers/admin/sites_controller.rb +48 -0
  19. data/app/controllers/admin/snippets_controller.rb +56 -0
  20. data/app/controllers/admin/theme_assets_controller.rb +79 -0
  21. data/app/controllers/application_controller.rb +3 -0
  22. data/app/controllers/home_controller.rb +7 -0
  23. data/app/helpers/admin/accounts_helper.rb +8 -0
  24. data/app/helpers/admin/assets_helper.rb +15 -0
  25. data/app/helpers/admin/base_helper.rb +54 -0
  26. data/app/helpers/admin/custom_fields_helper.rb +24 -0
  27. data/app/helpers/admin/login_helper.rb +17 -0
  28. data/app/helpers/admin/pages_helper.rb +23 -0
  29. data/app/helpers/admin/sites_helper.rb +24 -0
  30. data/app/models/account.rb +46 -0
  31. data/app/models/asset.rb +38 -0
  32. data/app/models/asset_collection.rb +60 -0
  33. data/app/models/content_instance.rb +29 -0
  34. data/app/models/content_type.rb +63 -0
  35. data/app/models/extensions/asset/vignette.rb +40 -0
  36. data/app/models/extensions/page/parts.rb +54 -0
  37. data/app/models/extensions/page/render.rb +19 -0
  38. data/app/models/extensions/page/tree.rb +97 -0
  39. data/app/models/layout.rb +49 -0
  40. data/app/models/liquid_template.rb +32 -0
  41. data/app/models/membership.rb +36 -0
  42. data/app/models/page.rb +87 -0
  43. data/app/models/page_part.rb +38 -0
  44. data/app/models/site.rb +93 -0
  45. data/app/models/snippet.rb +3 -0
  46. data/app/models/theme_asset.rb +101 -0
  47. data/app/uploaders/asset_uploader.rb +68 -0
  48. data/app/uploaders/theme_asset_uploader.rb +42 -0
  49. data/app/views/admin/accounts/new.html.haml +22 -0
  50. data/app/views/admin/asset_collections/_asset.html.haml +7 -0
  51. data/app/views/admin/asset_collections/edit.html.haml +30 -0
  52. data/app/views/admin/asset_collections/index.html.haml +8 -0
  53. data/app/views/admin/asset_collections/new.html.haml +17 -0
  54. data/app/views/admin/assets/_form.html.haml +18 -0
  55. data/app/views/admin/assets/edit.html.haml +15 -0
  56. data/app/views/admin/assets/new.html.haml +12 -0
  57. data/app/views/admin/content_types/_form.html.haml +13 -0
  58. data/app/views/admin/content_types/edit.html.haml +16 -0
  59. data/app/views/admin/content_types/new.html.haml +15 -0
  60. data/app/views/admin/contents/_form.html.haml +10 -0
  61. data/app/views/admin/contents/edit.html.haml +16 -0
  62. data/app/views/admin/contents/index.html.haml +35 -0
  63. data/app/views/admin/contents/new.html.haml +15 -0
  64. data/app/views/admin/current_sites/_form.html.haml +45 -0
  65. data/app/views/admin/current_sites/edit.html.haml +15 -0
  66. data/app/views/admin/layouts/_form.html.haml +10 -0
  67. data/app/views/admin/layouts/_layout.html.haml +7 -0
  68. data/app/views/admin/layouts/edit.html.haml +18 -0
  69. data/app/views/admin/layouts/index.html.haml +15 -0
  70. data/app/views/admin/layouts/new.html.haml +15 -0
  71. data/app/views/admin/memberships/new.html.haml +15 -0
  72. data/app/views/admin/my_accounts/edit.html.haml +42 -0
  73. data/app/views/admin/pages/_form.html.haml +37 -0
  74. data/app/views/admin/pages/_page.html.haml +16 -0
  75. data/app/views/admin/pages/edit.html.haml +15 -0
  76. data/app/views/admin/pages/index.html.haml +18 -0
  77. data/app/views/admin/pages/new.html.haml +12 -0
  78. data/app/views/admin/passwords/edit.html.haml +18 -0
  79. data/app/views/admin/passwords/new.html.haml +17 -0
  80. data/app/views/admin/sessions/new.html.haml +15 -0
  81. data/app/views/admin/shared/_custom_fields.html.haml +47 -0
  82. data/app/views/admin/shared/_footer.html.haml +5 -0
  83. data/app/views/admin/shared/_form_actions.html.haml +15 -0
  84. data/app/views/admin/shared/_head.html.haml +18 -0
  85. data/app/views/admin/shared/_header.html.haml +8 -0
  86. data/app/views/admin/shared/_menu.html.haml +5 -0
  87. data/app/views/admin/shared/menu/_assets.html.haml +7 -0
  88. data/app/views/admin/shared/menu/_contents.html.haml +27 -0
  89. data/app/views/admin/shared/menu/_settings.html.haml +6 -0
  90. data/app/views/admin/sites/_form.html.haml +35 -0
  91. data/app/views/admin/sites/new.html.haml +12 -0
  92. data/app/views/admin/snippets/_form.html.haml +11 -0
  93. data/app/views/admin/snippets/_snippet.html.haml +7 -0
  94. data/app/views/admin/snippets/edit.html.haml +15 -0
  95. data/app/views/admin/snippets/index.html.haml +15 -0
  96. data/app/views/admin/snippets/new.html.haml +12 -0
  97. data/app/views/admin/theme_assets/_asset.html.haml +12 -0
  98. data/app/views/admin/theme_assets/_form.html.haml +39 -0
  99. data/app/views/admin/theme_assets/edit.html.haml +15 -0
  100. data/app/views/admin/theme_assets/images.html.haml +15 -0
  101. data/app/views/admin/theme_assets/index.html.haml +27 -0
  102. data/app/views/admin/theme_assets/new.html.haml +12 -0
  103. data/app/views/devise/confirmations/new.html.haml +12 -0
  104. data/app/views/devise/mailer/confirmation_instructions.html.haml +8 -0
  105. data/app/views/devise/mailer/reset_password_instructions.html.haml +12 -0
  106. data/app/views/devise/mailer/unlock_instructions.html.haml +10 -0
  107. data/app/views/devise/registrations/edit.html.haml +35 -0
  108. data/app/views/devise/registrations/new.html.haml +20 -0
  109. data/app/views/devise/sessions/new.html.haml +22 -0
  110. data/app/views/devise/shared/_links.haml +20 -0
  111. data/app/views/devise/unlocks/new.html.haml +12 -0
  112. data/app/views/home/show.html.haml +4 -0
  113. data/app/views/layouts/admin/application.html.haml +26 -0
  114. data/app/views/layouts/admin/login.html.haml +17 -0
  115. data/app/views/layouts/application.html.haml +7 -0
  116. data/config/application.rb +48 -0
  117. data/config/boot.rb +17 -0
  118. data/config/cucumber.yml +8 -0
  119. data/config/database.yml +20 -0
  120. data/config/environment.rb +5 -0
  121. data/config/environments/development.rb +35 -0
  122. data/config/environments/production.rb +33 -0
  123. data/config/environments/test.rb +33 -0
  124. data/config/initializers/backtrace_silencers.rb +7 -0
  125. data/config/initializers/carrierwave.rb +57 -0
  126. data/config/initializers/devise.rb +119 -0
  127. data/config/initializers/formtastic.rb +5 -0
  128. data/config/initializers/inflections.rb +10 -0
  129. data/config/initializers/locomotive.rb +7 -0
  130. data/config/initializers/mime_types.rb +5 -0
  131. data/config/initializers/mongoid.rb +61 -0
  132. data/config/initializers/rspec_generator.rb +8 -0
  133. data/config/locales/admin_ui_en.yml +211 -0
  134. data/config/locales/admin_ui_fr.yml +21 -0
  135. data/config/locales/carrierwave_en.yml +4 -0
  136. data/config/locales/carrierwave_fr.yml +4 -0
  137. data/config/locales/default_en.yml +29 -0
  138. data/config/locales/default_fr.yml +30 -0
  139. data/config/locales/devise.en.yml +36 -0
  140. data/config/routes.rb +57 -0
  141. data/lib/core_ext.rb +32 -0
  142. data/lib/generators/locomotive_generator.rb +14 -0
  143. data/lib/locomotive.rb +35 -0
  144. data/lib/locomotive/configuration.rb +67 -0
  145. data/lib/locomotive/devise/sessions_controller.rb +2 -0
  146. data/lib/locomotive/engine.rb +21 -0
  147. data/lib/locomotive/liquid.rb +5 -0
  148. data/lib/locomotive/liquid/db_file_system.rb +18 -0
  149. data/lib/locomotive/liquid/drops/asset_collections.rb +19 -0
  150. data/lib/locomotive/liquid/drops/base.rb +50 -0
  151. data/lib/locomotive/liquid/drops/content.rb +19 -0
  152. data/lib/locomotive/liquid/drops/contents.rb +63 -0
  153. data/lib/locomotive/liquid/drops/javascripts.rb +18 -0
  154. data/lib/locomotive/liquid/drops/stylesheets.rb +19 -0
  155. data/lib/locomotive/liquid/filters/date.rb +30 -0
  156. data/lib/locomotive/liquid/filters/html.rb +114 -0
  157. data/lib/locomotive/liquid/filters/misc.rb +30 -0
  158. data/lib/locomotive/liquid/liquify_template.rb +57 -0
  159. data/lib/locomotive/liquid/tags/blueprint.rb +21 -0
  160. data/lib/locomotive/liquid/tags/jquery.rb +17 -0
  161. data/lib/locomotive/liquid/tags/paginate.rb +100 -0
  162. data/lib/locomotive/liquid/tags/snippet.rb +42 -0
  163. data/lib/locomotive/liquid/tags/with_scope.rb +39 -0
  164. data/lib/locomotive/mongoid.rb +2 -0
  165. data/lib/locomotive/mongoid/document.rb +20 -0
  166. data/lib/locomotive/mongoid/model_extensions.rb +1 -0
  167. data/lib/locomotive/patches.rb +2 -0
  168. data/lib/locomotive/regexps.rb +13 -0
  169. data/lib/locomotive/render.rb +59 -0
  170. data/lib/locomotive/routing/default_constraint.rb +45 -0
  171. data/lib/locomotive/routing/site_dispatcher.rb +48 -0
  172. data/lib/misc_form_builder.rb +35 -0
  173. data/lib/tasks/cucumber.rake +53 -0
  174. data/lib/tasks/rspec.rake +69 -0
  175. data/public/images/admin/background/body.png +0 -0
  176. data/public/images/admin/background/footer.png +0 -0
  177. data/public/images/admin/background/light.png +0 -0
  178. data/public/images/admin/buttons/action-left.png +0 -0
  179. data/public/images/admin/buttons/action-right.png +0 -0
  180. data/public/images/admin/buttons/add-left.png +0 -0
  181. data/public/images/admin/buttons/add-right.png +0 -0
  182. data/public/images/admin/buttons/dark-gray-left.png +0 -0
  183. data/public/images/admin/buttons/dark-gray-right.png +0 -0
  184. data/public/images/admin/buttons/emboss-left.png +0 -0
  185. data/public/images/admin/buttons/emboss-right.png +0 -0
  186. data/public/images/admin/buttons/gray-left.png +0 -0
  187. data/public/images/admin/buttons/gray-right.png +0 -0
  188. data/public/images/admin/buttons/light-gray-left.png +0 -0
  189. data/public/images/admin/buttons/light-gray-right.png +0 -0
  190. data/public/images/admin/buttons/search.png +0 -0
  191. data/public/images/admin/buttons/transparent-left.png +0 -0
  192. data/public/images/admin/buttons/transparent-right.png +0 -0
  193. data/public/images/admin/form/big_item.png +0 -0
  194. data/public/images/admin/form/error-arrow.png +0 -0
  195. data/public/images/admin/form/field.png +0 -0
  196. data/public/images/admin/form/folded-arrow-off.png +0 -0
  197. data/public/images/admin/form/folded-arrow-on.png +0 -0
  198. data/public/images/admin/form/folded.png +0 -0
  199. data/public/images/admin/form/footer.png +0 -0
  200. data/public/images/admin/form/growl-error.png +0 -0
  201. data/public/images/admin/form/growl-notice.png +0 -0
  202. data/public/images/admin/form/header-first-on.png +0 -0
  203. data/public/images/admin/form/header-left-on.png +0 -0
  204. data/public/images/admin/form/header-on.png +0 -0
  205. data/public/images/admin/form/header-right-on.png +0 -0
  206. data/public/images/admin/form/header.png +0 -0
  207. data/public/images/admin/form/icons/drag.png +0 -0
  208. data/public/images/admin/form/icons/spinner.gif +0 -0
  209. data/public/images/admin/form/icons/trash.png +0 -0
  210. data/public/images/admin/form/item.png +0 -0
  211. data/public/images/admin/form/pen.png +0 -0
  212. data/public/images/admin/icons/filetype/medium/AC3.png +0 -0
  213. data/public/images/admin/icons/filetype/medium/ACE.png +0 -0
  214. data/public/images/admin/icons/filetype/medium/ADE.png +0 -0
  215. data/public/images/admin/icons/filetype/medium/ADP.png +0 -0
  216. data/public/images/admin/icons/filetype/medium/AI.png +0 -0
  217. data/public/images/admin/icons/filetype/medium/AIFF.png +0 -0
  218. data/public/images/admin/icons/filetype/medium/AU.png +0 -0
  219. data/public/images/admin/icons/filetype/medium/AVI.png +0 -0
  220. data/public/images/admin/icons/filetype/medium/BAT.png +0 -0
  221. data/public/images/admin/icons/filetype/medium/BIN.png +0 -0
  222. data/public/images/admin/icons/filetype/medium/BMP.png +0 -0
  223. data/public/images/admin/icons/filetype/medium/BUP.png +0 -0
  224. data/public/images/admin/icons/filetype/medium/CAB.png +0 -0
  225. data/public/images/admin/icons/filetype/medium/CAT.png +0 -0
  226. data/public/images/admin/icons/filetype/medium/CHM.png +0 -0
  227. data/public/images/admin/icons/filetype/medium/CSS.png +0 -0
  228. data/public/images/admin/icons/filetype/medium/CUE.png +0 -0
  229. data/public/images/admin/icons/filetype/medium/DAT.png +0 -0
  230. data/public/images/admin/icons/filetype/medium/DCR.png +0 -0
  231. data/public/images/admin/icons/filetype/medium/DER.png +0 -0
  232. data/public/images/admin/icons/filetype/medium/DIC.png +0 -0
  233. data/public/images/admin/icons/filetype/medium/DIVX.png +0 -0
  234. data/public/images/admin/icons/filetype/medium/DIZ.png +0 -0
  235. data/public/images/admin/icons/filetype/medium/DLL.png +0 -0
  236. data/public/images/admin/icons/filetype/medium/DOC.png +0 -0
  237. data/public/images/admin/icons/filetype/medium/DOCX.png +0 -0
  238. data/public/images/admin/icons/filetype/medium/DOS.png +0 -0
  239. data/public/images/admin/icons/filetype/medium/DVD.png +0 -0
  240. data/public/images/admin/icons/filetype/medium/DWG.png +0 -0
  241. data/public/images/admin/icons/filetype/medium/DWT.png +0 -0
  242. data/public/images/admin/icons/filetype/medium/EMF.png +0 -0
  243. data/public/images/admin/icons/filetype/medium/EXC.png +0 -0
  244. data/public/images/admin/icons/filetype/medium/FON.png +0 -0
  245. data/public/images/admin/icons/filetype/medium/GIF.png +0 -0
  246. data/public/images/admin/icons/filetype/medium/HLP.png +0 -0
  247. data/public/images/admin/icons/filetype/medium/HTML.png +0 -0
  248. data/public/images/admin/icons/filetype/medium/IFO.png +0 -0
  249. data/public/images/admin/icons/filetype/medium/INF.png +0 -0
  250. data/public/images/admin/icons/filetype/medium/INI.png +0 -0
  251. data/public/images/admin/icons/filetype/medium/INS.png +0 -0
  252. data/public/images/admin/icons/filetype/medium/IP.png +0 -0
  253. data/public/images/admin/icons/filetype/medium/ISO.png +0 -0
  254. data/public/images/admin/icons/filetype/medium/ISP.png +0 -0
  255. data/public/images/admin/icons/filetype/medium/JAVA.png +0 -0
  256. data/public/images/admin/icons/filetype/medium/JFIF.png +0 -0
  257. data/public/images/admin/icons/filetype/medium/JPEG.png +0 -0
  258. data/public/images/admin/icons/filetype/medium/JPG.png +0 -0
  259. data/public/images/admin/icons/filetype/medium/LOG.png +0 -0
  260. data/public/images/admin/icons/filetype/medium/M4A.png +0 -0
  261. data/public/images/admin/icons/filetype/medium/MID.png +0 -0
  262. data/public/images/admin/icons/filetype/medium/MMF.png +0 -0
  263. data/public/images/admin/icons/filetype/medium/MMM.png +0 -0
  264. data/public/images/admin/icons/filetype/medium/MOV.png +0 -0
  265. data/public/images/admin/icons/filetype/medium/MOVIE.png +0 -0
  266. data/public/images/admin/icons/filetype/medium/MP2.png +0 -0
  267. data/public/images/admin/icons/filetype/medium/MP2V.png +0 -0
  268. data/public/images/admin/icons/filetype/medium/MP3.png +0 -0
  269. data/public/images/admin/icons/filetype/medium/MP4.png +0 -0
  270. data/public/images/admin/icons/filetype/medium/MPE.png +0 -0
  271. data/public/images/admin/icons/filetype/medium/MPEG.png +0 -0
  272. data/public/images/admin/icons/filetype/medium/MPG.png +0 -0
  273. data/public/images/admin/icons/filetype/medium/MPV2.png +0 -0
  274. data/public/images/admin/icons/filetype/medium/NFO.png +0 -0
  275. data/public/images/admin/icons/filetype/medium/PDD.png +0 -0
  276. data/public/images/admin/icons/filetype/medium/PDF.png +0 -0
  277. data/public/images/admin/icons/filetype/medium/PHP.png +0 -0
  278. data/public/images/admin/icons/filetype/medium/PNG.png +0 -0
  279. data/public/images/admin/icons/filetype/medium/PPT.png +0 -0
  280. data/public/images/admin/icons/filetype/medium/PPTX.png +0 -0
  281. data/public/images/admin/icons/filetype/medium/PSD.png +0 -0
  282. data/public/images/admin/icons/filetype/medium/RAR.png +0 -0
  283. data/public/images/admin/icons/filetype/medium/REG.png +0 -0
  284. data/public/images/admin/icons/filetype/medium/RTF.png +0 -0
  285. data/public/images/admin/icons/filetype/medium/SCP.png +0 -0
  286. data/public/images/admin/icons/filetype/medium/THEME.png +0 -0
  287. data/public/images/admin/icons/filetype/medium/TIF.png +0 -0
  288. data/public/images/admin/icons/filetype/medium/TIFF.png +0 -0
  289. data/public/images/admin/icons/filetype/medium/TLB.png +0 -0
  290. data/public/images/admin/icons/filetype/medium/TTF.png +0 -0
  291. data/public/images/admin/icons/filetype/medium/TXT.png +0 -0
  292. data/public/images/admin/icons/filetype/medium/Thumbs.db +0 -0
  293. data/public/images/admin/icons/filetype/medium/UIS.png +0 -0
  294. data/public/images/admin/icons/filetype/medium/URL.png +0 -0
  295. data/public/images/admin/icons/filetype/medium/VBS.png +0 -0
  296. data/public/images/admin/icons/filetype/medium/VCR.png +0 -0
  297. data/public/images/admin/icons/filetype/medium/VOB.png +0 -0
  298. data/public/images/admin/icons/filetype/medium/WAV.png +0 -0
  299. data/public/images/admin/icons/filetype/medium/WBA.png +0 -0
  300. data/public/images/admin/icons/filetype/medium/WMA.png +0 -0
  301. data/public/images/admin/icons/filetype/medium/WMV.png +0 -0
  302. data/public/images/admin/icons/filetype/medium/WPL.png +0 -0
  303. data/public/images/admin/icons/filetype/medium/WRI.png +0 -0
  304. data/public/images/admin/icons/filetype/medium/WTX.png +0 -0
  305. data/public/images/admin/icons/filetype/medium/XLS.png +0 -0
  306. data/public/images/admin/icons/filetype/medium/XLSX.png +0 -0
  307. data/public/images/admin/icons/filetype/medium/XML.png +0 -0
  308. data/public/images/admin/icons/filetype/medium/XSL.png +0 -0
  309. data/public/images/admin/icons/filetype/medium/ZAP.png +0 -0
  310. data/public/images/admin/icons/filetype/medium/ZIP.png +0 -0
  311. data/public/images/admin/icons/filetype/medium/unknown.png +0 -0
  312. data/public/images/admin/icons/filetype/thumb/AC3.png +0 -0
  313. data/public/images/admin/icons/filetype/thumb/ACE.png +0 -0
  314. data/public/images/admin/icons/filetype/thumb/ADE.png +0 -0
  315. data/public/images/admin/icons/filetype/thumb/ADP.png +0 -0
  316. data/public/images/admin/icons/filetype/thumb/AI.png +0 -0
  317. data/public/images/admin/icons/filetype/thumb/AIFF.png +0 -0
  318. data/public/images/admin/icons/filetype/thumb/AU.png +0 -0
  319. data/public/images/admin/icons/filetype/thumb/AVI.png +0 -0
  320. data/public/images/admin/icons/filetype/thumb/BAT.png +0 -0
  321. data/public/images/admin/icons/filetype/thumb/BIN.png +0 -0
  322. data/public/images/admin/icons/filetype/thumb/BMP.png +0 -0
  323. data/public/images/admin/icons/filetype/thumb/BUP.png +0 -0
  324. data/public/images/admin/icons/filetype/thumb/CAB.png +0 -0
  325. data/public/images/admin/icons/filetype/thumb/CAT.png +0 -0
  326. data/public/images/admin/icons/filetype/thumb/CHM.png +0 -0
  327. data/public/images/admin/icons/filetype/thumb/CSS.png +0 -0
  328. data/public/images/admin/icons/filetype/thumb/CUE.png +0 -0
  329. data/public/images/admin/icons/filetype/thumb/DAT.png +0 -0
  330. data/public/images/admin/icons/filetype/thumb/DCR.png +0 -0
  331. data/public/images/admin/icons/filetype/thumb/DER.png +0 -0
  332. data/public/images/admin/icons/filetype/thumb/DIC.png +0 -0
  333. data/public/images/admin/icons/filetype/thumb/DIVX.png +0 -0
  334. data/public/images/admin/icons/filetype/thumb/DIZ.png +0 -0
  335. data/public/images/admin/icons/filetype/thumb/DLL.png +0 -0
  336. data/public/images/admin/icons/filetype/thumb/DOC.png +0 -0
  337. data/public/images/admin/icons/filetype/thumb/DOCX.png +0 -0
  338. data/public/images/admin/icons/filetype/thumb/DOS.png +0 -0
  339. data/public/images/admin/icons/filetype/thumb/DVD.png +0 -0
  340. data/public/images/admin/icons/filetype/thumb/DWG.png +0 -0
  341. data/public/images/admin/icons/filetype/thumb/DWT.png +0 -0
  342. data/public/images/admin/icons/filetype/thumb/EMF.png +0 -0
  343. data/public/images/admin/icons/filetype/thumb/EXC.png +0 -0
  344. data/public/images/admin/icons/filetype/thumb/FON.png +0 -0
  345. data/public/images/admin/icons/filetype/thumb/GIF.png +0 -0
  346. data/public/images/admin/icons/filetype/thumb/HLP.png +0 -0
  347. data/public/images/admin/icons/filetype/thumb/HTML.png +0 -0
  348. data/public/images/admin/icons/filetype/thumb/IFO.png +0 -0
  349. data/public/images/admin/icons/filetype/thumb/INF.png +0 -0
  350. data/public/images/admin/icons/filetype/thumb/INI.png +0 -0
  351. data/public/images/admin/icons/filetype/thumb/INS.png +0 -0
  352. data/public/images/admin/icons/filetype/thumb/IP.png +0 -0
  353. data/public/images/admin/icons/filetype/thumb/ISO.png +0 -0
  354. data/public/images/admin/icons/filetype/thumb/ISP.png +0 -0
  355. data/public/images/admin/icons/filetype/thumb/JAVA.png +0 -0
  356. data/public/images/admin/icons/filetype/thumb/JFIF.png +0 -0
  357. data/public/images/admin/icons/filetype/thumb/JPEG.png +0 -0
  358. data/public/images/admin/icons/filetype/thumb/JPG.png +0 -0
  359. data/public/images/admin/icons/filetype/thumb/LOG.png +0 -0
  360. data/public/images/admin/icons/filetype/thumb/M4A.png +0 -0
  361. data/public/images/admin/icons/filetype/thumb/MID.png +0 -0
  362. data/public/images/admin/icons/filetype/thumb/MMF.png +0 -0
  363. data/public/images/admin/icons/filetype/thumb/MMM.png +0 -0
  364. data/public/images/admin/icons/filetype/thumb/MOV.png +0 -0
  365. data/public/images/admin/icons/filetype/thumb/MOVIE.png +0 -0
  366. data/public/images/admin/icons/filetype/thumb/MP2.png +0 -0
  367. data/public/images/admin/icons/filetype/thumb/MP2V.png +0 -0
  368. data/public/images/admin/icons/filetype/thumb/MP3.png +0 -0
  369. data/public/images/admin/icons/filetype/thumb/MP4.png +0 -0
  370. data/public/images/admin/icons/filetype/thumb/MPE.png +0 -0
  371. data/public/images/admin/icons/filetype/thumb/MPEG.png +0 -0
  372. data/public/images/admin/icons/filetype/thumb/MPG.png +0 -0
  373. data/public/images/admin/icons/filetype/thumb/MPV2.png +0 -0
  374. data/public/images/admin/icons/filetype/thumb/NFO.png +0 -0
  375. data/public/images/admin/icons/filetype/thumb/PDD.png +0 -0
  376. data/public/images/admin/icons/filetype/thumb/PDF.png +0 -0
  377. data/public/images/admin/icons/filetype/thumb/PHP.png +0 -0
  378. data/public/images/admin/icons/filetype/thumb/PNG.png +0 -0
  379. data/public/images/admin/icons/filetype/thumb/PPT.png +0 -0
  380. data/public/images/admin/icons/filetype/thumb/PPTX.png +0 -0
  381. data/public/images/admin/icons/filetype/thumb/PSD.png +0 -0
  382. data/public/images/admin/icons/filetype/thumb/RAR.png +0 -0
  383. data/public/images/admin/icons/filetype/thumb/REG.png +0 -0
  384. data/public/images/admin/icons/filetype/thumb/RTF.png +0 -0
  385. data/public/images/admin/icons/filetype/thumb/SCP.png +0 -0
  386. data/public/images/admin/icons/filetype/thumb/THEME.png +0 -0
  387. data/public/images/admin/icons/filetype/thumb/TIF.png +0 -0
  388. data/public/images/admin/icons/filetype/thumb/TIFF.png +0 -0
  389. data/public/images/admin/icons/filetype/thumb/TLB.png +0 -0
  390. data/public/images/admin/icons/filetype/thumb/TTF.png +0 -0
  391. data/public/images/admin/icons/filetype/thumb/TXT.png +0 -0
  392. data/public/images/admin/icons/filetype/thumb/UIS.png +0 -0
  393. data/public/images/admin/icons/filetype/thumb/URL.png +0 -0
  394. data/public/images/admin/icons/filetype/thumb/VBS.png +0 -0
  395. data/public/images/admin/icons/filetype/thumb/VCR.png +0 -0
  396. data/public/images/admin/icons/filetype/thumb/VOB.png +0 -0
  397. data/public/images/admin/icons/filetype/thumb/WAV.png +0 -0
  398. data/public/images/admin/icons/filetype/thumb/WBA.png +0 -0
  399. data/public/images/admin/icons/filetype/thumb/WMA.png +0 -0
  400. data/public/images/admin/icons/filetype/thumb/WMV.png +0 -0
  401. data/public/images/admin/icons/filetype/thumb/WPL.png +0 -0
  402. data/public/images/admin/icons/filetype/thumb/WRI.png +0 -0
  403. data/public/images/admin/icons/filetype/thumb/WTX.png +0 -0
  404. data/public/images/admin/icons/filetype/thumb/XLS.png +0 -0
  405. data/public/images/admin/icons/filetype/thumb/XLSX.png +0 -0
  406. data/public/images/admin/icons/filetype/thumb/XML.png +0 -0
  407. data/public/images/admin/icons/filetype/thumb/XSL.png +0 -0
  408. data/public/images/admin/icons/filetype/thumb/ZAP.png +0 -0
  409. data/public/images/admin/icons/filetype/thumb/ZIP.png +0 -0
  410. data/public/images/admin/icons/filetype/thumb/unknown.png +0 -0
  411. data/public/images/admin/icons/flags/en.png +0 -0
  412. data/public/images/admin/icons/flags/fr.png +0 -0
  413. data/public/images/admin/list/empty.png +0 -0
  414. data/public/images/admin/list/icons/cross.png +0 -0
  415. data/public/images/admin/list/icons/drag.png +0 -0
  416. data/public/images/admin/list/icons/node_closed.png +0 -0
  417. data/public/images/admin/list/icons/node_open.png +0 -0
  418. data/public/images/admin/list/icons/trash.png +0 -0
  419. data/public/images/admin/list/item-left.png +0 -0
  420. data/public/images/admin/list/item-right.png +0 -0
  421. data/public/images/admin/list/item.png +0 -0
  422. data/public/images/admin/list/none.png +0 -0
  423. data/public/images/admin/list/thumb.png +0 -0
  424. data/public/images/admin/login/bottom_panel_bg.png +0 -0
  425. data/public/images/admin/login/buttons/left_bg.png +0 -0
  426. data/public/images/admin/login/buttons/right_bg.png +0 -0
  427. data/public/images/admin/login/content_panel_bg.png +0 -0
  428. data/public/images/admin/login/input_bg.png +0 -0
  429. data/public/images/admin/login/light_bg.png +0 -0
  430. data/public/images/admin/login/top_panel_bg.png +0 -0
  431. data/public/images/admin/login/wrapper_bg.png +0 -0
  432. data/public/images/admin/menu/arrow.png +0 -0
  433. data/public/images/admin/menu/blue-bg.png +0 -0
  434. data/public/images/admin/menu/blue-border.png +0 -0
  435. data/public/images/admin/menu/gray-bg.png +0 -0
  436. data/public/images/admin/menu/gray-border.png +0 -0
  437. data/public/images/admin/menu/green-bg.png +0 -0
  438. data/public/images/admin/menu/green-border.png +0 -0
  439. data/public/images/admin/menu/icons/add.png +0 -0
  440. data/public/images/admin/menu/icons/bullet.png +0 -0
  441. data/public/images/admin/menu/icons/folder.png +0 -0
  442. data/public/images/admin/menu/icons/settings.png +0 -0
  443. data/public/images/admin/menu/left.png +0 -0
  444. data/public/images/admin/menu/popup-body.png +0 -0
  445. data/public/images/admin/menu/popup-footer.png +0 -0
  446. data/public/images/admin/menu/popup-header.png +0 -0
  447. data/public/images/admin/menu/right.png +0 -0
  448. data/public/images/admin/menu/shadow.png +0 -0
  449. data/public/images/admin/menu/top-left.png +0 -0
  450. data/public/images/admin/menu/top.png +0 -0
  451. data/public/images/admin/nocoffee.gif +0 -0
  452. data/public/images/admin/plugins/fancybox/blank.gif +0 -0
  453. data/public/images/admin/plugins/fancybox/fancy_close.png +0 -0
  454. data/public/images/admin/plugins/fancybox/fancy_loading.png +0 -0
  455. data/public/images/admin/plugins/fancybox/fancy_nav_left.png +0 -0
  456. data/public/images/admin/plugins/fancybox/fancy_nav_right.png +0 -0
  457. data/public/images/admin/plugins/fancybox/fancy_shadow_e.png +0 -0
  458. data/public/images/admin/plugins/fancybox/fancy_shadow_n.png +0 -0
  459. data/public/images/admin/plugins/fancybox/fancy_shadow_ne.png +0 -0
  460. data/public/images/admin/plugins/fancybox/fancy_shadow_nw.png +0 -0
  461. data/public/images/admin/plugins/fancybox/fancy_shadow_s.png +0 -0
  462. data/public/images/admin/plugins/fancybox/fancy_shadow_se.png +0 -0
  463. data/public/images/admin/plugins/fancybox/fancy_shadow_sw.png +0 -0
  464. data/public/images/admin/plugins/fancybox/fancy_shadow_w.png +0 -0
  465. data/public/images/admin/plugins/fancybox/fancy_title_left.png +0 -0
  466. data/public/images/admin/plugins/fancybox/fancy_title_main.png +0 -0
  467. data/public/images/admin/plugins/fancybox/fancy_title_over.png +0 -0
  468. data/public/images/admin/plugins/fancybox/fancy_title_right.png +0 -0
  469. data/public/images/admin/plugins/fancybox/fancybox-x.png +0 -0
  470. data/public/images/admin/plugins/fancybox/fancybox-y.png +0 -0
  471. data/public/images/admin/plugins/fancybox/fancybox.png +0 -0
  472. data/public/images/admin/plugins/toggle_handle-bg.png +0 -0
  473. data/public/images/admin/plugins/toggle_handle_left-bg.png +0 -0
  474. data/public/images/admin/plugins/toggle_handle_right-bg.png +0 -0
  475. data/public/images/admin/plugins/toggle_shadow-bg.png +0 -0
  476. data/public/images/admin/rails.png +0 -0
  477. data/public/javascripts/admin/application.js +105 -0
  478. data/public/javascripts/admin/asset_collections.js +50 -0
  479. data/public/javascripts/admin/content_types.js +16 -0
  480. data/public/javascripts/admin/contents.js +17 -0
  481. data/public/javascripts/admin/custom_fields.js +113 -0
  482. data/public/javascripts/admin/jquery.js +154 -0
  483. data/public/javascripts/admin/jquery.ui.js +404 -0
  484. data/public/javascripts/admin/page_parts.js +83 -0
  485. data/public/javascripts/admin/pages.js +67 -0
  486. data/public/javascripts/admin/plugins/codemirror/codemirror.js +469 -0
  487. data/public/javascripts/admin/plugins/codemirror/editor.js +1465 -0
  488. data/public/javascripts/admin/plugins/codemirror/highlight.js +68 -0
  489. data/public/javascripts/admin/plugins/codemirror/mirrorframe.js +81 -0
  490. data/public/javascripts/admin/plugins/codemirror/parsecss.js +155 -0
  491. data/public/javascripts/admin/plugins/codemirror/parsedummy.js +32 -0
  492. data/public/javascripts/admin/plugins/codemirror/parsehtmlmixed.js +74 -0
  493. data/public/javascripts/admin/plugins/codemirror/parsejavascript.js +352 -0
  494. data/public/javascripts/admin/plugins/codemirror/parseliquid.js +409 -0
  495. data/public/javascripts/admin/plugins/codemirror/parsesparql.js +162 -0
  496. data/public/javascripts/admin/plugins/codemirror/parsexml.js +286 -0
  497. data/public/javascripts/admin/plugins/codemirror/select.js +619 -0
  498. data/public/javascripts/admin/plugins/codemirror/stringstream.js +140 -0
  499. data/public/javascripts/admin/plugins/codemirror/tokenize.js +57 -0
  500. data/public/javascripts/admin/plugins/codemirror/tokenizejavascript.js +174 -0
  501. data/public/javascripts/admin/plugins/codemirror/undo.js +410 -0
  502. data/public/javascripts/admin/plugins/codemirror/util.js +130 -0
  503. data/public/javascripts/admin/plugins/cookie.js +96 -0
  504. data/public/javascripts/admin/plugins/fancybox.js +1077 -0
  505. data/public/javascripts/admin/plugins/growl.js +143 -0
  506. data/public/javascripts/admin/plugins/json2.js +478 -0
  507. data/public/javascripts/admin/plugins/plupload/gears_init.js +86 -0
  508. data/public/javascripts/admin/plugins/plupload/jquery.plupload.queue.min.js +1 -0
  509. data/public/javascripts/admin/plugins/plupload/plupload.browserplus.min.js +1 -0
  510. data/public/javascripts/admin/plugins/plupload/plupload.flash.min.js +1 -0
  511. data/public/javascripts/admin/plugins/plupload/plupload.flash.swf +0 -0
  512. data/public/javascripts/admin/plugins/plupload/plupload.full.js +1 -0
  513. data/public/javascripts/admin/plugins/plupload/plupload.gears.min.js +1 -0
  514. data/public/javascripts/admin/plugins/plupload/plupload.html4.min.js +1 -0
  515. data/public/javascripts/admin/plugins/plupload/plupload.html5.min.js +1 -0
  516. data/public/javascripts/admin/plugins/plupload/plupload.js +1 -0
  517. data/public/javascripts/admin/plugins/plupload/plupload.silverlight.min.js +1 -0
  518. data/public/javascripts/admin/plugins/plupload/plupload.silverlight.xap +0 -0
  519. data/public/javascripts/admin/plugins/scrollTo.js +11 -0
  520. data/public/javascripts/admin/plugins/toggle.js +109 -0
  521. data/public/javascripts/admin/plugins/wslide.js +125 -0
  522. data/public/javascripts/admin/rails.js +127 -0
  523. data/public/javascripts/admin/site.js +36 -0
  524. data/public/javascripts/admin/snippets.js +16 -0
  525. data/public/javascripts/admin/theme_assets.js +103 -0
  526. data/public/stylesheets/admin/application.css +257 -0
  527. data/public/stylesheets/admin/blueprint/ie.css +26 -0
  528. data/public/stylesheets/admin/blueprint/plugins/buttons/icons/cross.png +0 -0
  529. data/public/stylesheets/admin/blueprint/plugins/buttons/icons/key.png +0 -0
  530. data/public/stylesheets/admin/blueprint/plugins/buttons/icons/tick.png +0 -0
  531. data/public/stylesheets/admin/blueprint/plugins/buttons/readme.txt +32 -0
  532. data/public/stylesheets/admin/blueprint/plugins/buttons/screen.css +97 -0
  533. data/public/stylesheets/admin/blueprint/plugins/fancy-type/readme.txt +14 -0
  534. data/public/stylesheets/admin/blueprint/plugins/fancy-type/screen.css +71 -0
  535. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/doc.png +0 -0
  536. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/email.png +0 -0
  537. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/external.png +0 -0
  538. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/feed.png +0 -0
  539. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/im.png +0 -0
  540. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/pdf.png +0 -0
  541. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/visited.png +0 -0
  542. data/public/stylesheets/admin/blueprint/plugins/link-icons/icons/xls.png +0 -0
  543. data/public/stylesheets/admin/blueprint/plugins/link-icons/readme.txt +18 -0
  544. data/public/stylesheets/admin/blueprint/plugins/link-icons/screen.css +40 -0
  545. data/public/stylesheets/admin/blueprint/plugins/rtl/readme.txt +10 -0
  546. data/public/stylesheets/admin/blueprint/plugins/rtl/screen.css +109 -0
  547. data/public/stylesheets/admin/blueprint/print.css +30 -0
  548. data/public/stylesheets/admin/blueprint/screen.css +253 -0
  549. data/public/stylesheets/admin/blueprint/src/forms.css +49 -0
  550. data/public/stylesheets/admin/blueprint/src/grid.css +213 -0
  551. data/public/stylesheets/admin/blueprint/src/grid.png +0 -0
  552. data/public/stylesheets/admin/blueprint/src/ie.css +59 -0
  553. data/public/stylesheets/admin/blueprint/src/print.css +85 -0
  554. data/public/stylesheets/admin/blueprint/src/reset.css +38 -0
  555. data/public/stylesheets/admin/blueprint/src/typography.css +105 -0
  556. data/public/stylesheets/admin/box.css +14 -0
  557. data/public/stylesheets/admin/buttons.css +67 -0
  558. data/public/stylesheets/admin/formtastic.css +137 -0
  559. data/public/stylesheets/admin/formtastic_changes.css +461 -0
  560. data/public/stylesheets/admin/layout.css +175 -0
  561. data/public/stylesheets/admin/login.css +141 -0
  562. data/public/stylesheets/admin/menu.css +241 -0
  563. data/public/stylesheets/admin/page_parts.css +60 -0
  564. data/public/stylesheets/admin/plugins/codemirror/csscolors.css +51 -0
  565. data/public/stylesheets/admin/plugins/codemirror/docs.css +50 -0
  566. data/public/stylesheets/admin/plugins/codemirror/javascriptcolors.css +56 -0
  567. data/public/stylesheets/admin/plugins/codemirror/jscolors.css +59 -0
  568. data/public/stylesheets/admin/plugins/codemirror/liquidcolors.css +35 -0
  569. data/public/stylesheets/admin/plugins/codemirror/people.jpg +0 -0
  570. data/public/stylesheets/admin/plugins/codemirror/sparqlcolors.css +43 -0
  571. data/public/stylesheets/admin/plugins/codemirror/xmlcolors.css +55 -0
  572. data/public/stylesheets/admin/plugins/fancybox.css +363 -0
  573. data/public/stylesheets/admin/plugins/toggle.css +42 -0
  574. data/spec/factories.rb +77 -0
  575. data/spec/lib/locomotive/liquid/filters/html_spec.rb +82 -0
  576. data/spec/lib/locomotive/liquid/filters/misc_spec.rb +36 -0
  577. data/spec/lib/locomotive/liquid/tags/paginate_spec.rb +99 -0
  578. data/spec/lib/locomotive/liquid/tags/snippet_spec.rb +20 -0
  579. data/spec/lib/locomotive/liquid/tags/with_scope_spec.rb +20 -0
  580. data/spec/lib/locomotive/render_spec.rb +85 -0
  581. data/spec/models/account_spec.rb +65 -0
  582. data/spec/models/asset_collections_spec.rb +174 -0
  583. data/spec/models/content_instance_spec.rb +33 -0
  584. data/spec/models/content_type_spec.rb +49 -0
  585. data/spec/models/layout_spec.rb +64 -0
  586. data/spec/models/liquid_template_spec.rb +19 -0
  587. data/spec/models/membership_spec.rb +55 -0
  588. data/spec/models/page_spec.rb +329 -0
  589. data/spec/models/site_spec.rb +143 -0
  590. data/spec/models/snippet_spec.rb +9 -0
  591. data/spec/models/theme_asset_spec.rb +89 -0
  592. data/spec/spec_helper.rb +17 -0
  593. data/spec/support/be_valid.rb +31 -0
  594. data/spec/support/carrierwave.rb +32 -0
  595. data/spec/support/locomotive.rb +41 -0
  596. data/vendor/plugins/custom_fields/MIT-LICENSE +20 -0
  597. data/vendor/plugins/custom_fields/README +13 -0
  598. data/vendor/plugins/custom_fields/Rakefile +23 -0
  599. data/vendor/plugins/custom_fields/init.rb +2 -0
  600. data/vendor/plugins/custom_fields/install.rb +1 -0
  601. data/vendor/plugins/custom_fields/lib/custom_fields.rb +14 -0
  602. data/vendor/plugins/custom_fields/lib/custom_fields/custom_field.rb +84 -0
  603. data/vendor/plugins/custom_fields/lib/custom_fields/custom_fields_for.rb +49 -0
  604. data/vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/embeds_many.rb +24 -0
  605. data/vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/document.rb +35 -0
  606. data/vendor/plugins/custom_fields/uninstall.rb +1 -0
  607. metadata +848 -0
@@ -0,0 +1,1465 @@
1
+ /* The Editor object manages the content of the editable frame. It
2
+ * catches events, colours nodes, and indents lines. This file also
3
+ * holds some functions for transforming arbitrary DOM structures into
4
+ * plain sequences of <span> and <br> elements
5
+ */
6
+
7
+ var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
8
+ var webkit = /AppleWebKit/.test(navigator.userAgent);
9
+ var safari = /Apple Computers, Inc/.test(navigator.vendor);
10
+ var gecko = /gecko\/(\d{8})/i.test(navigator.userAgent);
11
+
12
+ // Make sure a string does not contain two consecutive 'collapseable'
13
+ // whitespace characters.
14
+ function makeWhiteSpace(n) {
15
+ var buffer = [], nb = true;
16
+ for (; n > 0; n--) {
17
+ buffer.push((nb || n == 1) ? nbsp : " ");
18
+ nb = !nb;
19
+ }
20
+ return buffer.join("");
21
+ }
22
+
23
+ // Create a set of white-space characters that will not be collapsed
24
+ // by the browser, but will not break text-wrapping either.
25
+ function fixSpaces(string) {
26
+ if (string.charAt(0) == " ") string = nbsp + string.slice(1);
27
+ return string.replace(/\t/g, function(){return makeWhiteSpace(indentUnit);})
28
+ .replace(/[ \u00a0]{2,}/g, function(s) {return makeWhiteSpace(s.length);});
29
+ }
30
+
31
+ function cleanText(text) {
32
+ return text.replace(/\u00a0/g, " ").replace(/\u200b/g, "");
33
+ }
34
+
35
+ // Create a SPAN node with the expected properties for document part
36
+ // spans.
37
+ function makePartSpan(value, doc) {
38
+ var text = value;
39
+ if (value.nodeType == 3) text = value.nodeValue;
40
+ else value = doc.createTextNode(text);
41
+
42
+ var span = doc.createElement("SPAN");
43
+ span.isPart = true;
44
+ span.appendChild(value);
45
+ span.currentText = text;
46
+ return span;
47
+ }
48
+
49
+ // On webkit, when the last BR of the document does not have text
50
+ // behind it, the cursor can not be put on the line after it. This
51
+ // makes pressing enter at the end of the document occasionally do
52
+ // nothing (or at least seem to do nothing). To work around it, this
53
+ // function makes sure the document ends with a span containing a
54
+ // zero-width space character. The traverseDOM iterator filters such
55
+ // character out again, so that the parsers won't see them. This
56
+ // function is called from a few strategic places to make sure the
57
+ // zwsp is restored after the highlighting process eats it.
58
+ var webkitLastLineHack = webkit ?
59
+ function(container) {
60
+ var last = container.lastChild;
61
+ if (!last || !last.isPart || last.textContent != "\u200b")
62
+ container.appendChild(makePartSpan("\u200b", container.ownerDocument));
63
+ } : function() {};
64
+
65
+ var Editor = (function(){
66
+ // The HTML elements whose content should be suffixed by a newline
67
+ // when converting them to flat text.
68
+ var newlineElements = {"P": true, "DIV": true, "LI": true};
69
+
70
+ function asEditorLines(string) {
71
+ var tab = makeWhiteSpace(indentUnit);
72
+ return map(string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n").split("\n"), fixSpaces);
73
+ }
74
+
75
+ // Helper function for traverseDOM. Flattens an arbitrary DOM node
76
+ // into an array of textnodes and <br> tags.
77
+ function simplifyDOM(root, atEnd) {
78
+ var doc = root.ownerDocument;
79
+ var result = [];
80
+ var leaving = true;
81
+
82
+ function simplifyNode(node, top) {
83
+ if (node.nodeType == 3) {
84
+ var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/[\r\u200b]/g, "").replace(/\n/g, " "));
85
+ if (text.length) leaving = false;
86
+ result.push(node);
87
+ }
88
+ else if (isBR(node) && node.childNodes.length == 0) {
89
+ leaving = true;
90
+ result.push(node);
91
+ }
92
+ else {
93
+ forEach(node.childNodes, simplifyNode);
94
+ if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) {
95
+ leaving = true;
96
+ if (!atEnd || !top)
97
+ result.push(doc.createElement("BR"));
98
+ }
99
+ }
100
+ }
101
+
102
+ simplifyNode(root, true);
103
+ return result;
104
+ }
105
+
106
+ // Creates a MochiKit-style iterator that goes over a series of DOM
107
+ // nodes. The values it yields are strings, the textual content of
108
+ // the nodes. It makes sure that all nodes up to and including the
109
+ // one whose text is being yielded have been 'normalized' to be just
110
+ // <span> and <br> elements.
111
+ // See the story.html file for some short remarks about the use of
112
+ // continuation-passing style in this iterator.
113
+ function traverseDOM(start){
114
+ function _yield(value, c){cc = c; return value;}
115
+ function push(fun, arg, c){return function(){return fun(arg, c);};}
116
+ function stop(){cc = stop; throw StopIteration;};
117
+ var cc = push(scanNode, start, stop);
118
+ var owner = start.ownerDocument;
119
+ var nodeQueue = [];
120
+
121
+ // Create a function that can be used to insert nodes after the
122
+ // one given as argument.
123
+ function pointAt(node){
124
+ var parent = node.parentNode;
125
+ var next = node.nextSibling;
126
+ return function(newnode) {
127
+ parent.insertBefore(newnode, next);
128
+ };
129
+ }
130
+ var point = null;
131
+
132
+ // This an Opera-specific hack -- always insert an empty span
133
+ // between two BRs, because Opera's cursor code gets terribly
134
+ // confused when the cursor is between two BRs.
135
+ var afterBR = true;
136
+
137
+ // Insert a normalized node at the current point. If it is a text
138
+ // node, wrap it in a <span>, and give that span a currentText
139
+ // property -- this is used to cache the nodeValue, because
140
+ // directly accessing nodeValue is horribly slow on some browsers.
141
+ // The dirty property is used by the highlighter to determine
142
+ // which parts of the document have to be re-highlighted.
143
+ function insertPart(part){
144
+ var text = "\n";
145
+ if (part.nodeType == 3) {
146
+ select.snapshotChanged();
147
+ part = makePartSpan(part, owner);
148
+ text = part.currentText;
149
+ afterBR = false;
150
+ }
151
+ else {
152
+ if (afterBR && window.opera)
153
+ point(makePartSpan("", owner));
154
+ afterBR = true;
155
+ }
156
+ part.dirty = true;
157
+ nodeQueue.push(part);
158
+ point(part);
159
+ return text;
160
+ }
161
+
162
+ // Extract the text and newlines from a DOM node, insert them into
163
+ // the document, and yield the textual content. Used to replace
164
+ // non-normalized nodes.
165
+ function writeNode(node, c, end) {
166
+ var toYield = [];
167
+ forEach(simplifyDOM(node, end), function(part) {
168
+ toYield.push(insertPart(part));
169
+ });
170
+ return _yield(toYield.join(""), c);
171
+ }
172
+
173
+ // Check whether a node is a normalized <span> element.
174
+ function partNode(node){
175
+ if (node.isPart && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
176
+ node.currentText = node.firstChild.nodeValue;
177
+ return !/[\n\t\r]/.test(node.currentText);
178
+ }
179
+ return false;
180
+ }
181
+
182
+ // Handle a node. Add its successor to the continuation if there
183
+ // is one, find out whether the node is normalized. If it is,
184
+ // yield its content, otherwise, normalize it (writeNode will take
185
+ // care of yielding).
186
+ function scanNode(node, c){
187
+ if (node.nextSibling)
188
+ c = push(scanNode, node.nextSibling, c);
189
+
190
+ if (partNode(node)){
191
+ nodeQueue.push(node);
192
+ afterBR = false;
193
+ return _yield(node.currentText, c);
194
+ }
195
+ else if (isBR(node)) {
196
+ if (afterBR && window.opera)
197
+ node.parentNode.insertBefore(makePartSpan("", owner), node);
198
+ nodeQueue.push(node);
199
+ afterBR = true;
200
+ return _yield("\n", c);
201
+ }
202
+ else {
203
+ var end = !node.nextSibling;
204
+ point = pointAt(node);
205
+ removeElement(node);
206
+ return writeNode(node, c, end);
207
+ }
208
+ }
209
+
210
+ // MochiKit iterators are objects with a next function that
211
+ // returns the next value or throws StopIteration when there are
212
+ // no more values.
213
+ return {next: function(){return cc();}, nodes: nodeQueue};
214
+ }
215
+
216
+ // Determine the text size of a processed node.
217
+ function nodeSize(node) {
218
+ return isBR(node) ? 1 : node.currentText.length;
219
+ }
220
+
221
+ // Search backwards through the top-level nodes until the next BR or
222
+ // the start of the frame.
223
+ function startOfLine(node) {
224
+ while (node && !isBR(node)) node = node.previousSibling;
225
+ return node;
226
+ }
227
+ function endOfLine(node, container) {
228
+ if (!node) node = container.firstChild;
229
+ else if (isBR(node)) node = node.nextSibling;
230
+
231
+ while (node && !isBR(node)) node = node.nextSibling;
232
+ return node;
233
+ }
234
+
235
+ function time() {return new Date().getTime();}
236
+
237
+ // Client interface for searching the content of the editor. Create
238
+ // these by calling CodeMirror.getSearchCursor. To use, call
239
+ // findNext on the resulting object -- this returns a boolean
240
+ // indicating whether anything was found, and can be called again to
241
+ // skip to the next find. Use the select and replace methods to
242
+ // actually do something with the found locations.
243
+ function SearchCursor(editor, string, fromCursor, caseFold) {
244
+ this.editor = editor;
245
+ this.caseFold = caseFold;
246
+ if (caseFold) string = string.toLowerCase();
247
+ this.history = editor.history;
248
+ this.history.commit();
249
+
250
+ // Are we currently at an occurrence of the search string?
251
+ this.atOccurrence = false;
252
+ // The object stores a set of nodes coming after its current
253
+ // position, so that when the current point is taken out of the
254
+ // DOM tree, we can still try to continue.
255
+ this.fallbackSize = 15;
256
+ var cursor;
257
+ // Start from the cursor when specified and a cursor can be found.
258
+ if (fromCursor && (cursor = select.cursorPos(this.editor.container))) {
259
+ this.line = cursor.node;
260
+ this.offset = cursor.offset;
261
+ }
262
+ else {
263
+ this.line = null;
264
+ this.offset = 0;
265
+ }
266
+ this.valid = !!string;
267
+
268
+ // Create a matcher function based on the kind of string we have.
269
+ var target = string.split("\n"), self = this;
270
+ this.matches = (target.length == 1) ?
271
+ // For one-line strings, searching can be done simply by calling
272
+ // indexOf on the current line.
273
+ function() {
274
+ var line = cleanText(self.history.textAfter(self.line).slice(self.offset));
275
+ var match = (self.caseFold ? line.toLowerCase() : line).indexOf(string);
276
+ if (match > -1)
277
+ return {from: {node: self.line, offset: self.offset + match},
278
+ to: {node: self.line, offset: self.offset + match + string.length}};
279
+ } :
280
+ // Multi-line strings require internal iteration over lines, and
281
+ // some clunky checks to make sure the first match ends at the
282
+ // end of the line and the last match starts at the start.
283
+ function() {
284
+ var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset));
285
+ var match = (self.caseFold ? firstLine.toLowerCase() : firstLine).lastIndexOf(target[0]);
286
+ if (match == -1 || match != firstLine.length - target[0].length)
287
+ return false;
288
+ var startOffset = self.offset + match;
289
+
290
+ var line = self.history.nodeAfter(self.line);
291
+ for (var i = 1; i < target.length - 1; i++) {
292
+ var lineText = cleanText(self.history.textAfter(line));
293
+ if ((self.caseFold ? lineText.toLowerCase() : lineText) != target[i])
294
+ return false;
295
+ line = self.history.nodeAfter(line);
296
+ }
297
+
298
+ var lastLine = cleanText(self.history.textAfter(line));
299
+ if ((self.caseFold ? lastLine.toLowerCase() : lastLine).indexOf(target[target.length - 1]) != 0)
300
+ return false;
301
+
302
+ return {from: {node: self.line, offset: startOffset},
303
+ to: {node: line, offset: target[target.length - 1].length}};
304
+ };
305
+ }
306
+
307
+ SearchCursor.prototype = {
308
+ findNext: function() {
309
+ if (!this.valid) return false;
310
+ this.atOccurrence = false;
311
+ var self = this;
312
+
313
+ // Go back to the start of the document if the current line is
314
+ // no longer in the DOM tree.
315
+ if (this.line && !this.line.parentNode) {
316
+ this.line = null;
317
+ this.offset = 0;
318
+ }
319
+
320
+ // Set the cursor's position one character after the given
321
+ // position.
322
+ function saveAfter(pos) {
323
+ if (self.history.textAfter(pos.node).length > pos.offset) {
324
+ self.line = pos.node;
325
+ self.offset = pos.offset + 1;
326
+ }
327
+ else {
328
+ self.line = self.history.nodeAfter(pos.node);
329
+ self.offset = 0;
330
+ }
331
+ }
332
+
333
+ while (true) {
334
+ var match = this.matches();
335
+ // Found the search string.
336
+ if (match) {
337
+ this.atOccurrence = match;
338
+ saveAfter(match.from);
339
+ return true;
340
+ }
341
+ this.line = this.history.nodeAfter(this.line);
342
+ this.offset = 0;
343
+ // End of document.
344
+ if (!this.line) {
345
+ this.valid = false;
346
+ return false;
347
+ }
348
+ }
349
+ },
350
+
351
+ select: function() {
352
+ if (this.atOccurrence) {
353
+ select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to);
354
+ select.scrollToCursor(this.editor.container);
355
+ }
356
+ },
357
+
358
+ replace: function(string) {
359
+ if (this.atOccurrence) {
360
+ var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string);
361
+ this.line = end.node;
362
+ this.offset = end.offset;
363
+ this.atOccurrence = false;
364
+ }
365
+ }
366
+ };
367
+
368
+ // The Editor object is the main inside-the-iframe interface.
369
+ function Editor(options) {
370
+ this.options = options;
371
+ window.indentUnit = options.indentUnit;
372
+ this.parent = parent;
373
+ this.doc = document;
374
+ var container = this.container = this.doc.body;
375
+ this.win = window;
376
+ this.history = new History(container, options.undoDepth, options.undoDelay, this);
377
+ var self = this;
378
+
379
+ if (!Editor.Parser)
380
+ throw "No parser loaded.";
381
+ if (options.parserConfig && Editor.Parser.configure)
382
+ Editor.Parser.configure(options.parserConfig);
383
+
384
+ if (!options.readOnly)
385
+ select.setCursorPos(container, {node: null, offset: 0});
386
+
387
+ this.dirty = [];
388
+ this.importCode(options.content || "");
389
+ this.history.onChange = options.onChange;
390
+
391
+ if (!options.readOnly) {
392
+ if (options.continuousScanning !== false) {
393
+ this.scanner = this.documentScanner(options.passTime);
394
+ this.delayScanning();
395
+ }
396
+
397
+ function setEditable() {
398
+ // In IE, designMode frames can not run any scripts, so we use
399
+ // contentEditable instead.
400
+ if (document.body.contentEditable != undefined && internetExplorer)
401
+ document.body.contentEditable = "true";
402
+ else
403
+ document.designMode = "on";
404
+
405
+ document.documentElement.style.borderWidth = "0";
406
+ if (!options.textWrapping)
407
+ container.style.whiteSpace = "nowrap";
408
+ }
409
+
410
+ // If setting the frame editable fails, try again when the user
411
+ // focus it (happens when the frame is not visible on
412
+ // initialisation, in Firefox).
413
+ try {
414
+ setEditable();
415
+ }
416
+ catch(e) {
417
+ var focusEvent = addEventHandler(document, "focus", function() {
418
+ focusEvent();
419
+ setEditable();
420
+ }, true);
421
+ }
422
+
423
+ addEventHandler(document, "keydown", method(this, "keyDown"));
424
+ addEventHandler(document, "keypress", method(this, "keyPress"));
425
+ addEventHandler(document, "keyup", method(this, "keyUp"));
426
+
427
+ function cursorActivity() {self.cursorActivity(false);}
428
+ addEventHandler(document.body, "mouseup", cursorActivity);
429
+ addEventHandler(document.body, "cut", cursorActivity);
430
+
431
+ // workaround for a gecko bug [?] where going forward and then
432
+ // back again breaks designmode (no more cursor)
433
+ if (gecko)
434
+ addEventHandler(this.win, "pagehide", function(){self.unloaded = true;});
435
+
436
+ addEventHandler(document.body, "paste", function(event) {
437
+ cursorActivity();
438
+ var text = null;
439
+ try {
440
+ var clipboardData = event.clipboardData || window.clipboardData;
441
+ if (clipboardData) text = clipboardData.getData('Text');
442
+ }
443
+ catch(e) {}
444
+ if (text !== null) {
445
+ event.stop();
446
+ self.replaceSelection(text);
447
+ select.scrollToCursor(self.container);
448
+ }
449
+ });
450
+
451
+ if (this.options.autoMatchParens)
452
+ addEventHandler(document.body, "click", method(this, "scheduleParenHighlight"));
453
+ }
454
+ else if (!options.textWrapping) {
455
+ container.style.whiteSpace = "nowrap";
456
+ }
457
+ }
458
+
459
+ function isSafeKey(code) {
460
+ return (code >= 16 && code <= 18) || // shift, control, alt
461
+ (code >= 33 && code <= 40); // arrows, home, end
462
+ }
463
+
464
+ Editor.prototype = {
465
+ // Import a piece of code into the editor.
466
+ importCode: function(code) {
467
+ this.history.push(null, null, asEditorLines(code));
468
+ this.history.reset();
469
+ },
470
+
471
+ // Extract the code from the editor.
472
+ getCode: function() {
473
+ if (!this.container.firstChild)
474
+ return "";
475
+
476
+ var accum = [];
477
+ select.markSelection(this.win);
478
+ forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
479
+ webkitLastLineHack(this.container);
480
+ select.selectMarked();
481
+ return cleanText(accum.join(""));
482
+ },
483
+
484
+ checkLine: function(node) {
485
+ if (node === false || !(node == null || node.parentNode == this.container))
486
+ throw parent.CodeMirror.InvalidLineHandle;
487
+ },
488
+
489
+ cursorPosition: function(start) {
490
+ if (start == null) start = true;
491
+ var pos = select.cursorPos(this.container, start);
492
+ if (pos) return {line: pos.node, character: pos.offset};
493
+ else return {line: null, character: 0};
494
+ },
495
+
496
+ firstLine: function() {
497
+ return null;
498
+ },
499
+
500
+ lastLine: function() {
501
+ if (this.container.lastChild) return startOfLine(this.container.lastChild);
502
+ else return null;
503
+ },
504
+
505
+ nextLine: function(line) {
506
+ this.checkLine(line);
507
+ var end = endOfLine(line, this.container);
508
+ return end || false;
509
+ },
510
+
511
+ prevLine: function(line) {
512
+ this.checkLine(line);
513
+ if (line == null) return false;
514
+ return startOfLine(line.previousSibling);
515
+ },
516
+
517
+ visibleLineCount: function() {
518
+ var line = this.container.firstChild;
519
+ while (line && isBR(line)) line = line.nextSibling; // BR heights are unreliable
520
+ if (!line) return false;
521
+ var innerHeight = (window.innerHeight
522
+ || document.documentElement.clientHeight
523
+ || document.body.clientHeight);
524
+ return Math.floor(innerHeight / line.offsetHeight);
525
+ },
526
+
527
+ selectLines: function(startLine, startOffset, endLine, endOffset) {
528
+ this.checkLine(startLine);
529
+ var start = {node: startLine, offset: startOffset}, end = null;
530
+ if (endOffset !== undefined) {
531
+ this.checkLine(endLine);
532
+ end = {node: endLine, offset: endOffset};
533
+ }
534
+ select.setCursorPos(this.container, start, end);
535
+ select.scrollToCursor(this.container);
536
+ },
537
+
538
+ lineContent: function(line) {
539
+ var accum = [];
540
+ for (line = line ? line.nextSibling : this.container.firstChild;
541
+ line && !isBR(line); line = line.nextSibling)
542
+ accum.push(nodeText(line));
543
+ return cleanText(accum.join(""));
544
+ },
545
+
546
+ setLineContent: function(line, content) {
547
+ this.history.commit();
548
+ this.replaceRange({node: line, offset: 0},
549
+ {node: line, offset: this.history.textAfter(line).length},
550
+ content);
551
+ this.addDirtyNode(line);
552
+ this.scheduleHighlight();
553
+ },
554
+
555
+ removeLine: function(line) {
556
+ var node = line ? line.nextSibling : this.container.firstChild;
557
+ while (node) {
558
+ var next = node.nextSibling;
559
+ removeElement(node);
560
+ if (isBR(node)) break;
561
+ node = next;
562
+ }
563
+ this.addDirtyNode(line);
564
+ this.scheduleHighlight();
565
+ },
566
+
567
+ insertIntoLine: function(line, position, content) {
568
+ var before = null;
569
+ if (position == "end") {
570
+ before = endOfLine(line, this.container);
571
+ }
572
+ else {
573
+ for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) {
574
+ if (position == 0) {
575
+ before = cur;
576
+ break;
577
+ }
578
+ var text = nodeText(cur);
579
+ if (text.length > position) {
580
+ before = cur.nextSibling;
581
+ content = text.slice(0, position) + content + text.slice(position);
582
+ removeElement(cur);
583
+ break;
584
+ }
585
+ position -= text.length;
586
+ }
587
+ }
588
+
589
+ var lines = asEditorLines(content), doc = this.container.ownerDocument;
590
+ for (var i = 0; i < lines.length; i++) {
591
+ if (i > 0) this.container.insertBefore(doc.createElement("BR"), before);
592
+ this.container.insertBefore(makePartSpan(lines[i], doc), before);
593
+ }
594
+ this.addDirtyNode(line);
595
+ this.scheduleHighlight();
596
+ },
597
+
598
+ // Retrieve the selected text.
599
+ selectedText: function() {
600
+ var h = this.history;
601
+ h.commit();
602
+
603
+ var start = select.cursorPos(this.container, true),
604
+ end = select.cursorPos(this.container, false);
605
+ if (!start || !end) return "";
606
+
607
+ if (start.node == end.node)
608
+ return h.textAfter(start.node).slice(start.offset, end.offset);
609
+
610
+ var text = [h.textAfter(start.node).slice(start.offset)];
611
+ for (var pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos))
612
+ text.push(h.textAfter(pos));
613
+ text.push(h.textAfter(end.node).slice(0, end.offset));
614
+ return cleanText(text.join("\n"));
615
+ },
616
+
617
+ // Replace the selection with another piece of text.
618
+ replaceSelection: function(text) {
619
+ this.history.commit();
620
+
621
+ var start = select.cursorPos(this.container, true),
622
+ end = select.cursorPos(this.container, false);
623
+ if (!start || !end) return;
624
+
625
+ end = this.replaceRange(start, end, text);
626
+ select.setCursorPos(this.container, end);
627
+ webkitLastLineHack(this.container);
628
+ },
629
+
630
+ reroutePasteEvent: function() {
631
+ if (this.capturingPaste || window.opera) return;
632
+ this.capturingPaste = true;
633
+ var te = window.frameElement.CodeMirror.textareaHack;
634
+ parent.focus();
635
+ te.value = "";
636
+ te.focus();
637
+
638
+ var self = this;
639
+ this.parent.setTimeout(function() {
640
+ self.capturingPaste = false;
641
+ self.win.focus();
642
+ if (self.selectionSnapshot) // IE hack
643
+ self.win.select.setBookmark(self.container, self.selectionSnapshot);
644
+ var text = te.value;
645
+ if (text) {
646
+ self.replaceSelection(text);
647
+ select.scrollToCursor(self.container);
648
+ }
649
+ }, 10);
650
+ },
651
+
652
+ replaceRange: function(from, to, text) {
653
+ var lines = asEditorLines(text);
654
+ lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
655
+ var lastLine = lines[lines.length - 1];
656
+ lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
657
+ var end = this.history.nodeAfter(to.node);
658
+ this.history.push(from.node, end, lines);
659
+ return {node: this.history.nodeBefore(end),
660
+ offset: lastLine.length};
661
+ },
662
+
663
+ getSearchCursor: function(string, fromCursor, caseFold) {
664
+ return new SearchCursor(this, string, fromCursor, caseFold);
665
+ },
666
+
667
+ // Re-indent the whole buffer
668
+ reindent: function() {
669
+ if (this.container.firstChild)
670
+ this.indentRegion(null, this.container.lastChild);
671
+ },
672
+
673
+ reindentSelection: function(direction) {
674
+ if (!select.somethingSelected(this.win)) {
675
+ this.indentAtCursor(direction);
676
+ }
677
+ else {
678
+ var start = select.selectionTopNode(this.container, true),
679
+ end = select.selectionTopNode(this.container, false);
680
+ if (start === false || end === false) return;
681
+ this.indentRegion(start, end, direction);
682
+ }
683
+ },
684
+
685
+ grabKeys: function(eventHandler, filter) {
686
+ this.frozen = eventHandler;
687
+ this.keyFilter = filter;
688
+ },
689
+ ungrabKeys: function() {
690
+ this.frozen = "leave";
691
+ this.keyFilter = null;
692
+ },
693
+
694
+ setParser: function(name) {
695
+ Editor.Parser = window[name];
696
+ if (this.container.firstChild) {
697
+ forEach(this.container.childNodes, function(n) {
698
+ if (n.nodeType != 3) n.dirty = true;
699
+ });
700
+ this.addDirtyNode(this.container.firstChild);
701
+ this.scheduleHighlight();
702
+ }
703
+ },
704
+
705
+ // Intercept enter and tab, and assign their new functions.
706
+ keyDown: function(event) {
707
+ if (this.frozen == "leave") this.frozen = null;
708
+ if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode, event))) {
709
+ event.stop();
710
+ this.frozen(event);
711
+ return;
712
+ }
713
+
714
+ var code = event.keyCode;
715
+ // Don't scan when the user is typing.
716
+ this.delayScanning();
717
+ // Schedule a paren-highlight event, if configured.
718
+ if (this.options.autoMatchParens)
719
+ this.scheduleParenHighlight();
720
+
721
+ // The various checks for !altKey are there because AltGr sets both
722
+ // ctrlKey and altKey to true, and should not be recognised as
723
+ // Control.
724
+ if (code == 13) { // enter
725
+ if (event.ctrlKey && !event.altKey) {
726
+ this.reparseBuffer();
727
+ }
728
+ else {
729
+ select.insertNewlineAtCursor(this.win);
730
+ this.indentAtCursor();
731
+ select.scrollToCursor(this.container);
732
+ }
733
+ event.stop();
734
+ }
735
+ else if (code == 9 && this.options.tabMode != "default" && !event.ctrlKey) { // tab
736
+ this.handleTab(!event.shiftKey);
737
+ event.stop();
738
+ }
739
+ else if (code == 32 && event.shiftKey && this.options.tabMode == "default") { // space
740
+ this.handleTab(true);
741
+ event.stop();
742
+ }
743
+ else if (code == 36 && !event.shiftKey && !event.ctrlKey) { // home
744
+ if (this.home()) event.stop();
745
+ }
746
+ else if (code == 35 && !event.shiftKey && !event.ctrlKey) { // end
747
+ if (this.end()) event.stop();
748
+ }
749
+ // Only in Firefox is the default behavior for PgUp/PgDn correct.
750
+ else if (code == 33 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgUp
751
+ if (this.pageUp()) event.stop();
752
+ }
753
+ else if (code == 34 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgDn
754
+ if (this.pageDown()) event.stop();
755
+ }
756
+ else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ]
757
+ this.highlightParens(event.shiftKey, true);
758
+ event.stop();
759
+ }
760
+ else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right
761
+ var cursor = select.selectionTopNode(this.container);
762
+ if (cursor === false || !this.container.firstChild) return;
763
+
764
+ if (code == 37) select.focusAfterNode(startOfLine(cursor), this.container);
765
+ else {
766
+ var end = endOfLine(cursor, this.container);
767
+ select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container);
768
+ }
769
+ event.stop();
770
+ }
771
+ else if ((event.ctrlKey || event.metaKey) && !event.altKey) {
772
+ if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y
773
+ select.scrollToNode(this.history.redo());
774
+ event.stop();
775
+ }
776
+ else if (code == 90 || (safari && code == 8)) { // Z, backspace
777
+ select.scrollToNode(this.history.undo());
778
+ event.stop();
779
+ }
780
+ else if (code == 83 && this.options.saveFunction) { // S
781
+ this.options.saveFunction();
782
+ event.stop();
783
+ }
784
+ else if (internetExplorer && code == 86) {
785
+ this.reroutePasteEvent();
786
+ }
787
+ }
788
+ },
789
+
790
+ // Check for characters that should re-indent the current line,
791
+ // and prevent Opera from handling enter and tab anyway.
792
+ keyPress: function(event) {
793
+ var electric = Editor.Parser.electricChars, self = this;
794
+ // Hack for Opera, and Firefox on OS X, in which stopping a
795
+ // keydown event does not prevent the associated keypress event
796
+ // from happening, so we have to cancel enter and tab again
797
+ // here.
798
+ if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode, event))) ||
799
+ event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
800
+ (event.keyCode == 32 && event.shiftKey && this.options.tabMode == "default"))
801
+ event.stop();
802
+ else if (electric && electric.indexOf(event.character) != -1)
803
+ this.parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
804
+ else if ((event.character == "v" || event.character == "V")
805
+ && (event.ctrlKey || event.metaKey) && !event.altKey) // ctrl-V
806
+ this.reroutePasteEvent();
807
+ },
808
+
809
+ // Mark the node at the cursor dirty when a non-safe key is
810
+ // released.
811
+ keyUp: function(event) {
812
+ this.cursorActivity(isSafeKey(event.keyCode));
813
+ },
814
+
815
+ // Indent the line following a given <br>, or null for the first
816
+ // line. If given a <br> element, this must have been highlighted
817
+ // so that it has an indentation method. Returns the whitespace
818
+ // element that has been modified or created (if any).
819
+ indentLineAfter: function(start, direction) {
820
+ // whiteSpace is the whitespace span at the start of the line,
821
+ // or null if there is no such node.
822
+ var whiteSpace = start ? start.nextSibling : this.container.firstChild;
823
+ if (whiteSpace && !hasClass(whiteSpace, "whitespace"))
824
+ whiteSpace = null;
825
+
826
+ // Sometimes the start of the line can influence the correct
827
+ // indentation, so we retrieve it.
828
+ var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
829
+ var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
830
+
831
+ // Ask the lexical context for the correct indentation, and
832
+ // compute how much this differs from the current indentation.
833
+ var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
834
+ if (direction != null && this.options.tabMode == "shift")
835
+ newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
836
+ else if (start)
837
+ newIndent = start.indentation(nextChars, curIndent, direction);
838
+ else if (Editor.Parser.firstIndentation)
839
+ newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
840
+ var indentDiff = newIndent - curIndent;
841
+
842
+ // If there is too much, this is just a matter of shrinking a span.
843
+ if (indentDiff < 0) {
844
+ if (newIndent == 0) {
845
+ if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild, 0);
846
+ removeElement(whiteSpace);
847
+ whiteSpace = null;
848
+ }
849
+ else {
850
+ select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
851
+ whiteSpace.currentText = makeWhiteSpace(newIndent);
852
+ whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
853
+ }
854
+ }
855
+ // Not enough...
856
+ else if (indentDiff > 0) {
857
+ // If there is whitespace, we grow it.
858
+ if (whiteSpace) {
859
+ whiteSpace.currentText = makeWhiteSpace(newIndent);
860
+ whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
861
+ }
862
+ // Otherwise, we have to add a new whitespace node.
863
+ else {
864
+ whiteSpace = makePartSpan(makeWhiteSpace(newIndent), this.doc);
865
+ whiteSpace.className = "whitespace";
866
+ if (start) insertAfter(whiteSpace, start);
867
+ else this.container.insertBefore(whiteSpace, this.container.firstChild);
868
+ }
869
+ var fromNode = firstText && (firstText.firstChild || firstText);
870
+ select.snapshotMove(fromNode, whiteSpace.firstChild, newIndent, false, true);
871
+ }
872
+ if (indentDiff != 0) this.addDirtyNode(start);
873
+ },
874
+
875
+ // Re-highlight the selected part of the document.
876
+ highlightAtCursor: function() {
877
+ var pos = select.selectionTopNode(this.container, true);
878
+ var to = select.selectionTopNode(this.container, false);
879
+ if (pos === false || to === false) return false;
880
+
881
+ select.markSelection(this.win);
882
+ if (this.highlight(pos, endOfLine(to, this.container), true, 20) === false)
883
+ return false;
884
+ select.selectMarked();
885
+ return true;
886
+ },
887
+
888
+ // When tab is pressed with text selected, the whole selection is
889
+ // re-indented, when nothing is selected, the line with the cursor
890
+ // is re-indented.
891
+ handleTab: function(direction) {
892
+ if (this.options.tabMode == "spaces")
893
+ select.insertTabAtCursor(this.win);
894
+ else
895
+ this.reindentSelection(direction);
896
+ },
897
+
898
+ // Custom home behaviour that doesn't land the cursor in front of
899
+ // leading whitespace unless pressed twice.
900
+ home: function() {
901
+ var cur = select.selectionTopNode(this.container, true), start = cur;
902
+ if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
903
+ return false;
904
+
905
+ while (cur && !isBR(cur)) cur = cur.previousSibling;
906
+ var next = cur ? cur.nextSibling : this.container.firstChild;
907
+ if (next && next != start && next.isPart && hasClass(next, "whitespace"))
908
+ select.focusAfterNode(next, this.container);
909
+ else
910
+ select.focusAfterNode(cur, this.container);
911
+
912
+ select.scrollToCursor(this.container);
913
+ return true;
914
+ },
915
+
916
+ // Some browsers (Opera) don't manage to handle the end key
917
+ // properly in the face of vertical scrolling.
918
+ end: function() {
919
+ var cur = select.selectionTopNode(this.container, true);
920
+ if (cur === false) return false;
921
+ cur = endOfLine(cur, this.container);
922
+ if (!cur) return false;
923
+ select.focusAfterNode(cur.previousSibling, this.container);
924
+ select.scrollToCursor(this.container);
925
+ return true;
926
+ },
927
+
928
+ pageUp: function() {
929
+ var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
930
+ if (line === false || scrollAmount === false) return false;
931
+ // Try to keep one line on the screen.
932
+ scrollAmount -= 2;
933
+ for (var i = 0; i < scrollAmount; i++) {
934
+ line = this.prevLine(line);
935
+ if (line === false) break;
936
+ }
937
+ if (i == 0) return false; // Already at first line
938
+ select.setCursorPos(this.container, {node: line, offset: 0});
939
+ select.scrollToCursor(this.container);
940
+ return true;
941
+ },
942
+
943
+ pageDown: function() {
944
+ var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
945
+ if (line === false || scrollAmount === false) return false;
946
+ // Try to move to the last line of the current page.
947
+ scrollAmount -= 2;
948
+ for (var i = 0; i < scrollAmount; i++) {
949
+ var nextLine = this.nextLine(line);
950
+ if (nextLine === false) break;
951
+ line = nextLine;
952
+ }
953
+ if (i == 0) return false; // Already at last line
954
+ select.setCursorPos(this.container, {node: line, offset: 0});
955
+ select.scrollToCursor(this.container);
956
+ return true;
957
+ },
958
+
959
+ // Delay (or initiate) the next paren highlight event.
960
+ scheduleParenHighlight: function() {
961
+ if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
962
+ var self = this;
963
+ this.parenEvent = this.parent.setTimeout(function(){self.highlightParens();}, 300);
964
+ },
965
+
966
+ // Take the token before the cursor. If it contains a character in
967
+ // '()[]{}', search for the matching paren/brace/bracket, and
968
+ // highlight them in green for a moment, or red if no proper match
969
+ // was found.
970
+ highlightParens: function(jump, fromKey) {
971
+ var self = this;
972
+ // give the relevant nodes a colour.
973
+ function highlight(node, ok) {
974
+ if (!node) return;
975
+ if (self.options.markParen) {
976
+ self.options.markParen(node, ok);
977
+ }
978
+ else {
979
+ node.style.fontWeight = "bold";
980
+ node.style.color = ok ? "#8F8" : "#F88";
981
+ }
982
+ }
983
+ function unhighlight(node) {
984
+ if (!node) return;
985
+ if (self.options.unmarkParen) {
986
+ self.options.unmarkParen(node);
987
+ }
988
+ else {
989
+ node.style.fontWeight = "";
990
+ node.style.color = "";
991
+ }
992
+ }
993
+ if (!fromKey && self.highlighted) {
994
+ unhighlight(self.highlighted[0]);
995
+ unhighlight(self.highlighted[1]);
996
+ }
997
+
998
+ if (!window.select) return;
999
+ // Clear the event property.
1000
+ if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
1001
+ this.parenEvent = null;
1002
+
1003
+ // Extract a 'paren' from a piece of text.
1004
+ function paren(node) {
1005
+ if (node.currentText) {
1006
+ var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
1007
+ return match && match[1];
1008
+ }
1009
+ }
1010
+ // Determine the direction a paren is facing.
1011
+ function forward(ch) {
1012
+ return /[\(\[\{]/.test(ch);
1013
+ }
1014
+
1015
+ var ch, cursor = select.selectionTopNode(this.container, true);
1016
+ if (!cursor || !this.highlightAtCursor()) return;
1017
+ cursor = select.selectionTopNode(this.container, true);
1018
+ if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor)))))
1019
+ return;
1020
+ // We only look for tokens with the same className.
1021
+ var className = cursor.className, dir = forward(ch), match = matching[ch];
1022
+
1023
+ // Since parts of the document might not have been properly
1024
+ // highlighted, and it is hard to know in advance which part we
1025
+ // have to scan, we just try, and when we find dirty nodes we
1026
+ // abort, parse them, and re-try.
1027
+ function tryFindMatch() {
1028
+ var stack = [], ch, ok = true;
1029
+ for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
1030
+ if (runner.className == className && isSpan(runner) && (ch = paren(runner))) {
1031
+ if (forward(ch) == dir)
1032
+ stack.push(ch);
1033
+ else if (!stack.length)
1034
+ ok = false;
1035
+ else if (stack.pop() != matching[ch])
1036
+ ok = false;
1037
+ if (!stack.length) break;
1038
+ }
1039
+ else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
1040
+ return {node: runner, status: "dirty"};
1041
+ }
1042
+ }
1043
+ return {node: runner, status: runner && ok};
1044
+ }
1045
+
1046
+ while (true) {
1047
+ var found = tryFindMatch();
1048
+ if (found.status == "dirty") {
1049
+ this.highlight(found.node, endOfLine(found.node));
1050
+ // Needed because in some corner cases a highlight does not
1051
+ // reach a node.
1052
+ found.node.dirty = false;
1053
+ continue;
1054
+ }
1055
+ else {
1056
+ highlight(cursor, found.status);
1057
+ highlight(found.node, found.status);
1058
+ if (fromKey)
1059
+ self.parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
1060
+ else
1061
+ self.highlighted = [cursor, found.node];
1062
+ if (jump && found.node)
1063
+ select.focusAfterNode(found.node.previousSibling, this.container);
1064
+ break;
1065
+ }
1066
+ }
1067
+ },
1068
+
1069
+ // Adjust the amount of whitespace at the start of the line that
1070
+ // the cursor is on so that it is indented properly.
1071
+ indentAtCursor: function(direction) {
1072
+ if (!this.container.firstChild) return;
1073
+ // The line has to have up-to-date lexical information, so we
1074
+ // highlight it first.
1075
+ if (!this.highlightAtCursor()) return;
1076
+ var cursor = select.selectionTopNode(this.container, false);
1077
+ // If we couldn't determine the place of the cursor,
1078
+ // there's nothing to indent.
1079
+ if (cursor === false)
1080
+ return;
1081
+ select.markSelection(this.win);
1082
+ this.indentLineAfter(startOfLine(cursor), direction);
1083
+ select.selectMarked();
1084
+ },
1085
+
1086
+ // Indent all lines whose start falls inside of the current
1087
+ // selection.
1088
+ indentRegion: function(start, end, direction) {
1089
+ var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
1090
+ if (!isBR(end)) end = endOfLine(end, this.container);
1091
+ this.addDirtyNode(start);
1092
+
1093
+ do {
1094
+ var next = endOfLine(current, this.container);
1095
+ if (current) this.highlight(before, next, true);
1096
+ this.indentLineAfter(current, direction);
1097
+ before = current;
1098
+ current = next;
1099
+ } while (current != end);
1100
+ select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
1101
+ },
1102
+
1103
+ // Find the node that the cursor is in, mark it as dirty, and make
1104
+ // sure a highlight pass is scheduled.
1105
+ cursorActivity: function(safe) {
1106
+ // pagehide event hack above
1107
+ if (this.unloaded) {
1108
+ this.win.document.designMode = "off";
1109
+ this.win.document.designMode = "on";
1110
+ this.unloaded = false;
1111
+ }
1112
+
1113
+ if (internetExplorer) {
1114
+ this.container.createTextRange().execCommand("unlink");
1115
+ this.selectionSnapshot = select.getBookmark(this.container);
1116
+ }
1117
+
1118
+ var activity = this.options.cursorActivity;
1119
+ if (!safe || activity) {
1120
+ var cursor = select.selectionTopNode(this.container, false);
1121
+ if (cursor === false || !this.container.firstChild) return;
1122
+ cursor = cursor || this.container.firstChild;
1123
+ if (activity) activity(cursor);
1124
+ if (!safe) {
1125
+ this.scheduleHighlight();
1126
+ this.addDirtyNode(cursor);
1127
+ }
1128
+ }
1129
+ },
1130
+
1131
+ reparseBuffer: function() {
1132
+ forEach(this.container.childNodes, function(node) {node.dirty = true;});
1133
+ if (this.container.firstChild)
1134
+ this.addDirtyNode(this.container.firstChild);
1135
+ },
1136
+
1137
+ // Add a node to the set of dirty nodes, if it isn't already in
1138
+ // there.
1139
+ addDirtyNode: function(node) {
1140
+ node = node || this.container.firstChild;
1141
+ if (!node) return;
1142
+
1143
+ for (var i = 0; i < this.dirty.length; i++)
1144
+ if (this.dirty[i] == node) return;
1145
+
1146
+ if (node.nodeType != 3)
1147
+ node.dirty = true;
1148
+ this.dirty.push(node);
1149
+ },
1150
+
1151
+ allClean: function() {
1152
+ return !this.dirty.length;
1153
+ },
1154
+
1155
+ // Cause a highlight pass to happen in options.passDelay
1156
+ // milliseconds. Clear the existing timeout, if one exists. This
1157
+ // way, the passes do not happen while the user is typing, and
1158
+ // should as unobtrusive as possible.
1159
+ scheduleHighlight: function() {
1160
+ // Timeouts are routed through the parent window, because on
1161
+ // some browsers designMode windows do not fire timeouts.
1162
+ var self = this;
1163
+ this.parent.clearTimeout(this.highlightTimeout);
1164
+ this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
1165
+ },
1166
+
1167
+ // Fetch one dirty node, and remove it from the dirty set.
1168
+ getDirtyNode: function() {
1169
+ while (this.dirty.length > 0) {
1170
+ var found = this.dirty.pop();
1171
+ // IE8 sometimes throws an unexplainable 'invalid argument'
1172
+ // exception for found.parentNode
1173
+ try {
1174
+ // If the node has been coloured in the meantime, or is no
1175
+ // longer in the document, it should not be returned.
1176
+ while (found && found.parentNode != this.container)
1177
+ found = found.parentNode;
1178
+ if (found && (found.dirty || found.nodeType == 3))
1179
+ return found;
1180
+ } catch (e) {}
1181
+ }
1182
+ return null;
1183
+ },
1184
+
1185
+ // Pick dirty nodes, and highlight them, until options.passTime
1186
+ // milliseconds have gone by. The highlight method will continue
1187
+ // to next lines as long as it finds dirty nodes. It returns
1188
+ // information about the place where it stopped. If there are
1189
+ // dirty nodes left after this function has spent all its lines,
1190
+ // it shedules another highlight to finish the job.
1191
+ highlightDirty: function(force) {
1192
+ // Prevent FF from raising an error when it is firing timeouts
1193
+ // on a page that's no longer loaded.
1194
+ if (!window.select) return false;
1195
+
1196
+ if (!this.options.readOnly) select.markSelection(this.win);
1197
+ var start, endTime = force ? null : time() + this.options.passTime;
1198
+ while ((time() < endTime || force) && (start = this.getDirtyNode())) {
1199
+ var result = this.highlight(start, endTime);
1200
+ if (result && result.node && result.dirty)
1201
+ this.addDirtyNode(result.node);
1202
+ }
1203
+ if (!this.options.readOnly) select.selectMarked();
1204
+ if (start) this.scheduleHighlight();
1205
+ return this.dirty.length == 0;
1206
+ },
1207
+
1208
+ // Creates a function that, when called through a timeout, will
1209
+ // continuously re-parse the document.
1210
+ documentScanner: function(passTime) {
1211
+ var self = this, pos = null;
1212
+ return function() {
1213
+ // FF timeout weirdness workaround.
1214
+ if (!window.select) return;
1215
+ // If the current node is no longer in the document... oh
1216
+ // well, we start over.
1217
+ if (pos && pos.parentNode != self.container)
1218
+ pos = null;
1219
+ select.markSelection(self.win);
1220
+ var result = self.highlight(pos, time() + passTime, true);
1221
+ select.selectMarked();
1222
+ var newPos = result ? (result.node && result.node.nextSibling) : null;
1223
+ pos = (pos == newPos) ? null : newPos;
1224
+ self.delayScanning();
1225
+ };
1226
+ },
1227
+
1228
+ // Starts the continuous scanning process for this document after
1229
+ // a given interval.
1230
+ delayScanning: function() {
1231
+ if (this.scanner) {
1232
+ this.parent.clearTimeout(this.documentScan);
1233
+ this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
1234
+ }
1235
+ },
1236
+
1237
+ // The function that does the actual highlighting/colouring (with
1238
+ // help from the parser and the DOM normalizer). Its interface is
1239
+ // rather overcomplicated, because it is used in different
1240
+ // situations: ensuring that a certain line is highlighted, or
1241
+ // highlighting up to X milliseconds starting from a certain
1242
+ // point. The 'from' argument gives the node at which it should
1243
+ // start. If this is null, it will start at the beginning of the
1244
+ // document. When a timestamp is given with the 'target' argument,
1245
+ // it will stop highlighting at that time. If this argument holds
1246
+ // a DOM node, it will highlight until it reaches that node. If at
1247
+ // any time it comes across two 'clean' lines (no dirty nodes), it
1248
+ // will stop, except when 'cleanLines' is true. maxBacktrack is
1249
+ // the maximum number of lines to backtrack to find an existing
1250
+ // parser instance. This is used to give up in situations where a
1251
+ // highlight would take too long and freeze the browser interface.
1252
+ highlight: function(from, target, cleanLines, maxBacktrack){
1253
+ var container = this.container, self = this, active = this.options.activeTokens;
1254
+ var endTime = (typeof target == "number" ? target : null);
1255
+
1256
+ if (!container.firstChild)
1257
+ return false;
1258
+ // Backtrack to the first node before from that has a partial
1259
+ // parse stored.
1260
+ while (from && (!from.parserFromHere || from.dirty)) {
1261
+ if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0)
1262
+ return false;
1263
+ from = from.previousSibling;
1264
+ }
1265
+ // If we are at the end of the document, do nothing.
1266
+ if (from && !from.nextSibling)
1267
+ return false;
1268
+
1269
+ // Check whether a part (<span> node) and the corresponding token
1270
+ // match.
1271
+ function correctPart(token, part){
1272
+ return !part.reduced && part.currentText == token.value && part.className == token.style;
1273
+ }
1274
+ // Shorten the text associated with a part by chopping off
1275
+ // characters from the front. Note that only the currentText
1276
+ // property gets changed. For efficiency reasons, we leave the
1277
+ // nodeValue alone -- we set the reduced flag to indicate that
1278
+ // this part must be replaced.
1279
+ function shortenPart(part, minus){
1280
+ part.currentText = part.currentText.substring(minus);
1281
+ part.reduced = true;
1282
+ }
1283
+ // Create a part corresponding to a given token.
1284
+ function tokenPart(token){
1285
+ var part = makePartSpan(token.value, self.doc);
1286
+ part.className = token.style;
1287
+ return part;
1288
+ }
1289
+
1290
+ function maybeTouch(node) {
1291
+ if (node) {
1292
+ var old = node.oldNextSibling;
1293
+ if (lineDirty || old === undefined || node.nextSibling != old)
1294
+ self.history.touch(node);
1295
+ node.oldNextSibling = node.nextSibling;
1296
+ }
1297
+ else {
1298
+ var old = self.container.oldFirstChild;
1299
+ if (lineDirty || old === undefined || self.container.firstChild != old)
1300
+ self.history.touch(null);
1301
+ self.container.oldFirstChild = self.container.firstChild;
1302
+ }
1303
+ }
1304
+
1305
+ // Get the token stream. If from is null, we start with a new
1306
+ // parser from the start of the frame, otherwise a partial parse
1307
+ // is resumed.
1308
+ var traversal = traverseDOM(from ? from.nextSibling : container.firstChild),
1309
+ stream = stringStream(traversal),
1310
+ parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream);
1311
+
1312
+ function surroundedByBRs(node) {
1313
+ return (node.previousSibling == null || isBR(node.previousSibling)) &&
1314
+ (node.nextSibling == null || isBR(node.nextSibling));
1315
+ }
1316
+
1317
+ // parts is an interface to make it possible to 'delay' fetching
1318
+ // the next DOM node until we are completely done with the one
1319
+ // before it. This is necessary because often the next node is
1320
+ // not yet available when we want to proceed past the current
1321
+ // one.
1322
+ var parts = {
1323
+ current: null,
1324
+ // Fetch current node.
1325
+ get: function(){
1326
+ if (!this.current)
1327
+ this.current = traversal.nodes.shift();
1328
+ return this.current;
1329
+ },
1330
+ // Advance to the next part (do not fetch it yet).
1331
+ next: function(){
1332
+ this.current = null;
1333
+ },
1334
+ // Remove the current part from the DOM tree, and move to the
1335
+ // next.
1336
+ remove: function(){
1337
+ container.removeChild(this.get());
1338
+ this.current = null;
1339
+ },
1340
+ // Advance to the next part that is not empty, discarding empty
1341
+ // parts.
1342
+ getNonEmpty: function(){
1343
+ var part = this.get();
1344
+ // Allow empty nodes when they are alone on a line, needed
1345
+ // for the FF cursor bug workaround (see select.js,
1346
+ // insertNewlineAtCursor).
1347
+ while (part && isSpan(part) && part.currentText == "") {
1348
+ // Leave empty nodes that are alone on a line alone in
1349
+ // Opera, since that browsers doesn't deal well with
1350
+ // having 2 BRs in a row.
1351
+ if (window.opera && surroundedByBRs(part)) {
1352
+ this.next();
1353
+ part = this.get();
1354
+ }
1355
+ else {
1356
+ var old = part;
1357
+ this.remove();
1358
+ part = this.get();
1359
+ // Adjust selection information, if any. See select.js for details.
1360
+ select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0);
1361
+ }
1362
+ }
1363
+
1364
+ return part;
1365
+ }
1366
+ };
1367
+
1368
+ var lineDirty = false, prevLineDirty = true, lineNodes = 0;
1369
+
1370
+ // This forEach loops over the tokens from the parsed stream, and
1371
+ // at the same time uses the parts object to proceed through the
1372
+ // corresponding DOM nodes.
1373
+ forEach(parsed, function(token){
1374
+ var part = parts.getNonEmpty();
1375
+
1376
+ if (token.value == "\n"){
1377
+ // The idea of the two streams actually staying synchronized
1378
+ // is such a long shot that we explicitly check.
1379
+ if (!isBR(part))
1380
+ throw "Parser out of sync. Expected BR.";
1381
+
1382
+ if (part.dirty || !part.indentation) lineDirty = true;
1383
+ maybeTouch(from);
1384
+ from = part;
1385
+
1386
+ // Every <br> gets a copy of the parser state and a lexical
1387
+ // context assigned to it. The first is used to be able to
1388
+ // later resume parsing from this point, the second is used
1389
+ // for indentation.
1390
+ part.parserFromHere = parsed.copy();
1391
+ part.indentation = token.indentation;
1392
+ part.dirty = false;
1393
+
1394
+ // If the target argument wasn't an integer, go at least
1395
+ // until that node.
1396
+ if (endTime == null && part == target) throw StopIteration;
1397
+
1398
+ // A clean line with more than one node means we are done.
1399
+ // Throwing a StopIteration is the way to break out of a
1400
+ // MochiKit forEach loop.
1401
+ if ((endTime != null && time() >= endTime) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines))
1402
+ throw StopIteration;
1403
+ prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0;
1404
+ parts.next();
1405
+ }
1406
+ else {
1407
+ if (!isSpan(part))
1408
+ throw "Parser out of sync. Expected SPAN.";
1409
+ if (part.dirty)
1410
+ lineDirty = true;
1411
+ lineNodes++;
1412
+
1413
+ // If the part matches the token, we can leave it alone.
1414
+ if (correctPart(token, part)){
1415
+ part.dirty = false;
1416
+ parts.next();
1417
+ }
1418
+ // Otherwise, we have to fix it.
1419
+ else {
1420
+ lineDirty = true;
1421
+ // Insert the correct part.
1422
+ var newPart = tokenPart(token);
1423
+ container.insertBefore(newPart, part);
1424
+ if (active) active(newPart, token, self);
1425
+ var tokensize = token.value.length;
1426
+ var offset = 0;
1427
+ // Eat up parts until the text for this token has been
1428
+ // removed, adjusting the stored selection info (see
1429
+ // select.js) in the process.
1430
+ while (tokensize > 0) {
1431
+ part = parts.get();
1432
+ var partsize = part.currentText.length;
1433
+ select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
1434
+ if (partsize > tokensize){
1435
+ shortenPart(part, tokensize);
1436
+ tokensize = 0;
1437
+ }
1438
+ else {
1439
+ tokensize -= partsize;
1440
+ offset += partsize;
1441
+ parts.remove();
1442
+ }
1443
+ }
1444
+ }
1445
+ }
1446
+ });
1447
+ maybeTouch(from);
1448
+ webkitLastLineHack(this.container);
1449
+
1450
+ // The function returns some status information that is used by
1451
+ // hightlightDirty to determine whether and where it has to
1452
+ // continue.
1453
+ return {node: parts.getNonEmpty(),
1454
+ dirty: lineDirty};
1455
+ }
1456
+ };
1457
+
1458
+ return Editor;
1459
+ })();
1460
+
1461
+ addEventHandler(window, "load", function() {
1462
+ var CodeMirror = window.frameElement.CodeMirror;
1463
+ var e = CodeMirror.editor = new Editor(CodeMirror.options);
1464
+ this.parent.setTimeout(method(CodeMirror, "init"), 0);
1465
+ });