markdown - Version 2.0.0

Version Notes

https://github.com/SchumacherFM/Magento-Markdown

Tons of new features! See the github pages for the version history.

Download this release

Release Info

Developer Cyrill Schumacher
Extension markdown
Version 2.0.0
Comparing to
See all releases


Code changes from version 1.4.2 to 2.0.0

Files changed (36) hide show
  1. app/code/community/SchumacherFM/Markdown/Helper/Data.php +140 -15
  2. app/code/community/SchumacherFM/Markdown/Model/Editor/Config.php +0 -85
  3. app/code/community/SchumacherFM/Markdown/Model/Editor/Observer.php +1 -20
  4. app/code/community/SchumacherFM/Markdown/Model/Markdown/Abstract.php +24 -10
  5. app/code/community/SchumacherFM/Markdown/Model/Markdown/Observer.php +14 -11
  6. app/code/community/SchumacherFM/Markdown/Model/Observer/Adminhtml/Block.php +318 -0
  7. app/code/community/SchumacherFM/Markdown/Model/Observer/Adminhtml/EpicEditor.php +87 -0
  8. app/code/community/SchumacherFM/Markdown/Model/Observer/Adminhtml/LayoutUpdate.php +66 -0
  9. app/code/community/SchumacherFM/Markdown/Model/Observer/AdminhtmlBlock.php +0 -150
  10. app/code/community/SchumacherFM/Markdown/controllers/Adminhtml/MarkdownController.php +82 -6
  11. app/code/community/SchumacherFM/Markdown/etc/adminhtml.xml +3 -3
  12. app/code/community/SchumacherFM/Markdown/etc/config.xml +29 -18
  13. app/code/community/SchumacherFM/Markdown/etc/system.xml +150 -19
  14. app/design/adminhtml/default/default/layout/markdown.xml +18 -9
  15. js/mage/adminhtml/markdown.js +0 -144
  16. js/mage/adminhtml/marked.js +0 -1181
  17. js/markdown/adminhtml/epiceditor.js +1896 -0
  18. js/markdown/adminhtml/filereader.js +446 -0
  19. js/markdown/adminhtml/highlight.pack.js +2 -0
  20. js/markdown/adminhtml/highlight/styles/xcode.css +154 -0
  21. js/markdown/adminhtml/markdown.js +715 -0
  22. js/markdown/adminhtml/marked.js +1165 -0
  23. js/markdown/adminhtml/reMarked.js +636 -0
  24. package.xml +19 -16
  25. skin/adminhtml/default/default/epiceditor/highlight/styles/default.css +135 -0
  26. skin/adminhtml/default/default/epiceditor/highlight/styles/github.css +127 -0
  27. skin/adminhtml/default/default/epiceditor/highlight/styles/googlecode.css +144 -0
  28. skin/adminhtml/default/default/epiceditor/themes/base/epiceditor.css +64 -0
  29. skin/adminhtml/default/default/epiceditor/themes/editor/epic-dark.css +15 -0
  30. skin/adminhtml/default/default/epiceditor/themes/editor/epic-light.css +14 -0
  31. skin/adminhtml/default/default/epiceditor/themes/preview/bartik.css +167 -0
  32. skin/adminhtml/default/default/epiceditor/themes/preview/github.css +368 -0
  33. skin/adminhtml/default/default/epiceditor/themes/preview/githubNxcode.css +533 -0
  34. skin/adminhtml/default/default/epiceditor/themes/preview/preview-dark.css +143 -0
  35. skin/adminhtml/default/default/markdown.css +0 -310
  36. skin/adminhtml/default/default/markdown/mdm.css +6 -0
app/code/community/SchumacherFM/Markdown/Helper/Data.php CHANGED
@@ -7,7 +7,6 @@
7
  */
8
  class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
9
  {
10
- const URL_MD_SYNTAX = 'http://daringfireball.net/projects/markdown/syntax';
11
  const URL_MD_EXTRA_SYNTAX = 'http://michelf.ca/projects/php-markdown/extra/';
12
 
13
  /**
@@ -20,13 +19,21 @@ class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
20
  *
21
  * @return string
22
  */
23
- public function render($text, array $options = null)
24
  {
25
  return Mage::getSingleton('markdown/markdown_render')
26
  ->setOptions($options)
27
  ->renderMarkdown($text);
28
  }
29
 
 
 
 
 
 
 
 
 
30
  /**
31
  * @param bool $encoded
32
  *
@@ -34,7 +41,10 @@ class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
34
  */
35
  public function getDetectionTag($encoded = FALSE)
36
  {
37
- $tag = Mage::getStoreConfig('schumacherfm/markdown/detection_tag');
 
 
 
38
  return $encoded ? rawurlencode($tag) : $tag;
39
  }
40
 
@@ -47,7 +57,7 @@ class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
47
  */
48
  public function isDisabled()
49
  {
50
- return !(boolean)Mage::getStoreConfig('schumacherfm/markdown/enable');
51
  }
52
 
53
  /**
@@ -55,9 +65,9 @@ class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
55
  *
56
  * @return bool
57
  */
58
- public function isMarkdownExtra($type = null)
59
  {
60
- return (boolean)Mage::getStoreConfig('schumacherfm/markdown/md_extra' . (!empty($type) ? '_' . $type : ''));
61
  }
62
 
63
  /**
@@ -70,7 +80,7 @@ class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
70
  */
71
  public function getTransactionalEmailCSS()
72
  {
73
- $file = Mage::getStoreConfig('schumacherfm/markdown/te_md_css');
74
  if (empty($file)) {
75
  return '';
76
  }
@@ -85,24 +95,139 @@ class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
85
  }
86
 
87
  /**
 
 
88
  * @return string
89
  */
90
- public function getAdminRenderUrl()
91
  {
92
- return Mage::helper("adminhtml")->getUrl('*/markdown/render');
93
  }
94
 
95
  /**
96
- * @param string $htmlId
97
  *
98
  * @return string
99
  */
100
- public function getRenderMarkdownJs($htmlId)
101
  {
102
- $args = array('\'' . $htmlId . '\'', '\'' . Mage::helper('markdown')->getDetectionTag(TRUE) . '\'');
103
- if ($this->isMarkdownExtra()) {
104
- $args[] = '\'' . $this->getAdminRenderUrl() . '\'';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  }
106
- return 'renderMarkdown(' . implode(',', $args) . ');';
107
  }
108
  }
7
  */
8
  class SchumacherFM_Markdown_Helper_Data extends Mage_Core_Helper_Abstract
9
  {
 
10
  const URL_MD_EXTRA_SYNTAX = 'http://michelf.ca/projects/php-markdown/extra/';
11
 
12
  /**
19
  *
20
  * @return string
21
  */
22
+ public function render($text, array $options = NULL)
23
  {
24
  return Mage::getSingleton('markdown/markdown_render')
25
  ->setOptions($options)
26
  ->renderMarkdown($text);
27
  }
28
 
29
+ /**
30
+ * @return mixed|string
31
+ */
32
+ public function getCheatSheetUrl()
33
+ {
34
+ return Mage::getStoreConfig('markdown/markdown/cheatsheet');
35
+ }
36
+
37
  /**
38
  * @param bool $encoded
39
  *
41
  */
42
  public function getDetectionTag($encoded = FALSE)
43
  {
44
+ $tag = trim(Mage::getStoreConfig('markdown/markdown/detection_tag'));
45
+ if (empty($tag) === TRUE) {
46
+ return '';
47
+ }
48
  return $encoded ? rawurlencode($tag) : $tag;
49
  }
50
 
57
  */
58
  public function isDisabled()
59
  {
60
+ return !(boolean)Mage::getStoreConfig('markdown/markdown/enable');
61
  }
62
 
63
  /**
65
  *
66
  * @return bool
67
  */
68
+ public function isMarkdownExtra($type = NULL)
69
  {
70
+ return (boolean)Mage::getStoreConfig('markdown/markdown_extra/enable' . (!empty($type) ? '_' . $type : ''));
71
  }
72
 
73
  /**
80
  */
81
  public function getTransactionalEmailCSS()
82
  {
83
+ $file = Mage::getStoreConfig('markdown/markdown_extra/te_md_css');
84
  if (empty($file)) {
85
  return '';
86
  }
95
  }
96
 
97
  /**
98
+ * @param array $params
99
+ *
100
  * @return string
101
  */
102
+ public function getAdminRenderUrl(array $params = NULL)
103
  {
104
+ return Mage::helper('adminhtml')->getUrl('adminhtml/markdown/render', $params);
105
  }
106
 
107
  /**
108
+ * @param array $params
109
  *
110
  * @return string
111
  */
112
+ public function getAdminFileUploadUrl(array $params = NULL)
113
  {
114
+ return Mage::helper('adminhtml')->getUrl('adminhtml/markdown/fileUpload', $params);
115
+ }
116
+
117
+ /**
118
+ * @param array $params
119
+ *
120
+ * @return string
121
+ */
122
+ public function getAdminEnableUrl(array $params = NULL)
123
+ {
124
+ return Mage::helper('adminhtml')->getUrl('adminhtml/markdown/enable', $params);
125
+ }
126
+
127
+ /**
128
+ * @return bool
129
+ */
130
+ public function isEpicEditorEnabled()
131
+ {
132
+ return (boolean)Mage::getStoreConfig('markdown/epiceditor/enable');
133
+ }
134
+
135
+ /**
136
+ * @return bool
137
+ */
138
+ public function isEpicEditorLoadOnClick()
139
+ {
140
+ return (boolean)Mage::getStoreConfig('markdown/epiceditor/load_on_click_textarea');
141
+ }
142
+
143
+ /**
144
+ * if json is invalid returns false
145
+ *
146
+ * @return string|boolean
147
+ */
148
+ public function getEpicEditorConfig()
149
+ {
150
+ $config = $this->_getJsonConfig('epiceditor');
151
+ $config = FALSE !== $config ? json_decode($config, TRUE) : array();
152
+ $config['basePath'] = Mage::getBaseUrl('skin') . 'adminhtml/default/default/epiceditor/';
153
+ return json_encode($config);
154
+ }
155
+
156
+ /**
157
+ * if json is invalid returns false
158
+ *
159
+ * @param string $type
160
+ *
161
+ * @return bool|string
162
+ */
163
+ protected function _getJsonConfig($type)
164
+ {
165
+ $config = trim(Mage::getStoreConfig('markdown/' . $type . '/config'));
166
+ if (empty($config)) {
167
+ return FALSE;
168
+ }
169
+ $decoded = json_decode($config);
170
+ return $decoded instanceof stdClass ? rawurlencode($config) : FALSE;
171
+ }
172
+
173
+ /**
174
+ * @return bool
175
+ */
176
+ public function isReMarkedEnabled()
177
+ {
178
+ return (boolean)Mage::getStoreConfig('markdown/remarked/enable');
179
+ }
180
+
181
+ /**
182
+ * if json is invalid returns false
183
+ *
184
+ * @return string|boolean
185
+ */
186
+ public function getReMarkedConfig()
187
+ {
188
+ return $this->_getJsonConfig('remarked');
189
+ }
190
+
191
+ /**
192
+ * @param $imageUrl
193
+ *
194
+ * @return string
195
+ */
196
+ public function getTemplateMediaUrl($imageUrl)
197
+ {
198
+ return sprintf('{{media url="%s"}}', $imageUrl);
199
+ }
200
+
201
+ /**
202
+ * @param $content
203
+ *
204
+ * @return mixed
205
+ */
206
+ public function renderTemplateMediaUrl($content)
207
+ {
208
+ return preg_replace('~\{\{media\s+url="([^"]+)"\s*\}\}~i', Mage::getBaseUrl('media') . '\\1', $content);
209
+ }
210
+
211
+ /**
212
+ * @return array
213
+ */
214
+ public function getAllowedLayoutHandles()
215
+ {
216
+ $handles = array(
217
+ 'editor' => 1,
218
+ 'adminhtml_cms_block_edit' => 1,
219
+ 'adminhtml_cms_page_edit' => 1,
220
+ 'adminhtml_system_email_template_edit' => 1,
221
+ 'adminhtml_catalog_product_edit' => 1,
222
+ 'adminhtml_catalog_category_edit' => 1,
223
+ );
224
+
225
+ $customHandles = trim((string)Mage::getStoreConfig('markdown/markdown/custom_layout_handles'));
226
+ if (!empty($customHandles)) {
227
+ $customHandles = preg_split('~\s+~', $customHandles, -1, PREG_SPLIT_NO_EMPTY);
228
+ $customHandles = array_flip($customHandles);
229
+ $handles = array_merge($handles, $customHandles);
230
  }
231
+ return $handles;
232
  }
233
  }
app/code/community/SchumacherFM/Markdown/Model/Editor/Config.php DELETED
@@ -1,85 +0,0 @@
1
- <?php
2
- /**
3
- * @category SchumacherFM_Markdown
4
- * @package Model
5
- * @author Cyrill at Schumacher dot fm / @SchumacherFM
6
- * @copyright Copyright (c)
7
- */
8
- class SchumacherFM_Markdown_Model_Editor_Config
9
- {
10
- /**
11
- * Prepare variable wysiwyg config
12
- *
13
- * @param Varien_Object $config
14
- *
15
- * @return array
16
- */
17
- public function getWysiwygPluginSettings($config)
18
- {
19
- $variableConfig = array();
20
- $onclickPreview = array(
21
- 'search' => array('html_id'),
22
- 'subject' => Mage::helper('markdown')->getRenderMarkdownJs('{{html_id}}'),
23
- );
24
- $onclickSyntax = array(
25
- 'search' => array('html_id'),
26
- 'subject' => 'mdExternalUrl(\'' . SchumacherFM_Markdown_Helper_Data::URL_MD_SYNTAX . '\',\'{{html_id}}\');'
27
- );
28
- $variableWysiwygPlugin = array(
29
- array(
30
- 'name' => 'markdownToggle',
31
- 'src' => '',
32
- 'options' => array(
33
- 'title' => Mage::helper('markdown')->__('[M↓] enable'),
34
- 'url' => '',
35
- 'onclick' => array(
36
- 'search' => array('html_id'),
37
- 'subject' => 'toggleMarkdown(\'' . Mage::helper('markdown')->getDetectionTag(TRUE) . '\',\'{{html_id}}\');'
38
- ),
39
- 'class' => 'plugin'
40
- )
41
- ),
42
- array(
43
- 'name' => 'markdown',
44
- 'src' => '',
45
- 'options' => array(
46
- 'title' => Mage::helper('markdown')->__('[M↓] Preview'),
47
- 'url' => '',
48
- 'onclick' => $onclickPreview,
49
- 'class' => 'plugin'
50
- )
51
- ),
52
- array(
53
- 'name' => 'mdExternalUrl',
54
- 'src' => '',
55
- 'options' => array(
56
- 'title' => Mage::helper('markdown')->__('[M↓] Syntax'),
57
- 'url' => '',
58
- 'onclick' => $onclickSyntax,
59
- 'class' => 'plugin'
60
- )
61
- ),
62
- );
63
-
64
- if (Mage::helper('markdown')->isMarkdownExtra()) {
65
- $variableWysiwygPlugin[] = array(
66
- 'name' => 'markdownextrasyntax',
67
- 'src' => '',
68
- 'options' => array(
69
- 'title' => Mage::helper('markdown')->__('[M↓] Extra Syntax'),
70
- 'url' => '',
71
- 'onclick' => array(
72
- 'search' => array('html_id'),
73
- 'subject' => 'mdExternalUrl(\'' . SchumacherFM_Markdown_Helper_Data::URL_MD_EXTRA_SYNTAX . '\',\'{{html_id}}\');'
74
- ),
75
- 'class' => 'plugin'
76
- )
77
- );
78
- }
79
-
80
- $configPlugins = $config->getData('plugins');
81
- $variableConfig['plugins'] = array_merge($configPlugins, $variableWysiwygPlugin);
82
- return $variableConfig;
83
- }
84
-
85
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/code/community/SchumacherFM/Markdown/Model/Editor/Observer.php CHANGED
@@ -7,25 +7,6 @@
7
  */
8
  class SchumacherFM_Markdown_Model_Editor_Observer
9
  {
10
- /**
11
- * Add markdown wysiwyg plugin config
12
- *
13
- * @param Varien_Event_Observer $observer
14
- *
15
- * @return SchumacherFM_Markdown_Model_Editor_Observer
16
- */
17
- public function prepareWysiwygPluginConfig(Varien_Event_Observer $observer)
18
- {
19
- if (Mage::helper('markdown')->isDisabled()) {
20
- return null;
21
- }
22
-
23
- $config = $observer->getEvent()->getConfig();
24
- $settings = Mage::getModel('markdown/editor_config')->getWysiwygPluginSettings($config);
25
- $config->addData($settings);
26
- return $this;
27
- }
28
-
29
  /**
30
  * is Markdown is enabled then disable completely the wysiwyg editor
31
  *
@@ -43,4 +24,4 @@ class SchumacherFM_Markdown_Model_Editor_Observer
43
  $configurationModel->saveConfig('cms/wysiwyg/enabled', $isEnabled ? 'disabled' : 'enabled');
44
  }
45
  }
46
- }
7
  */
8
  class SchumacherFM_Markdown_Model_Editor_Observer
9
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  /**
11
  * is Markdown is enabled then disable completely the wysiwyg editor
12
  *
24
  $configurationModel->saveConfig('cms/wysiwyg/enabled', $isEnabled ? 'disabled' : 'enabled');
25
  }
26
  }
27
+ }
app/code/community/SchumacherFM/Markdown/Model/Markdown/Abstract.php CHANGED
@@ -23,10 +23,11 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
23
  /**
24
  * @var SchumacherFM_Markdown_Model_Michelf_Markdown
25
  */
26
- protected $_renderer = null;
27
 
28
  protected $_options = array(
29
  'force' => FALSE,
 
30
  'protectMagento' => TRUE,
31
  );
32
 
@@ -44,7 +45,7 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
44
  */
45
  public final function getRenderer()
46
  {
47
- if ($this->_renderer !== null) {
48
  return $this->_renderer;
49
  }
50
 
@@ -60,7 +61,7 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
60
  */
61
  protected function _getIsExtraRenderer()
62
  {
63
- return Mage::helper('markdown')->isMarkdownExtra();
64
  }
65
 
66
  /**
@@ -80,9 +81,9 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
80
  *
81
  * @return $this
82
  */
83
- public function setOptions(array $options = null)
84
  {
85
- $this->_options = $options;
86
  return $this;
87
  }
88
 
@@ -105,9 +106,10 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
105
  */
106
  protected function _renderMarkdown($text)
107
  {
 
108
  $force = isset($this->_options['force']) && $this->_options['force'] === TRUE;
109
  $protectMagento = isset($this->_options['protectMagento']) && $this->_options['protectMagento'] === TRUE;
110
- $this->_currentRenderedText = $text; // @todo optimize
111
 
112
  if (!$this->_isMarkdown() && $force === FALSE) {
113
  return $this->_currentRenderedText;
@@ -123,6 +125,7 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
123
  if ($protectMagento === TRUE) {
124
  $this->_preserveMagentoVariablesDecode();
125
  }
 
126
  return $this->_currentRenderedText;
127
  }
128
 
@@ -133,7 +136,11 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
133
  */
134
  protected function _removeMarkdownTag()
135
  {
136
- $this->_currentRenderedText = str_replace($this->_tag, '', $this->_currentRenderedText);
 
 
 
 
137
  return $this;
138
  }
139
 
@@ -178,8 +185,16 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
178
  */
179
  private function _isMarkdown()
180
  {
181
- $flag = !empty($this->_currentRenderedText);
182
- return $flag === TRUE && strpos($this->_currentRenderedText, $this->_tag) !== FALSE;
 
 
 
 
 
 
 
 
183
  }
184
 
185
  /**
@@ -191,5 +206,4 @@ abstract class SchumacherFM_Markdown_Model_Markdown_Abstract
191
  {
192
  return strpos($text, $this->_tag) !== FALSE;
193
  }
194
-
195
  }
23
  /**
24
  * @var SchumacherFM_Markdown_Model_Michelf_Markdown
25
  */
26
+ protected $_renderer = NULL;
27
 
28
  protected $_options = array(
29
  'force' => FALSE,
30
+ 'extra' => FALSE,
31
  'protectMagento' => TRUE,
32
  );
33
 
45
  */
46
  public final function getRenderer()
47
  {
48
+ if ($this->_renderer !== NULL) {
49
  return $this->_renderer;
50
  }
51
 
61
  */
62
  protected function _getIsExtraRenderer()
63
  {
64
+ return Mage::helper('markdown')->isMarkdownExtra() || $this->_options['extra'] === TRUE;
65
  }
66
 
67
  /**
81
  *
82
  * @return $this
83
  */
84
+ public function setOptions(array $options = NULL)
85
  {
86
+ $this->_options = array_merge($this->_options, $options);
87
  return $this;
88
  }
89
 
106
  */
107
  protected function _renderMarkdown($text)
108
  {
109
+ Varien_Profiler::start('renderMarkdown');
110
  $force = isset($this->_options['force']) && $this->_options['force'] === TRUE;
111
  $protectMagento = isset($this->_options['protectMagento']) && $this->_options['protectMagento'] === TRUE;
112
+ $this->_currentRenderedText = $text;
113
 
114
  if (!$this->_isMarkdown() && $force === FALSE) {
115
  return $this->_currentRenderedText;
125
  if ($protectMagento === TRUE) {
126
  $this->_preserveMagentoVariablesDecode();
127
  }
128
+ Varien_Profiler::stop('renderMarkdown');
129
  return $this->_currentRenderedText;
130
  }
131
 
136
  */
137
  protected function _removeMarkdownTag()
138
  {
139
+ if (empty($this->_tag) === TRUE) {
140
+ return $this;
141
+ }
142
+
143
+ $this->_currentRenderedText = trim(str_replace($this->_tag, '', $this->_currentRenderedText));
144
  return $this;
145
  }
146
 
185
  */
186
  private function _isMarkdown()
187
  {
188
+ if (empty($this->_currentRenderedText) === TRUE) {
189
+ return FALSE;
190
+ }
191
+ if (empty($this->_tag) === TRUE) {
192
+ return TRUE;
193
+ }
194
+ if (strpos($this->_currentRenderedText, $this->_tag) === FALSE) {
195
+ return FALSE;
196
+ }
197
+ return TRUE;
198
  }
199
 
200
  /**
206
  {
207
  return strpos($text, $this->_tag) !== FALSE;
208
  }
 
209
  }
app/code/community/SchumacherFM/Markdown/Model/Markdown/Observer.php CHANGED
@@ -10,7 +10,7 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
10
  /**
11
  * @var null
12
  */
13
- protected $_currentObserverMethod = null;
14
 
15
  /**
16
  * @var array
@@ -27,7 +27,7 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
27
  {
28
  $isset = isset($this->_mdExtraUsage[$this->_currentObserverMethod]);
29
  if (!$isset) {
30
- return null;
31
  }
32
 
33
  return Mage::helper('markdown')->isMarkdownExtra($this->_mdExtraUsage[$this->_currentObserverMethod]);
@@ -40,7 +40,7 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
40
  {
41
  $globalExtra = parent::_getIsExtraRenderer();
42
  $_observerMdExtraUsage = $this->_isObserverMdExtraUsage();
43
- if ($_observerMdExtraUsage === null) {
44
  return $globalExtra;
45
  }
46
  return $_observerMdExtraUsage;
@@ -55,17 +55,22 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
55
  {
56
  $this->_currentObserverMethod = __FUNCTION__;
57
  if ($this->_isDisabled) {
58
- return null;
59
  }
60
 
61
  $object = $observer->getEvent()->getObject();
62
  if (!$object instanceof Mage_Core_Model_Email_Template) {
63
- return null;
64
  }
65
 
66
  $template = $object->getData('template_text');
67
 
68
  if ($this->isMarkdown($template)) {
 
 
 
 
 
69
  $object->setData('template_text', $this->_renderMarkdown($template));
70
  $css = Mage::helper('markdown')->getTransactionalEmailCSS();
71
  $object->setData('template_styles', $css);
@@ -81,13 +86,13 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
81
  {
82
  $this->_currentObserverMethod = __FUNCTION__;
83
  if ($this->_isDisabled) {
84
- return null;
85
  }
86
 
87
  /** @var Mage_Cms_Model_Page $page */
88
  $page = $observer->getEvent()->getPage();
89
  if (!$page instanceof Mage_Cms_Model_Page) {
90
- return null;
91
  }
92
  $content = $this->_renderMarkdown($page->getContent());
93
  $page->setContent($content);
@@ -104,14 +109,14 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
104
  {
105
  $this->_currentObserverMethod = __FUNCTION__;
106
  if ($this->_isDisabled) {
107
- return null;
108
  }
109
 
110
  /** @var Mage_Cms_Block_Block $page */
111
  $block = $observer->getEvent()->getBlock();
112
 
113
  if (!$this->_isAllowedBlock($block)) {
114
- return null;
115
  }
116
 
117
  /** @var Varien_Object $transport */
@@ -128,7 +133,6 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
128
  ));
129
  $html = $transport->getHtml();
130
  $transport->setHtml($this->_renderMarkdown($html));
131
-
132
  }
133
 
134
  /**
@@ -140,5 +144,4 @@ class SchumacherFM_Markdown_Model_Markdown_Observer extends SchumacherFM_Markdow
140
  {
141
  return $block instanceof Mage_Cms_Block_Block || $block instanceof Mage_Cms_Block_Widget_Block;
142
  }
143
-
144
  }
10
  /**
11
  * @var null
12
  */
13
+ protected $_currentObserverMethod = NULL;
14
 
15
  /**
16
  * @var array
27
  {
28
  $isset = isset($this->_mdExtraUsage[$this->_currentObserverMethod]);
29
  if (!$isset) {
30
+ return NULL;
31
  }
32
 
33
  return Mage::helper('markdown')->isMarkdownExtra($this->_mdExtraUsage[$this->_currentObserverMethod]);
40
  {
41
  $globalExtra = parent::_getIsExtraRenderer();
42
  $_observerMdExtraUsage = $this->_isObserverMdExtraUsage();
43
+ if ($_observerMdExtraUsage === NULL) {
44
  return $globalExtra;
45
  }
46
  return $_observerMdExtraUsage;
55
  {
56
  $this->_currentObserverMethod = __FUNCTION__;
57
  if ($this->_isDisabled) {
58
+ return NULL;
59
  }
60
 
61
  $object = $observer->getEvent()->getObject();
62
  if (!$object instanceof Mage_Core_Model_Email_Template) {
63
+ return NULL;
64
  }
65
 
66
  $template = $object->getData('template_text');
67
 
68
  if ($this->isMarkdown($template)) {
69
+
70
+ $this->setOptions(array(
71
+ 'extra' => Mage::helper('markdown')->isMarkdownExtra('email')
72
+ ));
73
+
74
  $object->setData('template_text', $this->_renderMarkdown($template));
75
  $css = Mage::helper('markdown')->getTransactionalEmailCSS();
76
  $object->setData('template_styles', $css);
86
  {
87
  $this->_currentObserverMethod = __FUNCTION__;
88
  if ($this->_isDisabled) {
89
+ return NULL;
90
  }
91
 
92
  /** @var Mage_Cms_Model_Page $page */
93
  $page = $observer->getEvent()->getPage();
94
  if (!$page instanceof Mage_Cms_Model_Page) {
95
+ return NULL;
96
  }
97
  $content = $this->_renderMarkdown($page->getContent());
98
  $page->setContent($content);
109
  {
110
  $this->_currentObserverMethod = __FUNCTION__;
111
  if ($this->_isDisabled) {
112
+ return NULL;
113
  }
114
 
115
  /** @var Mage_Cms_Block_Block $page */
116
  $block = $observer->getEvent()->getBlock();
117
 
118
  if (!$this->_isAllowedBlock($block)) {
119
+ return NULL;
120
  }
121
 
122
  /** @var Varien_Object $transport */
133
  ));
134
  $html = $transport->getHtml();
135
  $transport->setHtml($this->_renderMarkdown($html));
 
136
  }
137
 
138
  /**
144
  {
145
  return $block instanceof Mage_Cms_Block_Block || $block instanceof Mage_Cms_Block_Widget_Block;
146
  }
 
147
  }
app/code/community/SchumacherFM/Markdown/Model/Observer/Adminhtml/Block.php ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @category SchumacherFM_Markdown
4
+ * @package Observer
5
+ * @author Cyrill at Schumacher dot fm / @SchumacherFM
6
+ * @copyright Copyright (c)
7
+ */
8
+ class SchumacherFM_Markdown_Model_Observer_Adminhtml_Block
9
+ {
10
+
11
+ /**
12
+ * @var bool
13
+ */
14
+ protected $_configInserted = FALSE;
15
+
16
+ /**
17
+ * @var array
18
+ */
19
+ protected $_afterElementHtml = array();
20
+
21
+ /**
22
+ * adminhtml_block_html_before
23
+ *
24
+ * @param Varien_Event_Observer $observer
25
+ *
26
+ * @return null
27
+ */
28
+ public function alterTextareaBlockTemplate(Varien_Event_Observer $observer)
29
+ {
30
+ if (Mage::helper('markdown')->isDisabled()) {
31
+ return NULL;
32
+ }
33
+
34
+ /** @var $block Mage_Adminhtml_Block_Template */
35
+ $block = $observer->getEvent()->getBlock();
36
+
37
+ $isWidgetElement = $block instanceof Mage_Adminhtml_Block_Widget_Form_Renderer_Fieldset_Element;
38
+ $isCatalogElement = $block instanceof Mage_Adminhtml_Block_Catalog_Form_Renderer_Fieldset_Element;
39
+
40
+ /**
41
+ * main reason for this layout handle thing is to avoid loading of lot of unused JS/CSS ...
42
+ */
43
+ $isLayoutHandleAllowed = Mage::getSingleton('markdown/observer_adminhtml_layoutUpdate')->isAllowed();
44
+
45
+ if ($isWidgetElement || $isCatalogElement) {
46
+ /** @var Varien_Data_Form_Element_Abstract $element */
47
+ $element = $block->getElement();
48
+
49
+ $_isElementEditor = $this->_isElementEditor($element);
50
+ $_isCatalogElementAllowed = $this->_isCatalogElementAllowed($element);
51
+ $_isEmailTemplateElementAllowed = $this->_isEmailTemplateElementAllowed($element);
52
+
53
+ if ($_isElementEditor || $_isCatalogElementAllowed || $_isEmailTemplateElementAllowed) {
54
+ $method = $isLayoutHandleAllowed ? '_integrate' : '_addMarkdownHint';
55
+ $this->$method($element);
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @param Varien_Data_Form_Element_Abstract $element
62
+ *
63
+ * @return $this
64
+ */
65
+ protected function _addMarkdownHint(Varien_Data_Form_Element_Abstract $element)
66
+ {
67
+ $element->setData('after_element_html', '<small>' .
68
+ Mage::helper('markdown')->__('Markdown feature may be available here!')
69
+ . '</small>' . $element->getData('after_element_html'));
70
+
71
+ /* not sure if useful ...
72
+ $params = array(
73
+ 'layoutHandle' => '@todo',
74
+ 'returnUrl' => Mage::app()->getRequest()->getRequestUri(),
75
+ );
76
+ $url = Mage::helper('markdown')->getAdminEnableUrl($params);
77
+ $element->setData('after_element_html', '<small><a href="' . $url . '">' .
78
+ Mage::helper('markdown')->__('Click to add Markdown feature!')
79
+ . '</a></small>');
80
+ */
81
+ return $this;
82
+ }
83
+
84
+ /**
85
+ * @param Varien_Data_Form_Element_Abstract $element
86
+ *
87
+ * @return $this
88
+ */
89
+ protected function _integrate(Varien_Data_Form_Element_Abstract $element)
90
+ {
91
+ $uniqueEntityId = $this->_getUniqueEntityId($element);
92
+ $idPrefix = $element->getForm()->getHtmlIdPrefix();
93
+ $element->setId(str_replace($idPrefix, '', $element->getHtmlId()) . $uniqueEntityId);
94
+
95
+ // adds to every Element the MD buttons at the bottom of the textarea
96
+ return $this->_getMarkdownButtons($element)->_addEpicEditorHtml($element)->_mergeAfterElementHtml($element);
97
+ }
98
+
99
+ /**
100
+ * @param Varien_Data_Form_Element_Abstract $element
101
+ *
102
+ * @return $this
103
+ */
104
+ protected function _mergeAfterElementHtml(Varien_Data_Form_Element_Abstract $element)
105
+ {
106
+ $this->_afterElementHtml[90] = $element->getData('after_element_html');
107
+
108
+ $config = array();
109
+ $config['dt'] = Mage::helper('markdown')->getDetectionTag(TRUE);
110
+ $config['fuu'] = Mage::helper('markdown')->getAdminFileUploadUrl(); // file upload url
111
+
112
+ /**
113
+ * when rendering via marked.js include that place holder ... if rendere via PHP replace {{media url...}}
114
+ * with the real image.
115
+ */
116
+ $config['phi'] = Mage::getBaseUrl('media');
117
+
118
+ if ($this->_isMarkdownExtra($element)) {
119
+ $config['eru'] = Mage::helper('markdown')->getAdminRenderUrl(array('markdownExtra' => 1)); // extra renderer url
120
+ }
121
+
122
+ $config['eeloc'] = Mage::helper('markdown')->isEpicEditorLoadOnClick();
123
+
124
+ if (Mage::helper('markdown')->isReMarkedEnabled() === TRUE) {
125
+ $config['rmc'] = Mage::helper('markdown')->getReMarkedConfig();
126
+ }
127
+
128
+ if ($this->_configInserted === FALSE) {
129
+ $this->_afterElementHtml[1000] = '<div id="markdownGlobalConfig" data-config=\'' .
130
+ Zend_Json_Encoder::encode($config)
131
+ . '\' style="display:none;"></div>';
132
+ $this->_configInserted = TRUE;
133
+ }
134
+
135
+ ksort($this->_afterElementHtml);
136
+ $element->setData('after_element_html', implode(' ', $this->_afterElementHtml));
137
+ $this->_afterElementHtml = array();
138
+ $element->addClass('initFileReader');
139
+ return $this;
140
+ }
141
+
142
+ /**
143
+ * @param Varien_Data_Form_Element_Abstract $element
144
+ *
145
+ * @return $this
146
+ */
147
+ protected function _addEpicEditorHtml(Varien_Data_Form_Element_Abstract $element)
148
+ {
149
+ if (!Mage::helper('markdown')->isEpicEditorEnabled()) {
150
+ return $this;
151
+ }
152
+
153
+ $id = $element->getHtmlId();
154
+
155
+ $element->addClass('initEpicEditor');
156
+ $this->_afterElementHtml[100] = '<div id="epiceditor_EE_' . $id . '"' . $this->_getEpicEditorHtmlConfig($element) . '></div>';
157
+ return $this;
158
+ }
159
+
160
+ /**
161
+ * @param Varien_Data_Form_Element_Abstract $element
162
+ *
163
+ * @return string
164
+ */
165
+ protected function _getEpicEditorHtmlConfig(Varien_Data_Form_Element_Abstract $element)
166
+ {
167
+ $config = Mage::helper('markdown')->getEpicEditorConfig();
168
+ $dataConfig = '';
169
+ if ($config) {
170
+ $dataConfig = ' data-config=\'' . $config . '\'';
171
+ }
172
+ return $dataConfig;
173
+ }
174
+
175
+ /**
176
+ * this is mainly a work around for the category section because fields will
177
+ * be there loaded via ajax with the same id each time ... and that confuses me and
178
+ * Epic Editor 8-)
179
+ *
180
+ * @param Varien_Data_Form_Element_Abstract $parentElement
181
+ *
182
+ * @return string
183
+ */
184
+ protected function _getUniqueEntityId(Varien_Data_Form_Element_Abstract $parentElement)
185
+ {
186
+ /** @var Varien_Data_Form_Element_Collection $elements */
187
+ $elements = $parentElement->getForm()->getElements();
188
+
189
+ $idString = '';
190
+ foreach ($elements as $fieldSet) {
191
+ /** @var Varien_Data_Form_Element_Fieldset $fieldSet */
192
+ $sortedElements = $fieldSet->getSortedElements();
193
+ foreach ($sortedElements as $sortedElement) {
194
+ /** @var $sortedElement Varien_Data_Form_Element_Abstract */
195
+ if (stristr($sortedElement->getName(), 'id') !== FALSE) {
196
+ $idString .= $sortedElement->getValue();
197
+ }
198
+ }
199
+ }
200
+
201
+ // prevent trouble with strange values due to localStorage ...
202
+ $secretKey = Mage::getModel('adminhtml/url')->getSecretKey();
203
+ $path = Mage::app()->getRequest()->getRequestUri();
204
+ $idString .= '_' . md5(str_replace($secretKey, '', $path));
205
+
206
+ // we could also use here md5 but it want to see the values.
207
+ return preg_replace('~[^a-z0-9_\-]+~i', '', $idString);
208
+ }
209
+
210
+ /**
211
+ * @param Varien_Data_Form_Element_Abstract $element
212
+ *
213
+ * @return bool
214
+ */
215
+ protected function _isEmailTemplateElementAllowed(Varien_Data_Form_Element_Abstract $element)
216
+ {
217
+ $trueOne = $element instanceof Varien_Data_Form_Element_Textarea;
218
+ $trueTwo = stristr($element->getHtmlId(), 'template_text') !== FALSE;
219
+ return $trueOne && $trueTwo;
220
+ }
221
+
222
+ /**
223
+ * @param Varien_Data_Form_Element_Abstract $element
224
+ *
225
+ * @return bool
226
+ */
227
+ protected function _isCatalogElementAllowed(Varien_Data_Form_Element_Abstract $element)
228
+ {
229
+ $isTextArea = $element instanceof Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg;
230
+ $isDescription = stristr($element->getName(), 'description') !== FALSE && stristr($element->getName(), 'meta') === FALSE;
231
+ return $isDescription && $isTextArea;
232
+ }
233
+
234
+ /**
235
+ * @param Varien_Data_Form_Element_Abstract $element
236
+ *
237
+ * @return bool
238
+ */
239
+ protected function _isElementEditor(Varien_Data_Form_Element_Abstract $element)
240
+ {
241
+ return $element instanceof Varien_Data_Form_Element_Editor;
242
+ }
243
+
244
+ /**
245
+ * checks if md extra is enabled
246
+ *
247
+ * @param Varien_Data_Form_Element_Abstract $element
248
+ *
249
+ * @return bool
250
+ */
251
+ protected function _isMarkdownExtra(Varien_Data_Form_Element_Abstract $element)
252
+ {
253
+ $_isEmailTemplateElementAllowed = $this->_isEmailTemplateElementAllowed($element);
254
+
255
+ return Mage::helper('markdown')->isMarkdownExtra() ||
256
+ (Mage::helper('markdown')->isMarkdownExtra('email') && $_isEmailTemplateElementAllowed);
257
+ }
258
+
259
+ /**
260
+ * @param Varien_Data_Form_Element_Abstract $element
261
+ */
262
+ protected function _getMarkdownButtons(Varien_Data_Form_Element_Abstract $element)
263
+ {
264
+ $htmlId = $element->getHtmlId();
265
+
266
+ if (Mage::helper('markdown')->getDetectionTag() !== '') {
267
+ $this->_afterElementHtml[200] = Mage::getSingleton('core/layout')
268
+ ->createBlock('adminhtml/widget_button', '', array(
269
+ 'label' => Mage::helper('markdown')->__('[M↓] enable'),
270
+ 'type' => 'button',
271
+ 'onclick' => 'toggleMarkdown(\'' . $htmlId . '\');'
272
+ ))->toHtml();
273
+ }
274
+
275
+ $this->_afterElementHtml[210] = Mage::getSingleton('core/layout')
276
+ ->createBlock('adminhtml/widget_button', '', array(
277
+ 'label' => Mage::helper('markdown')->__('[M↓] Source'),
278
+ 'type' => 'button',
279
+ 'title' => Mage::helper('markdown')->__('View generated HTML source code'),
280
+ 'onclick' => 'toggleMarkdownSource(this,\'' . $htmlId . '\');'
281
+ ))->toHtml();
282
+
283
+ $this->_afterElementHtml[300] = Mage::getSingleton('core/layout')
284
+ ->createBlock('adminhtml/widget_button', '', array(
285
+ 'label' => Mage::helper('markdown')->__('[M↓] Syntax'),
286
+ 'type' => 'button',
287
+ 'onclick' => 'mdExternalUrl(\'' . Mage::helper('markdown')->getCheatSheetUrl() . '\');'
288
+ ))->toHtml();
289
+
290
+ if ($this->_isMarkdownExtra($element)) {
291
+ $this->_afterElementHtml[400] = Mage::getSingleton('core/layout')
292
+ ->createBlock('adminhtml/widget_button', '', array(
293
+ 'label' => Mage::helper('markdown')->__('[M↓] Extra Syntax'),
294
+ 'type' => 'button',
295
+ 'onclick' => 'mdExternalUrl(\'' . SchumacherFM_Markdown_Helper_Data::URL_MD_EXTRA_SYNTAX . '\');'
296
+ ))->toHtml();
297
+ }
298
+
299
+ if (Mage::helper('markdown')->isEpicEditorEnabled()) {
300
+ $this->_afterElementHtml[500] = Mage::getSingleton('core/layout')
301
+ ->createBlock('adminhtml/widget_button', '', array(
302
+ 'label' => Mage::helper('markdown')->__('EpicEditor'),
303
+ 'type' => 'button',
304
+ 'onclick' => 'toggleEpicEditor(this,\'' . $htmlId . '\');'
305
+ ))->toHtml();
306
+ }
307
+
308
+ if (Mage::helper('markdown')->isReMarkedEnabled() === TRUE) {
309
+ $this->_afterElementHtml[600] = Mage::getSingleton('core/layout')
310
+ ->createBlock('adminhtml/widget_button', '', array(
311
+ 'label' => Mage::helper('markdown')->__('HTML2[M↓]'),
312
+ 'type' => 'button',
313
+ 'onclick' => 'htmlToMarkDown(this,\'' . $htmlId . '\');'
314
+ ))->toHtml();
315
+ }
316
+ return $this;
317
+ }
318
+ }
app/code/community/SchumacherFM/Markdown/Model/Observer/Adminhtml/EpicEditor.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @category SchumacherFM_Markdown
4
+ * @package Observer
5
+ * @author Cyrill at Schumacher dot fm / @SchumacherFM
6
+ * @copyright Copyright (c)
7
+ *
8
+ * avoiding to load files on every page in the adminhtml area.
9
+ */
10
+ class SchumacherFM_Markdown_Model_Observer_Adminhtml_EpicEditor
11
+ {
12
+ /**
13
+ *
14
+ * @var array
15
+ */
16
+ protected $_epicEditorFiles = array(
17
+ 'js' => array(
18
+ 'markdown/adminhtml/epiceditor.js',
19
+ 'markdown/adminhtml/highlight.pack.js',
20
+ ),
21
+ );
22
+
23
+ /**
24
+ * adminhtml_block_html_before
25
+ *
26
+ * @param Varien_Event_Observer $observer
27
+ *
28
+ * @return null
29
+ */
30
+ public function injectEpicEditor(Varien_Event_Observer $observer)
31
+ {
32
+ if (Mage::helper('markdown')->isDisabled() || !Mage::helper('markdown')->isEpicEditorEnabled()) {
33
+ return NULL;
34
+ }
35
+
36
+ /** @var $block Mage_Adminhtml_Block_Page */
37
+ $block = $observer->getEvent()->getBlock();
38
+
39
+ if ($this->_isAllowedBlock($block) === FALSE) {
40
+ return NULL;
41
+ }
42
+
43
+ /** @var Mage_Adminhtml_Block_Page_Head $headBlock */
44
+ $headBlock = $block->getLayout()->getBlock('head');
45
+
46
+ if (isset($this->_epicEditorFiles['js'])) {
47
+ foreach ($this->_epicEditorFiles['js'] as $js) {
48
+ $headBlock->addJs($js);
49
+ }
50
+ }
51
+ if (isset($this->_epicEditorFiles['css'])) {
52
+ foreach ($this->_epicEditorFiles['css'] as $css) {
53
+ $headBlock->addCss($css);
54
+ }
55
+ }
56
+ return NULL;
57
+ }
58
+
59
+ /**
60
+ * @param Mage_Core_Block_Abstract $block
61
+ *
62
+ * @return bool
63
+ */
64
+ protected function _isAllowedBlock(Mage_Core_Block_Abstract $block)
65
+ {
66
+ $isPage = $block instanceof Mage_Adminhtml_Block_Page;
67
+ $isLayoutHandleAllowed = Mage::getSingleton('markdown/observer_adminhtml_layoutUpdate')->isAllowed();
68
+ return $isPage && $isLayoutHandleAllowed;
69
+ }
70
+
71
+ /**
72
+ * @param array $epicEditorFiles
73
+ */
74
+ public function setEpicEditorFiles($epicEditorFiles)
75
+ {
76
+ $this->_epicEditorFiles = $epicEditorFiles;
77
+ }
78
+
79
+ /**
80
+ * @return array
81
+ */
82
+ public function getEpicEditorFiles()
83
+ {
84
+ return $this->_epicEditorFiles;
85
+ }
86
+
87
+ }
app/code/community/SchumacherFM/Markdown/Model/Observer/Adminhtml/LayoutUpdate.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @category SchumacherFM_Markdown
4
+ * @package Observer
5
+ * @author Cyrill at Schumacher dot fm / @SchumacherFM
6
+ * @copyright Copyright (c)
7
+ */
8
+ class SchumacherFM_Markdown_Model_Observer_Adminhtml_LayoutUpdate
9
+ {
10
+
11
+ protected $_isAllowedFlag = FALSE;
12
+
13
+ /**
14
+ * adminhtml_block_html_before
15
+ *
16
+ * @param Varien_Event_Observer $observer
17
+ *
18
+ * @return null
19
+ */
20
+ public function injectMarkdownFiles(Varien_Event_Observer $observer)
21
+ {
22
+ if (Mage::helper('markdown')->isDisabled()) {
23
+ return NULL;
24
+ }
25
+
26
+ /** @var Mage_Core_Model_Layout $layout */
27
+ $layout = $observer->getEvent()->getLayout();
28
+
29
+ /** @var Mage_Core_Model_Layout_Update $update */
30
+ $update = $layout->getUpdate();
31
+
32
+ if ($this->_isAllowed($update)) {
33
+ $update->addHandle('MARKDOWN_HEADER');
34
+ }
35
+
36
+ }
37
+
38
+ /**
39
+ * @param Mage_Core_Model_Layout_Update $update
40
+ *
41
+ * @return bool
42
+ */
43
+ protected function _isAllowed(Mage_Core_Model_Layout_Update $update)
44
+ {
45
+
46
+ $handles = $update->getHandles();
47
+ $allowedHandles = Mage::helper('markdown')->getAllowedLayoutHandles();
48
+
49
+ foreach ($handles as $handle) {
50
+ if (isset($allowedHandles[$handle])) {
51
+ $this->_isAllowedFlag = TRUE;
52
+ break;
53
+ }
54
+ }
55
+ return $this->_isAllowedFlag;
56
+ }
57
+
58
+ /**
59
+ * @return bool
60
+ */
61
+ public function isAllowed()
62
+ {
63
+ return $this->_isAllowedFlag;
64
+ }
65
+
66
+ }
app/code/community/SchumacherFM/Markdown/Model/Observer/AdminhtmlBlock.php DELETED
@@ -1,150 +0,0 @@
1
- <?php
2
- /**
3
- * @category SchumacherFM_Markdown
4
- * @package Helper
5
- * @author Cyrill at Schumacher dot fm / @SchumacherFM
6
- * @copyright Copyright (c)
7
- */
8
- class SchumacherFM_Markdown_Model_Observer_AdminhtmlBlock
9
- {
10
- /**
11
- * adminhtml_block_html_before
12
- *
13
- * @param Varien_Event_Observer $observer
14
- *
15
- * @return null
16
- */
17
- public function alterTextareaBlockTemplate(Varien_Event_Observer $observer)
18
- {
19
- if (Mage::helper('markdown')->isDisabled()) {
20
- return null;
21
- }
22
-
23
- /** @var $block Mage_Adminhtml_Block_Template */
24
- $block = $observer->getEvent()->getBlock();
25
-
26
- if ($block instanceof Mage_Adminhtml_Block_Widget_Form_Renderer_Fieldset_Element) {
27
- /** @var Varien_Data_Form_Element_Abstract $element */
28
- $element = $block->getElement();
29
-
30
- if ($this->_isEmailTemplateElementAllowed($element)) {
31
- $element->setData('after_element_html', ' ');
32
- $this->_getMarkdownButtons($element, 'template_text');
33
- }
34
-
35
- if ($this->_isElementEditor($element)) {
36
- $this->_addLivePreviewToEditor($element);
37
- }
38
- }
39
-
40
- if ($block instanceof Mage_Adminhtml_Block_Catalog_Form_Renderer_Fieldset_Element) {
41
- /** @var Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg $element */
42
- $element = $block->getElement();
43
- if ($this->_isCatalogElementAllowed($element)) {
44
- $this->_getMarkdownButtons($element);
45
- }
46
- }
47
- }
48
-
49
- /**
50
- * @param Varien_Data_Form_Element_Abstract $element
51
- *
52
- * @return bool
53
- */
54
- protected function _isEmailTemplateElementAllowed(Varien_Data_Form_Element_Abstract $element)
55
- {
56
- $trueOne = $element instanceof Varien_Data_Form_Element_Note;
57
- $trueTwo = stristr($element->getHtmlId(), 'insert_variable') !== FALSE;
58
- return $trueOne && $trueTwo;
59
- }
60
-
61
- /**
62
- * @param Varien_Data_Form_Element_Abstract $element
63
- *
64
- * @return bool
65
- */
66
- protected function _isCatalogElementAllowed(Varien_Data_Form_Element_Abstract $element)
67
- {
68
- $isTextarea = $element instanceof Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg;
69
- $isDescription = stristr($element->getName(), 'description') !== FALSE && stristr($element->getName(), 'meta') === FALSE;
70
- return $isDescription && $isTextarea;
71
- }
72
-
73
- /**
74
- * @param Varien_Data_Form_Element_Abstract $element
75
- * @param string|null $htmlId
76
- */
77
- protected function _getMarkdownButtons(Varien_Data_Form_Element_Abstract $element, $htmlId = null)
78
- {
79
- $html = array($element->getData('after_element_html'));
80
- $htmlId = empty($htmlId) ? $element->getHtmlId() : $htmlId;
81
-
82
- $html[] = Mage::getSingleton('core/layout')
83
- ->createBlock('adminhtml/widget_button', '', array(
84
- 'label' => Mage::helper('markdown')->__('[M↓] enable'),
85
- 'type' => 'button',
86
- 'class' => 'btn-wysiwyg',
87
- 'onclick' => 'toggleMarkdown(\'' . Mage::helper('markdown')->getDetectionTag(TRUE) . '\',\'' . $htmlId . '\');'
88
- ))->toHtml();
89
-
90
- $html[] = Mage::getSingleton('core/layout')
91
- ->createBlock('adminhtml/widget_button', '', array(
92
- 'label' => Mage::helper('markdown')->__('[M↓] Preview'),
93
- 'type' => 'button',
94
- 'class' => 'btn-wysiwyg',
95
- 'onclick' => Mage::helper('markdown')->getRenderMarkdownJs($htmlId),
96
- ))->toHtml();
97
-
98
- $html[] = Mage::getSingleton('core/layout')
99
- ->createBlock('adminhtml/widget_button', '', array(
100
- 'label' => Mage::helper('markdown')->__('[M↓] Syntax'),
101
- 'type' => 'button',
102
- 'class' => 'btn-wysiwyg',
103
- 'onclick' => 'mdExternalUrl(\'' . SchumacherFM_Markdown_Helper_Data::URL_MD_SYNTAX . '\');'
104
- ))->toHtml();
105
-
106
- if (Mage::helper('markdown')->isMarkdownExtra()) {
107
-
108
- $html[] = Mage::getSingleton('core/layout')
109
- ->createBlock('adminhtml/widget_button', '', array(
110
- 'label' => Mage::helper('markdown')->__('[M↓] Extra Syntax'),
111
- 'type' => 'button',
112
- 'class' => 'btn-wysiwyg',
113
- 'onclick' => 'mdExternalUrl(\'' . SchumacherFM_Markdown_Helper_Data::URL_MD_EXTRA_SYNTAX . '\');'
114
- ))->toHtml();
115
- }
116
-
117
- $element->setData('after_element_html', implode(' ', $html));
118
- }
119
-
120
- /**
121
- * Live preview only available for non markdown extra mode.
122
- * otherwise the ajax request and php markdown rendering would kill the users patience
123
- *
124
- * @param Varien_Data_Form_Element_Abstract $element
125
- *
126
- * @return bool
127
- */
128
- protected function _isElementEditor(Varien_Data_Form_Element_Abstract $element)
129
- {
130
- return !Mage::helper('markdown')->isMarkdownExtra() && $element instanceof Varien_Data_Form_Element_Editor;
131
- }
132
-
133
- /**
134
- * @param Varien_Data_Form_Element_Editor $element
135
- *
136
- * @return $this
137
- */
138
- protected function _addLivePreviewToEditor(Varien_Data_Form_Element_Editor $element)
139
- {
140
- $previewHtml = '<div id="markdown_live_preview"
141
- style="overflow:scroll; height:25em;"
142
- data-mddetector="' . Mage::helper('markdown')->getDetectionTag(TRUE) . '"
143
- data-elementid="' . $element->getHtmlId() . '" class="buttons-set"><div class="markdown">' .
144
- Mage::helper('markdown')->__('[M↓] Live Preview enabled ...')
145
- . '</div></div>';
146
- $element->setData('after_element_html', $previewHtml);
147
- return $this;
148
- }
149
-
150
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/code/community/SchumacherFM/Markdown/controllers/Adminhtml/MarkdownController.php CHANGED
@@ -8,30 +8,106 @@
8
  class SchumacherFM_Markdown_Adminhtml_MarkdownController extends Mage_Adminhtml_Controller_Action
9
  {
10
 
 
 
 
 
 
 
 
 
11
  /**
12
  * @return void
13
  */
14
  public function renderAction()
15
  {
16
- $content = $this->getRequest()->getParam('content', null);
 
 
17
  if (!$this->getRequest()->isPost() || empty($content)) {
18
- return $this->_setReturn();
19
  }
20
 
21
- $md = Mage::helper('markdown')->render($content);
22
- return $this->_setReturn($md);
 
23
 
 
 
 
24
  }
25
 
26
  /**
27
  * @param string $string
 
28
  *
29
  * @return $this
30
  */
31
- protected function _setReturn($string = '')
32
  {
33
- $this->getResponse()->setBody($string);
 
 
 
34
  return $this;
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
8
  class SchumacherFM_Markdown_Adminhtml_MarkdownController extends Mage_Adminhtml_Controller_Action
9
  {
10
 
11
+ /**
12
+ * @see Adminhtml/Block.php --> _addMarkdownHint()
13
+ * not sure if useful
14
+ */
15
+ public function enableAction(){
16
+
17
+ }
18
+
19
  /**
20
  * @return void
21
  */
22
  public function renderAction()
23
  {
24
+ $content = $this->getRequest()->getParam('content', NULL);
25
+ $markdownExtra = ((int)$this->getRequest()->getParam('markdownExtra', 0)) === 1;
26
+
27
  if (!$this->getRequest()->isPost() || empty($content)) {
28
+ return $this->_setReturn('Incorrect Request');
29
  }
30
 
31
+ $md = Mage::helper('markdown')->render($content, array(
32
+ 'extra' => $markdownExtra
33
+ ));
34
 
35
+ $md = Mage::helper('markdown')->renderTemplateMediaUrl($md);
36
+
37
+ return $this->_setReturn($md);
38
  }
39
 
40
  /**
41
  * @param string $string
42
+ * @param bool $jsonEncode
43
  *
44
  * @return $this
45
  */
46
+ protected function _setReturn($string = '', $jsonEncode = FALSE)
47
  {
48
+ if (TRUE === $jsonEncode) {
49
+ $this->getResponse()->setHeader('Content-type', 'application/json', TRUE);
50
+ }
51
+ $this->getResponse()->setBody($jsonEncode ? Zend_Json_Encoder::encode($string) : $string);
52
  return $this;
53
  }
54
 
55
+ /**
56
+ * @todo better subdirectories
57
+ * saves a file in the dir: media/wysiwyg/markdown/....
58
+ *
59
+ * @return $this
60
+ */
61
+ public function fileUploadAction()
62
+ {
63
+
64
+ $return = array(
65
+ 'err' => TRUE,
66
+ 'msg' => 'An error occurred.',
67
+ 'fileUrl' => ''
68
+ );
69
+ $binaryData = base64_decode($this->getRequest()->getParam('binaryData', ''));
70
+ $file = json_decode($this->getRequest()->getParam('file', '[]'), TRUE);
71
+ $fileName = preg_replace('~[^\w\.]+~i', '', isset($file['name']) ? $file['name'] : '');
72
+
73
+ if (empty($fileName) || empty($binaryData) || empty($file)) {
74
+ $return['msg'] = 'Either fileName or binaryData or file is empty ...';
75
+ return $this->_setReturn($return, TRUE);
76
+ }
77
+
78
+ $savePath = $this->_getStorageRoot() . $this->_getStorageSubDirectory();
79
+ $io = new Varien_Io_File();
80
+ if ($io->checkAndCreateFolder($savePath)) {
81
+ $result = (int)file_put_contents($savePath . $fileName, $binaryData); // io->write will not work :-(
82
+ if ($result > 10) {
83
+ $return['err'] = FALSE;
84
+ $return['msg'] = '';
85
+ $return['fileUrl'] = Mage::helper('markdown')->getTemplateMediaUrl($this->_getStorageSubDirectory() . $fileName);
86
+ }
87
+ }
88
+
89
+ $this->_setReturn($return, TRUE);
90
+ }
91
+
92
+ /**
93
+ * @return mixed|string
94
+ */
95
+ protected function _getStorageSubDirectory()
96
+ {
97
+ $userDir = Mage::getStoreConfig('markdown/file_reader/upload_dir');
98
+ if (empty($userDir)) {
99
+ $userDir = Mage_Cms_Model_Wysiwyg_Config::IMAGE_DIRECTORY . DS . 'markdown' . DS;
100
+ }
101
+ return $userDir;
102
+ }
103
+
104
+ /**
105
+ * Images Storage root directory
106
+ *
107
+ * @return string
108
+ */
109
+ protected function _getStorageRoot()
110
+ {
111
+ return Mage::getConfig()->getOptions()->getMediaDir() . DS;
112
+ }
113
  }
app/code/community/SchumacherFM/Markdown/etc/adminhtml.xml CHANGED
@@ -16,9 +16,9 @@
16
  <children>
17
  <config>
18
  <children>
19
- <schumacherfm translate="title" module="schumacherfm_markdown">
20
- <title>@SchumacherFM (Config)</title>
21
- </schumacherfm>
22
  </children>
23
  </config>
24
  </children>
16
  <children>
17
  <config>
18
  <children>
19
+ <markdown translate="title" module="schumacherfm_markdown">
20
+ <title>Markdown [M↓]</title>
21
+ </markdown>
22
  </children>
23
  </config>
24
  </children>
app/code/community/SchumacherFM/Markdown/etc/config.xml CHANGED
@@ -1,8 +1,4 @@
1
- <?xml version="1.0"?>
2
- <!--* @category SchumacherFM_Markdown-->
3
- <!--* @package etc-->
4
- <!--* @author Cyrill at Schumacher dot fm / @SchumacherFM-->
5
- <!--* @copyright Copyright (c)-->
6
  <config>
7
  <modules>
8
  <SchumacherFM_Markdown>
@@ -97,20 +93,24 @@
97
  </modules>
98
  </translate>
99
  <events>
100
- <cms_wysiwyg_config_prepare>
101
  <observers>
102
- <markdown_observer>
103
- <class>markdown/editor_observer</class>
104
- <method>prepareWysiwygPluginConfig</method>
105
- </markdown_observer>
106
  </observers>
107
- </cms_wysiwyg_config_prepare>
108
  <adminhtml_block_html_before>
109
  <observers>
110
- <markdown_adminhtml_block_html_before>
111
- <class>markdown/observer_adminhtmlBlock</class>
112
  <method>alterTextareaBlockTemplate</method>
113
- </markdown_adminhtml_block_html_before>
 
 
 
 
114
  </observers>
115
  </adminhtml_block_html_before>
116
  <core_config_data_save_after>
@@ -124,14 +124,25 @@
124
  </events>
125
  </adminhtml>
126
  <default>
127
- <schumacherfm>
128
  <markdown>
129
  <enable>1</enable>
130
- <md_extra>0</md_extra>
131
- <md_extra_email>0</md_extra_email>
132
  <detection_tag>!#markdown</detection_tag>
133
  </markdown>
134
- </schumacherfm>
 
 
 
 
 
 
 
 
 
 
 
 
135
  </default>
136
  <phpunit>
137
  <suite>
1
+ <?xml version="1.0"?><!--* @category SchumacherFM_Markdown--><!--* @package etc--><!--* @author Cyrill at Schumacher dot fm / @SchumacherFM--><!--* @copyright Copyright (c)-->
 
 
 
 
2
  <config>
3
  <modules>
4
  <SchumacherFM_Markdown>
93
  </modules>
94
  </translate>
95
  <events>
96
+ <controller_action_layout_load_before>
97
  <observers>
98
+ <markdown_controller_action_layout_load_before>
99
+ <class>markdown/observer_adminhtml_layoutUpdate</class>
100
+ <method>injectMarkdownFiles</method>
101
+ </markdown_controller_action_layout_load_before>
102
  </observers>
103
+ </controller_action_layout_load_before>
104
  <adminhtml_block_html_before>
105
  <observers>
106
+ <markdown_adminhtml_block_html_before_textarea>
107
+ <class>markdown/observer_adminhtml_block</class>
108
  <method>alterTextareaBlockTemplate</method>
109
+ </markdown_adminhtml_block_html_before_textarea>
110
+ <markdown_adminhtml_block_html_before_page>
111
+ <class>markdown/observer_adminhtml_epicEditor</class>
112
+ <method>injectEpicEditor</method>
113
+ </markdown_adminhtml_block_html_before_page>
114
  </observers>
115
  </adminhtml_block_html_before>
116
  <core_config_data_save_after>
124
  </events>
125
  </adminhtml>
126
  <default>
127
+ <markdown>
128
  <markdown>
129
  <enable>1</enable>
130
+ <cheatsheet>https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet</cheatsheet>
 
131
  <detection_tag>!#markdown</detection_tag>
132
  </markdown>
133
+ <markdown_extra>
134
+ <enable>0</enable>
135
+ <enable_email>0</enable_email>
136
+ </markdown_extra>
137
+
138
+ <epiceditor>
139
+ <enable>0</enable>
140
+ <load_on_click_textarea>0</load_on_click_textarea>
141
+ </epiceditor>
142
+ <remarked>
143
+ <enable>1</enable>
144
+ </remarked>
145
+ </markdown>
146
  </default>
147
  <phpunit>
148
  <suite>
app/code/community/SchumacherFM/Markdown/etc/system.xml CHANGED
@@ -1,5 +1,4 @@
1
- <?xml version="1.0"?>
2
- <!--
3
  /**
4
  * @category SchumacherFM
5
  * @package SchumacherFM_Markdown
@@ -9,14 +8,15 @@
9
  -->
10
  <config>
11
  <sections>
12
- <schumacherfm>
13
- <label>@SchumacherFM</label>
14
- <tab>advanced</tab>
15
  <frontend_type>text</frontend_type>
16
- <sort_order>930</sort_order>
17
  <show_in_default>1</show_in_default>
18
  <show_in_website>1</show_in_website>
19
  <show_in_store>1</show_in_store>
 
20
  <groups>
21
  <markdown translate="label">
22
  <label>Markdown [M↓]</label>
@@ -35,15 +35,17 @@
35
  <show_in_website>1</show_in_website>
36
  <show_in_store>1</show_in_store>
37
  </enable>
38
- <md_extra translate="label">
39
- <label>Use Markdown Extra</label>
40
- <frontend_type>select</frontend_type>
41
- <source_model>adminhtml/system_config_source_yesno</source_model>
42
- <sort_order>20</sort_order>
43
  <show_in_default>1</show_in_default>
44
  <show_in_website>1</show_in_website>
45
  <show_in_store>1</show_in_store>
46
- </md_extra>
 
 
 
47
  <detection_tag translate="label">
48
  <label>Markdown Detection Tag</label>
49
  <frontend_type>text</frontend_type>
@@ -51,12 +53,45 @@
51
  <show_in_default>1</show_in_default>
52
  <show_in_website>1</show_in_website>
53
  <show_in_store>1</show_in_store>
54
- <comment>Every content field which contains markdown must have this tag included that it will be parsed.
55
- This tag will of course be removed during parsing. If empty, every content is considered as markdown.
56
- Only transactional emails which have the detection tag somewhere integrated will be rendered with markdown.
57
  </comment>
58
  </detection_tag>
59
- <md_extra_email translate="label">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  <label>Transactional email: Use Markdown Extra</label>
61
  <frontend_type>select</frontend_type>
62
  <source_model>adminhtml/system_config_source_yesno</source_model>
@@ -64,7 +99,7 @@
64
  <show_in_default>1</show_in_default>
65
  <show_in_website>1</show_in_website>
66
  <show_in_store>1</show_in_store>
67
- </md_extra_email>
68
  <te_md_css translate="label">
69
  <label>Transactional email: Path to CSS file</label>
70
  <frontend_type>text</frontend_type>
@@ -76,8 +111,104 @@
76
  Will be included in the &lt;style&gt; tag in each email.]]></comment>
77
  </te_md_css>
78
  </fields>
79
- </markdown>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  </groups>
81
- </schumacherfm>
82
  </sections>
83
  </config>
1
+ <?xml version="1.0"?><!--
 
2
  /**
3
  * @category SchumacherFM
4
  * @package SchumacherFM_Markdown
8
  -->
9
  <config>
10
  <sections>
11
+ <markdown>
12
+ <label>Markdown [M↓]</label>
13
+ <tab>general</tab>
14
  <frontend_type>text</frontend_type>
15
+ <sort_order>2001</sort_order>
16
  <show_in_default>1</show_in_default>
17
  <show_in_website>1</show_in_website>
18
  <show_in_store>1</show_in_store>
19
+
20
  <groups>
21
  <markdown translate="label">
22
  <label>Markdown [M↓]</label>
35
  <show_in_website>1</show_in_website>
36
  <show_in_store>1</show_in_store>
37
  </enable>
38
+ <cheatsheet translate="label">
39
+ <label>Markdown Cheat Sheet URL</label>
40
+ <frontend_type>text</frontend_type>
41
+ <sort_order>15</sort_order>
 
42
  <show_in_default>1</show_in_default>
43
  <show_in_website>1</show_in_website>
44
  <show_in_store>1</show_in_store>
45
+ <comment>Alternatives http://daringfireball.net/projects/markdown/syntax or
46
+ https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet or your own page
47
+ </comment>
48
+ </cheatsheet>
49
  <detection_tag translate="label">
50
  <label>Markdown Detection Tag</label>
51
  <frontend_type>text</frontend_type>
53
  <show_in_default>1</show_in_default>
54
  <show_in_website>1</show_in_website>
55
  <show_in_store>1</show_in_store>
56
+ <comment>Every content field which contains markdown must have this tag included otherwise it will not be parsed.
57
+ This tag will of course be removed during parsing. If empty, every content is considered as markdown.
58
+ Only transactional emails which have the detection tag somewhere integrated will be rendered with markdown.
59
  </comment>
60
  </detection_tag>
61
+ <custom_layout_handles translate="label">
62
+ <label>Additional Layout Handles</label>
63
+ <frontend_type>textarea</frontend_type>
64
+ <sort_order>220</sort_order>
65
+ <show_in_default>1</show_in_default>
66
+ <show_in_website>1</show_in_website>
67
+ <show_in_store>1</show_in_store>
68
+ <comment>Expert Mode: Load Markdown feature and EpicEditor also via these custom layout handles.
69
+ A whitespace separated list. Q: Where can I find the name of a Layout Handle? A: View the class attribute
70
+ on the body tag. Most handles starts with adminhtml-... Replace the - with _. Full string is required.
71
+ </comment>
72
+ </custom_layout_handles>
73
+
74
+ </fields>
75
+ </markdown>
76
+
77
+ <markdown_extra translate="label">
78
+ <label>Markdown Extra</label>
79
+ <frontend_type>text</frontend_type>
80
+ <sort_order>60</sort_order>
81
+ <show_in_default>1</show_in_default>
82
+ <show_in_website>1</show_in_website>
83
+ <show_in_store>1</show_in_store>
84
+ <fields>
85
+ <enable translate="label">
86
+ <label>Use Markdown Extra</label>
87
+ <frontend_type>select</frontend_type>
88
+ <source_model>adminhtml/system_config_source_yesno</source_model>
89
+ <sort_order>20</sort_order>
90
+ <show_in_default>1</show_in_default>
91
+ <show_in_website>1</show_in_website>
92
+ <show_in_store>1</show_in_store>
93
+ </enable>
94
+ <enable_email translate="label">
95
  <label>Transactional email: Use Markdown Extra</label>
96
  <frontend_type>select</frontend_type>
97
  <source_model>adminhtml/system_config_source_yesno</source_model>
99
  <show_in_default>1</show_in_default>
100
  <show_in_website>1</show_in_website>
101
  <show_in_store>1</show_in_store>
102
+ </enable_email>
103
  <te_md_css translate="label">
104
  <label>Transactional email: Path to CSS file</label>
105
  <frontend_type>text</frontend_type>
111
  Will be included in the &lt;style&gt; tag in each email.]]></comment>
112
  </te_md_css>
113
  </fields>
114
+ </markdown_extra>
115
+
116
+ <file_reader translate="label">
117
+ <label>Drag'n'Drop File Upload</label>
118
+ <frontend_type>text</frontend_type>
119
+ <sort_order>70</sort_order>
120
+ <show_in_default>1</show_in_default>
121
+ <show_in_website>1</show_in_website>
122
+ <show_in_store>1</show_in_store>
123
+ <fields>
124
+ <upload_dir translate="label">
125
+ <label>Upload directory</label>
126
+ <frontend_type>text</frontend_type>
127
+ <sort_order>100</sort_order>
128
+ <show_in_default>1</show_in_default>
129
+ <show_in_website>1</show_in_website>
130
+ <show_in_store>1</show_in_store>
131
+ <comment><![CDATA[Directory must be writable for the webserver. If directory did not exists system will
132
+ attempt to create it recursively. Please add trailing slash.
133
+ Default upload directory is media/wysiwyg/markdown/]]></comment>
134
+ </upload_dir>
135
+ </fields>
136
+ </file_reader>
137
+
138
+ <epiceditor>
139
+ <label>EpicEditor</label>
140
+ <frontend_type>text</frontend_type>
141
+ <sort_order>100</sort_order>
142
+ <show_in_default>1</show_in_default>
143
+ <show_in_website>1</show_in_website>
144
+ <show_in_store>1</show_in_store>
145
+ <fields>
146
+ <enable translate="label">
147
+ <label>Enable EpicEditor</label>
148
+ <frontend_type>select</frontend_type>
149
+ <source_model>adminhtml/system_config_source_yesno</source_model>
150
+ <sort_order>200</sort_order>
151
+ <show_in_default>1</show_in_default>
152
+ <show_in_website>1</show_in_website>
153
+ <show_in_store>1</show_in_store>
154
+ <comment><![CDATA[Enables the awesome Markdown <a href="http://epiceditor.com" target="_blank">EpicEditor</a>.]]></comment>
155
+ </enable>
156
+ <load_on_click_textarea translate="label">
157
+ <label>Load EpicEditor on click</label>
158
+ <frontend_type>select</frontend_type>
159
+ <source_model>adminhtml/system_config_source_yesno</source_model>
160
+ <sort_order>210</sort_order>
161
+ <show_in_default>1</show_in_default>
162
+ <show_in_website>1</show_in_website>
163
+ <show_in_store>1</show_in_store>
164
+ <comment><![CDATA[Loads the EpicEditor only on the first click into a textarea field. After that loading
165
+ and unloading of the EpicEditor must be done via button. Hint: only enable that if you're not using
166
+ too much the Drag'n'Drop image upload.]]></comment>
167
+ </load_on_click_textarea>
168
+ <config translate="label">
169
+ <label>EpicEditor Config</label>
170
+ <frontend_type>textarea</frontend_type>
171
+ <sort_order>220</sort_order>
172
+ <show_in_default>1</show_in_default>
173
+ <show_in_website>1</show_in_website>
174
+ <show_in_store>1</show_in_store>
175
+ <comment>Expert Mode: A valid JSON object. Will be merged into the EpicEditor config.</comment>
176
+ </config>
177
+ </fields>
178
+ </epiceditor>
179
+
180
+ <remarked>
181
+ <label>HTML to Markdown</label>
182
+ <frontend_type>text</frontend_type>
183
+ <sort_order>150</sort_order>
184
+ <show_in_default>1</show_in_default>
185
+ <show_in_website>1</show_in_website>
186
+ <show_in_store>1</show_in_store>
187
+ <fields>
188
+ <enable translate="label">
189
+ <label>Enable Converter</label>
190
+ <frontend_type>select</frontend_type>
191
+ <source_model>adminhtml/system_config_source_yesno</source_model>
192
+ <sort_order>200</sort_order>
193
+ <show_in_default>1</show_in_default>
194
+ <show_in_website>1</show_in_website>
195
+ <show_in_store>1</show_in_store>
196
+ <comment><![CDATA[Enables the awesome reMarked.js HTML to Markdown converter]]></comment>
197
+ </enable>
198
+ <config translate="label">
199
+ <label>reMarked.js Config</label>
200
+ <frontend_type>textarea</frontend_type>
201
+ <sort_order>220</sort_order>
202
+ <show_in_default>1</show_in_default>
203
+ <show_in_website>1</show_in_website>
204
+ <show_in_store>1</show_in_store>
205
+ <comment><![CDATA[Expert Mode: A valid JSON object. Will be merged into the
206
+ <a href="https://github.com/leeoniya/reMarked.js" target="_blank">reMarked.js</a> config.]]></comment>
207
+ </config>
208
+ </fields>
209
+ </remarked>
210
+
211
  </groups>
212
+ </markdown>
213
  </sections>
214
  </config>
app/design/adminhtml/default/default/layout/markdown.xml CHANGED
@@ -1,16 +1,25 @@
1
  <?xml version="1.0"?>
2
  <layout>
 
3
  <MARKDOWN_HEADER>
4
  <reference name="head">
5
- <action method="addJs"><script>mage/adminhtml/markdown.js</script></action>
6
- <action method="addJs"><script>mage/adminhtml/marked.js</script></action>
7
- <action method="addCss"><name>markdown.css</name></action>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  </reference>
9
  </MARKDOWN_HEADER>
10
- <editor>
11
- <update handle="MARKDOWN_HEADER"/>
12
- </editor>
13
- <adminhtml_system_email_template_edit>
14
- <update handle="MARKDOWN_HEADER"/>
15
- </adminhtml_system_email_template_edit>
16
  </layout>
1
  <?xml version="1.0"?>
2
  <layout>
3
+ <!--@see SchumacherFM_Markdown_Model_Observer_Adminhtml_LayoutUpdate-->
4
  <MARKDOWN_HEADER>
5
  <reference name="head">
6
+ <action method="addJs">
7
+ <script>markdown/adminhtml/reMarked.js</script>
8
+ </action>
9
+ <action method="addJs">
10
+ <script>markdown/adminhtml/marked.js</script>
11
+ </action>
12
+ <action method="addJs">
13
+ <script>markdown/adminhtml/filereader.js</script>
14
+ </action>
15
+ <action method="addJs">
16
+ <script>markdown/adminhtml/markdown.js</script>
17
+ </action>
18
+ <action method="addItem">
19
+ <type>skin_css</type>
20
+ <name>markdown/mdm.css</name>
21
+ <params/>
22
+ </action>
23
  </reference>
24
  </MARKDOWN_HEADER>
 
 
 
 
 
 
25
  </layout>
js/mage/adminhtml/markdown.js DELETED
@@ -1,144 +0,0 @@
1
- /**
2
- * @category SchumacherFM_Markdown
3
- * @package JavaScript
4
- * @author Cyrill at Schumacher dot fm / @SchumacherFM
5
- * @copyright Copyright (c)
6
- */
7
- ;
8
- (function () {
9
- var dialogWindow,
10
- dialogWindowId = 'markdown-preview',
11
- TEXT_PREFIX = '<div class="markdown">',
12
- TEXT_SUFFIX = '</div>',
13
-
14
- htmlId = '',
15
-
16
- showPreview = function (responseText) {
17
-
18
- dialogWindow = Dialog.info(TEXT_PREFIX + responseText + TEXT_SUFFIX, {
19
- draggable: true,
20
- resizable: true,
21
- closable: true,
22
- className: "magento",
23
- windowClassName: "popup-window",
24
- title: 'Markdown Preview',
25
- width: 800,
26
- height: 480,
27
- zIndex: 1000,
28
- recenterAuto: false,
29
- hideEffect: Element.hide,
30
- showEffect: Element.show,
31
- id: dialogWindowId,
32
- onClose: closeDialogWindow.bind(this)
33
- });
34
- },
35
-
36
- closeDialogWindow = function (window) {
37
- if (!window) {
38
- window = dialogWindow;
39
- }
40
- if (window) {
41
- window.close();
42
- }
43
- },
44
-
45
- _renderMarkdownJs = function (mdDetector) {
46
- mdDetector = unescape(mdDetector);
47
- showPreview(marked($(htmlId).value.replace(mdDetector, '')));
48
- },
49
-
50
- _renderMarkdownAjax = function (url) {
51
- new Ajax.Request(url, {
52
- method: 'post',
53
- parameters: {"content": $(htmlId).value},
54
- onComplete: function (data) {
55
- showPreview((data && data.responseText) ? data.responseText : 'Ajax Error');
56
- }
57
- });
58
-
59
- },
60
-
61
- renderMarkdown = function (Idhtml, mdDetector, renderUrl) {
62
- htmlId = Idhtml;
63
- if (renderUrl && typeof renderUrl === 'string') {
64
- _renderMarkdownAjax(renderUrl);
65
- } else {
66
- _renderMarkdownJs(mdDetector);
67
- }
68
- return;
69
-
70
- },
71
-
72
- mdExternalUrl = function (url, Idhtml) {
73
- htmlId = Idhtml;
74
- window.open(url);
75
- },
76
-
77
- toggleMarkdown = function (detectionTag, Idhtml) {
78
- detectionTag = unescape(detectionTag);
79
-
80
- if ($(Idhtml).value.indexOf(detectionTag) === -1) {
81
- $(Idhtml).value = detectionTag + "\n" + $(Idhtml).value;
82
- }
83
- alert('Markdown enabled with tag: "' + detectionTag + '"');
84
- },
85
-
86
- _livePreview = function ($markdownLivePreview) {
87
- var editorId = $markdownLivePreview.readAttribute('data-elementid'),
88
- $editorId = $(editorId),
89
- _mdHandling = new _mdHandler();
90
-
91
- _mdHandling.setMdDetector($markdownLivePreview);
92
-
93
- var _originalHeight = $markdownLivePreview.getStyle('height'), _clicked = false;
94
- $markdownLivePreview.observe('click', function (e) {
95
- var css = {height: ''};
96
- if (_clicked) {
97
- css['height'] = _originalHeight;
98
- _clicked = false;
99
- } else {
100
- _clicked = true;
101
- }
102
- $markdownLivePreview.setStyle(css);
103
- });
104
-
105
- $editorId.observe('keyup', function (e) {
106
- _mdHandling.text = e.target.value;
107
- $markdownLivePreview.innerHTML = _mdHandling.hasMarkdown()
108
- ? _mdHandling.getRenderedMarkdown()
109
- : 'Offline ...';
110
- });
111
- },
112
-
113
- _mdHandler = function () {
114
- this.text = '';
115
- this._mdDetector = '';
116
- };
117
-
118
- _mdHandler.prototype = {
119
- setMdDetector: function ($markdownLivePreview) {
120
- this._mdDetector = unescape($markdownLivePreview.readAttribute('data-mddetector') || '~~~@#$#@!');
121
- return this;
122
- },
123
- getRenderedMarkdown: function () {
124
- return TEXT_PREFIX + marked(this.text.replace(this._mdDetector, '')) + TEXT_SUFFIX;
125
- },
126
- hasMarkdown: function () {
127
- return this.text.indexOf(this._mdDetector) !== -1;
128
- }
129
- }
130
-
131
- this.renderMarkdown = renderMarkdown;
132
- this.mdExternalUrl = mdExternalUrl;
133
- this.toggleMarkdown = toggleMarkdown;
134
-
135
- document.observe('dom:loaded', function () {
136
- var markdownLivePreview = $('markdown_live_preview');
137
- if (markdownLivePreview) {
138
- _livePreview(markdownLivePreview);
139
- }
140
- });
141
-
142
- }).call(function () {
143
- return this || (typeof window !== 'undefined' ? window : global);
144
- }());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/mage/adminhtml/marked.js DELETED
@@ -1,1181 +0,0 @@
1
- /**
2
- * marked - a markdown parser
3
- * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
4
- * https://github.com/chjj/marked
5
- */
6
-
7
- ;(function () {
8
-
9
- /**
10
- * Block-Level Grammar
11
- */
12
-
13
- var block = {
14
- newline: /^\n+/,
15
- code: /^( {4}[^\n]+\n*)+/,
16
- fences: noop,
17
- hr: /^( *[-*_]){3,} *(?:\n+|$)/,
18
- heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
19
- nptable: noop,
20
- lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
21
- blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
22
- list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
23
- html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
24
- def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
25
- table: noop,
26
- paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
27
- text: /^[^\n]+/
28
- };
29
-
30
- block.bullet = /(?:[*+-]|\d+\.)/;
31
- block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
32
- block.item = replace(block.item, 'gm')
33
- (/bull/g, block.bullet)
34
- ();
35
-
36
- block.list = replace(block.list)
37
- (/bull/g, block.bullet)
38
- ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
39
- ();
40
-
41
- block._tag = '(?!(?:'
42
- + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
43
- + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
44
- + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
45
-
46
- block.html = replace(block.html)
47
- ('comment', /<!--[\s\S]*?-->/)
48
- ('closed', /<(tag)[\s\S]+?<\/\1>/)
49
- ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
50
- (/tag/g, block._tag)
51
- ();
52
-
53
- block.paragraph = replace(block.paragraph)
54
- ('hr', block.hr)
55
- ('heading', block.heading)
56
- ('lheading', block.lheading)
57
- ('blockquote', block.blockquote)
58
- ('tag', '<' + block._tag)
59
- ('def', block.def)
60
- ();
61
-
62
- /**
63
- * Normal Block Grammar
64
- */
65
-
66
- block.normal = merge({}, block);
67
-
68
- /**
69
- * GFM Block Grammar
70
- */
71
-
72
- block.gfm = merge({}, block.normal, {
73
- fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
74
- paragraph: /^/
75
- });
76
-
77
- block.gfm.paragraph = replace(block.paragraph)
78
- ('(?!', '(?!'
79
- + block.gfm.fences.source.replace('\\1', '\\2') + '|'
80
- + block.list.source.replace('\\1', '\\3') + '|')
81
- ();
82
-
83
- /**
84
- * GFM + Tables Block Grammar
85
- */
86
-
87
- block.tables = merge({}, block.gfm, {
88
- nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
89
- table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
90
- });
91
-
92
- /**
93
- * Block Lexer
94
- */
95
-
96
- function Lexer(options) {
97
- this.tokens = [];
98
- this.tokens.links = {};
99
- this.options = options || marked.defaults;
100
- this.rules = block.normal;
101
-
102
- if (this.options.gfm) {
103
- if (this.options.tables) {
104
- this.rules = block.tables;
105
- } else {
106
- this.rules = block.gfm;
107
- }
108
- }
109
- }
110
-
111
- /**
112
- * Expose Block Rules
113
- */
114
-
115
- Lexer.rules = block;
116
-
117
- /**
118
- * Static Lex Method
119
- */
120
-
121
- Lexer.lex = function (src, options) {
122
- var lexer = new Lexer(options);
123
- return lexer.lex(src);
124
- };
125
-
126
- /**
127
- * Preprocessing
128
- */
129
-
130
- Lexer.prototype.lex = function (src) {
131
- src = src
132
- .replace(/\r\n|\r/g, '\n')
133
- .replace(/\t/g, ' ')
134
- .replace(/\u00a0/g, ' ')
135
- .replace(/\u2424/g, '\n');
136
-
137
- return this.token(src, true);
138
- };
139
-
140
- /**
141
- * Lexing
142
- */
143
-
144
- Lexer.prototype.token = function (src, top) {
145
- var src = src.replace(/^ +$/gm, '')
146
- , next
147
- , loose
148
- , cap
149
- , bull
150
- , b
151
- , item
152
- , space
153
- , i
154
- , l;
155
-
156
- while (src) {
157
- // newline
158
- if (cap = this.rules.newline.exec(src)) {
159
- src = src.substring(cap[0].length);
160
- if (cap[0].length > 1) {
161
- this.tokens.push({
162
- type: 'space'
163
- });
164
- }
165
- }
166
-
167
- // code
168
- if (cap = this.rules.code.exec(src)) {
169
- src = src.substring(cap[0].length);
170
- cap = cap[0].replace(/^ {4}/gm, '');
171
- this.tokens.push({
172
- type: 'code',
173
- text: !this.options.pedantic
174
- ? cap.replace(/\n+$/, '')
175
- : cap
176
- });
177
- continue;
178
- }
179
-
180
- // fences (gfm)
181
- if (cap = this.rules.fences.exec(src)) {
182
- src = src.substring(cap[0].length);
183
- this.tokens.push({
184
- type: 'code',
185
- lang: cap[2],
186
- text: cap[3]
187
- });
188
- continue;
189
- }
190
-
191
- // heading
192
- if (cap = this.rules.heading.exec(src)) {
193
- src = src.substring(cap[0].length);
194
- this.tokens.push({
195
- type: 'heading',
196
- depth: cap[1].length,
197
- text: cap[2]
198
- });
199
- continue;
200
- }
201
-
202
- // table no leading pipe (gfm)
203
- if (top && (cap = this.rules.nptable.exec(src))) {
204
- src = src.substring(cap[0].length);
205
-
206
- item = {
207
- type: 'table',
208
- header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
209
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
210
- cells: cap[3].replace(/\n$/, '').split('\n')
211
- };
212
-
213
- for (i = 0; i < item.align.length; i++) {
214
- if (/^ *-+: *$/.test(item.align[i])) {
215
- item.align[i] = 'right';
216
- } else if (/^ *:-+: *$/.test(item.align[i])) {
217
- item.align[i] = 'center';
218
- } else if (/^ *:-+ *$/.test(item.align[i])) {
219
- item.align[i] = 'left';
220
- } else {
221
- item.align[i] = null;
222
- }
223
- }
224
-
225
- for (i = 0; i < item.cells.length; i++) {
226
- item.cells[i] = item.cells[i].split(/ *\| */);
227
- }
228
-
229
- this.tokens.push(item);
230
-
231
- continue;
232
- }
233
-
234
- // lheading
235
- if (cap = this.rules.lheading.exec(src)) {
236
- src = src.substring(cap[0].length);
237
- this.tokens.push({
238
- type: 'heading',
239
- depth: cap[2] === '=' ? 1 : 2,
240
- text: cap[1]
241
- });
242
- continue;
243
- }
244
-
245
- // hr
246
- if (cap = this.rules.hr.exec(src)) {
247
- src = src.substring(cap[0].length);
248
- this.tokens.push({
249
- type: 'hr'
250
- });
251
- continue;
252
- }
253
-
254
- // blockquote
255
- if (cap = this.rules.blockquote.exec(src)) {
256
- src = src.substring(cap[0].length);
257
-
258
- this.tokens.push({
259
- type: 'blockquote_start'
260
- });
261
-
262
- cap = cap[0].replace(/^ *> ?/gm, '');
263
-
264
- // Pass `top` to keep the current
265
- // "toplevel" state. This is exactly
266
- // how markdown.pl works.
267
- this.token(cap, top);
268
-
269
- this.tokens.push({
270
- type: 'blockquote_end'
271
- });
272
-
273
- continue;
274
- }
275
-
276
- // list
277
- if (cap = this.rules.list.exec(src)) {
278
- src = src.substring(cap[0].length);
279
- bull = cap[2];
280
-
281
- this.tokens.push({
282
- type: 'list_start',
283
- ordered: bull.length > 1
284
- });
285
-
286
- // Get each top-level item.
287
- cap = cap[0].match(this.rules.item);
288
-
289
- next = false;
290
- l = cap.length;
291
- i = 0;
292
-
293
- for (; i < l; i++) {
294
- item = cap[i];
295
-
296
- // Remove the list item's bullet
297
- // so it is seen as the next token.
298
- space = item.length;
299
- item = item.replace(/^ *([*+-]|\d+\.) +/, '');
300
-
301
- // Outdent whatever the
302
- // list item contains. Hacky.
303
- if (~item.indexOf('\n ')) {
304
- space -= item.length;
305
- item = !this.options.pedantic
306
- ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
307
- : item.replace(/^ {1,4}/gm, '');
308
- }
309
-
310
- // Determine whether the next list item belongs here.
311
- // Backpedal if it does not belong in this list.
312
- if (this.options.smartLists && i !== l - 1) {
313
- b = block.bullet.exec(cap[i + 1])[0];
314
- if (bull !== b && !(bull.length > 1 && b.length > 1)) {
315
- src = cap.slice(i + 1).join('\n') + src;
316
- i = l - 1;
317
- }
318
- }
319
-
320
- // Determine whether item is loose or not.
321
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
322
- // for discount behavior.
323
- loose = next || /\n\n(?!\s*$)/.test(item);
324
- if (i !== l - 1) {
325
- next = item.charAt(item.length - 1) === '\n';
326
- if (!loose) loose = next;
327
- }
328
-
329
- this.tokens.push({
330
- type: loose
331
- ? 'loose_item_start'
332
- : 'list_item_start'
333
- });
334
-
335
- // Recurse.
336
- this.token(item, false);
337
-
338
- this.tokens.push({
339
- type: 'list_item_end'
340
- });
341
- }
342
-
343
- this.tokens.push({
344
- type: 'list_end'
345
- });
346
-
347
- continue;
348
- }
349
-
350
- // html
351
- if (cap = this.rules.html.exec(src)) {
352
- src = src.substring(cap[0].length);
353
- this.tokens.push({
354
- type: this.options.sanitize
355
- ? 'paragraph'
356
- : 'html',
357
- pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
358
- text: cap[0]
359
- });
360
- continue;
361
- }
362
-
363
- // def
364
- if (top && (cap = this.rules.def.exec(src))) {
365
- src = src.substring(cap[0].length);
366
- this.tokens.links[cap[1].toLowerCase()] = {
367
- href: cap[2],
368
- title: cap[3]
369
- };
370
- continue;
371
- }
372
-
373
- // table (gfm)
374
- if (top && (cap = this.rules.table.exec(src))) {
375
- src = src.substring(cap[0].length);
376
-
377
- item = {
378
- type: 'table',
379
- header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
380
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
381
- cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
382
- };
383
-
384
- for (i = 0; i < item.align.length; i++) {
385
- if (/^ *-+: *$/.test(item.align[i])) {
386
- item.align[i] = 'right';
387
- } else if (/^ *:-+: *$/.test(item.align[i])) {
388
- item.align[i] = 'center';
389
- } else if (/^ *:-+ *$/.test(item.align[i])) {
390
- item.align[i] = 'left';
391
- } else {
392
- item.align[i] = null;
393
- }
394
- }
395
-
396
- for (i = 0; i < item.cells.length; i++) {
397
- item.cells[i] = item.cells[i]
398
- .replace(/^ *\| *| *\| *$/g, '')
399
- .split(/ *\| */);
400
- }
401
-
402
- this.tokens.push(item);
403
-
404
- continue;
405
- }
406
-
407
- // top-level paragraph
408
- if (top && (cap = this.rules.paragraph.exec(src))) {
409
- src = src.substring(cap[0].length);
410
- this.tokens.push({
411
- type: 'paragraph',
412
- text: cap[1].charAt(cap[1].length - 1) === '\n'
413
- ? cap[1].slice(0, -1)
414
- : cap[1]
415
- });
416
- continue;
417
- }
418
-
419
- // text
420
- if (cap = this.rules.text.exec(src)) {
421
- // Top-level should never reach here.
422
- src = src.substring(cap[0].length);
423
- this.tokens.push({
424
- type: 'text',
425
- text: cap[0]
426
- });
427
- continue;
428
- }
429
-
430
- if (src) {
431
- throw new
432
- Error('Infinite loop on byte: ' + src.charCodeAt(0));
433
- }
434
- }
435
-
436
- return this.tokens;
437
- };
438
-
439
- /**
440
- * Inline-Level Grammar
441
- */
442
-
443
- var inline = {
444
- escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
445
- autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
446
- url: noop,
447
- tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
448
- link: /^!?\[(inside)\]\(href\)/,
449
- reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
450
- nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
451
- strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
452
- em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
453
- code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
454
- br: /^ {2,}\n(?!\s*$)/,
455
- del: noop,
456
- text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
457
- };
458
-
459
- inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
460
- inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
461
-
462
- inline.link = replace(inline.link)
463
- ('inside', inline._inside)
464
- ('href', inline._href)
465
- ();
466
-
467
- inline.reflink = replace(inline.reflink)
468
- ('inside', inline._inside)
469
- ();
470
-
471
- /**
472
- * Normal Inline Grammar
473
- */
474
-
475
- inline.normal = merge({}, inline);
476
-
477
- /**
478
- * Pedantic Inline Grammar
479
- */
480
-
481
- inline.pedantic = merge({}, inline.normal, {
482
- strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
483
- em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
484
- });
485
-
486
- /**
487
- * GFM Inline Grammar
488
- */
489
-
490
- inline.gfm = merge({}, inline.normal, {
491
- escape: replace(inline.escape)('])', '~|])')(),
492
- url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
493
- del: /^~~(?=\S)([\s\S]*?\S)~~/,
494
- text: replace(inline.text)
495
- (']|', '~]|')
496
- ('|', '|https?://|')
497
- ()
498
- });
499
-
500
- /**
501
- * GFM + Line Breaks Inline Grammar
502
- */
503
-
504
- inline.breaks = merge({}, inline.gfm, {
505
- br: replace(inline.br)('{2,}', '*')(),
506
- text: replace(inline.gfm.text)('{2,}', '*')()
507
- });
508
-
509
- /**
510
- * Inline Lexer & Compiler
511
- */
512
-
513
- function InlineLexer(links, options) {
514
- this.options = options || marked.defaults;
515
- this.links = links;
516
- this.rules = inline.normal;
517
-
518
- if (!this.links) {
519
- throw new
520
- Error('Tokens array requires a `links` property.');
521
- }
522
-
523
- if (this.options.gfm) {
524
- if (this.options.breaks) {
525
- this.rules = inline.breaks;
526
- } else {
527
- this.rules = inline.gfm;
528
- }
529
- } else if (this.options.pedantic) {
530
- this.rules = inline.pedantic;
531
- }
532
- }
533
-
534
- /**
535
- * Expose Inline Rules
536
- */
537
-
538
- InlineLexer.rules = inline;
539
-
540
- /**
541
- * Static Lexing/Compiling Method
542
- */
543
-
544
- InlineLexer.output = function (src, links, options) {
545
- var inline = new InlineLexer(links, options);
546
- return inline.output(src);
547
- };
548
-
549
- /**
550
- * Lexing/Compiling
551
- */
552
-
553
- InlineLexer.prototype.output = function (src) {
554
- var out = ''
555
- , link
556
- , text
557
- , href
558
- , cap;
559
-
560
- while (src) {
561
- // escape
562
- if (cap = this.rules.escape.exec(src)) {
563
- src = src.substring(cap[0].length);
564
- out += cap[1];
565
- continue;
566
- }
567
-
568
- // autolink
569
- if (cap = this.rules.autolink.exec(src)) {
570
- src = src.substring(cap[0].length);
571
- if (cap[2] === '@') {
572
- text = cap[1].charAt(6) === ':'
573
- ? this.mangle(cap[1].substring(7))
574
- : this.mangle(cap[1]);
575
- href = this.mangle('mailto:') + text;
576
- } else {
577
- text = escape(cap[1]);
578
- href = text;
579
- }
580
- out += '<a href="'
581
- + href
582
- + '">'
583
- + text
584
- + '</a>';
585
- continue;
586
- }
587
-
588
- // url (gfm)
589
- if (cap = this.rules.url.exec(src)) {
590
- src = src.substring(cap[0].length);
591
- text = escape(cap[1]);
592
- href = text;
593
- out += '<a href="'
594
- + href
595
- + '">'
596
- + text
597
- + '</a>';
598
- continue;
599
- }
600
-
601
- // tag
602
- if (cap = this.rules.tag.exec(src)) {
603
- src = src.substring(cap[0].length);
604
- out += this.options.sanitize
605
- ? escape(cap[0])
606
- : cap[0];
607
- continue;
608
- }
609
-
610
- // link
611
- if (cap = this.rules.link.exec(src)) {
612
- src = src.substring(cap[0].length);
613
- out += this.outputLink(cap, {
614
- href: cap[2],
615
- title: cap[3]
616
- });
617
- continue;
618
- }
619
-
620
- // reflink, nolink
621
- if ((cap = this.rules.reflink.exec(src))
622
- || (cap = this.rules.nolink.exec(src))) {
623
- src = src.substring(cap[0].length);
624
- link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
625
- link = this.links[link.toLowerCase()];
626
- if (!link || !link.href) {
627
- out += cap[0].charAt(0);
628
- src = cap[0].substring(1) + src;
629
- continue;
630
- }
631
- out += this.outputLink(cap, link);
632
- continue;
633
- }
634
-
635
- // strong
636
- if (cap = this.rules.strong.exec(src)) {
637
- src = src.substring(cap[0].length);
638
- out += '<strong>'
639
- + this.output(cap[2] || cap[1])
640
- + '</strong>';
641
- continue;
642
- }
643
-
644
- // em
645
- if (cap = this.rules.em.exec(src)) {
646
- src = src.substring(cap[0].length);
647
- out += '<em>'
648
- + this.output(cap[2] || cap[1])
649
- + '</em>';
650
- continue;
651
- }
652
-
653
- // code
654
- if (cap = this.rules.code.exec(src)) {
655
- src = src.substring(cap[0].length);
656
- out += '<code>'
657
- + escape(cap[2], true)
658
- + '</code>';
659
- continue;
660
- }
661
-
662
- // br
663
- if (cap = this.rules.br.exec(src)) {
664
- src = src.substring(cap[0].length);
665
- out += '<br>';
666
- continue;
667
- }
668
-
669
- // del (gfm)
670
- if (cap = this.rules.del.exec(src)) {
671
- src = src.substring(cap[0].length);
672
- out += '<del>'
673
- + this.output(cap[1])
674
- + '</del>';
675
- continue;
676
- }
677
-
678
- // text
679
- if (cap = this.rules.text.exec(src)) {
680
- src = src.substring(cap[0].length);
681
- out += escape(this.smartypants(cap[0]));
682
- continue;
683
- }
684
-
685
- if (src) {
686
- throw new
687
- Error('Infinite loop on byte: ' + src.charCodeAt(0));
688
- }
689
- }
690
-
691
- return out;
692
- };
693
-
694
- /**
695
- * Compile Link
696
- */
697
-
698
- InlineLexer.prototype.outputLink = function (cap, link) {
699
- if (cap[0].charAt(0) !== '!') {
700
- return '<a href="'
701
- + escape(link.href)
702
- + '"'
703
- + (link.title
704
- ? ' title="'
705
- + escape(link.title)
706
- + '"'
707
- : '')
708
- + '>'
709
- + this.output(cap[1])
710
- + '</a>';
711
- } else {
712
- return '<img src="'
713
- + escape(link.href)
714
- + '" alt="'
715
- + escape(cap[1])
716
- + '"'
717
- + (link.title
718
- ? ' title="'
719
- + escape(link.title)
720
- + '"'
721
- : '')
722
- + '>';
723
- }
724
- };
725
-
726
- /**
727
- * Smartypants Transformations
728
- */
729
-
730
- InlineLexer.prototype.smartypants = function (text) {
731
- if (!this.options.smartypants) return text;
732
- return text
733
- // em-dashes
734
- .replace(/--/g, '\u2014')
735
- // opening singles
736
- .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
737
- // closing singles & apostrophes
738
- .replace(/'/g, '\u2019')
739
- // opening doubles
740
- .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
741
- // closing doubles
742
- .replace(/"/g, '\u201d')
743
- // ellipses
744
- .replace(/\.{3}/g, '\u2026');
745
- };
746
-
747
- /**
748
- * Mangle Links
749
- */
750
-
751
- InlineLexer.prototype.mangle = function (text) {
752
- var out = ''
753
- , l = text.length
754
- , i = 0
755
- , ch;
756
-
757
- for (; i < l; i++) {
758
- ch = text.charCodeAt(i);
759
- if (Math.random() > 0.5) {
760
- ch = 'x' + ch.toString(16);
761
- }
762
- out += '&#' + ch + ';';
763
- }
764
-
765
- return out;
766
- };
767
-
768
- /**
769
- * Parsing & Compiling
770
- */
771
-
772
- function Parser(options) {
773
- this.tokens = [];
774
- this.token = null;
775
- this.options = options || marked.defaults;
776
- }
777
-
778
- /**
779
- * Static Parse Method
780
- */
781
-
782
- Parser.parse = function (src, options) {
783
- var parser = new Parser(options);
784
- return parser.parse(src);
785
- };
786
-
787
- /**
788
- * Parse Loop
789
- */
790
-
791
- Parser.prototype.parse = function (src) {
792
- this.inline = new InlineLexer(src.links, this.options);
793
- this.tokens = src.reverse();
794
-
795
- var out = '';
796
- while (this.next()) {
797
- out += this.tok();
798
- }
799
-
800
- return out;
801
- };
802
-
803
- /**
804
- * Next Token
805
- */
806
-
807
- Parser.prototype.next = function () {
808
- return this.token = this.tokens.pop();
809
- };
810
-
811
- /**
812
- * Preview Next Token
813
- */
814
-
815
- Parser.prototype.peek = function () {
816
- return this.tokens[this.tokens.length - 1] || 0;
817
- };
818
-
819
- /**
820
- * Parse Text Tokens
821
- */
822
-
823
- Parser.prototype.parseText = function () {
824
- var body = this.token.text;
825
-
826
- while (this.peek().type === 'text') {
827
- body += '\n' + this.next().text;
828
- }
829
-
830
- return this.inline.output(body);
831
- };
832
-
833
- /**
834
- * Parse Current Token
835
- */
836
-
837
- Parser.prototype.tok = function () {
838
- switch (this.token.type) {
839
- case 'space':
840
- {
841
- return '';
842
- }
843
- case 'hr':
844
- {
845
- return '<hr>\n';
846
- }
847
- case 'heading':
848
- {
849
- return '<h'
850
- + this.token.depth
851
- + ' id="'
852
- + this.token.text.toLowerCase().replace(/[^\w]+/g, '-')
853
- + '">'
854
- + this.inline.output(this.token.text)
855
- + '</h'
856
- + this.token.depth
857
- + '>\n';
858
- }
859
- case 'code':
860
- {
861
- if (this.options.highlight) {
862
- var code = this.options.highlight(this.token.text, this.token.lang);
863
- if (code != null && code !== this.token.text) {
864
- this.token.escaped = true;
865
- this.token.text = code;
866
- }
867
- }
868
-
869
- if (!this.token.escaped) {
870
- this.token.text = escape(this.token.text, true);
871
- }
872
-
873
- return '<pre><code'
874
- + (this.token.lang
875
- ? ' class="'
876
- + this.options.langPrefix
877
- + this.token.lang
878
- + '"'
879
- : '')
880
- + '>'
881
- + this.token.text
882
- + '</code></pre>\n';
883
- }
884
- case 'table':
885
- {
886
- var body = ''
887
- , heading
888
- , i
889
- , row
890
- , cell
891
- , j;
892
-
893
- // header
894
- body += '<thead>\n<tr>\n';
895
- for (i = 0; i < this.token.header.length; i++) {
896
- heading = this.inline.output(this.token.header[i]);
897
- body += '<th';
898
- if (this.token.align[i]) {
899
- body += ' style="text-align:' + this.token.align[i] + '"';
900
- }
901
- body += '>' + heading + '</th>\n';
902
- }
903
- body += '</tr>\n</thead>\n';
904
-
905
- // body
906
- body += '<tbody>\n'
907
- for (i = 0; i < this.token.cells.length; i++) {
908
- row = this.token.cells[i];
909
- body += '<tr>\n';
910
- for (j = 0; j < row.length; j++) {
911
- cell = this.inline.output(row[j]);
912
- body += '<td';
913
- if (this.token.align[j]) {
914
- body += ' style="text-align:' + this.token.align[j] + '"';
915
- }
916
- body += '>' + cell + '</td>\n';
917
- }
918
- body += '</tr>\n';
919
- }
920
- body += '</tbody>\n';
921
-
922
- return '<table>\n'
923
- + body
924
- + '</table>\n';
925
- }
926
- case 'blockquote_start':
927
- {
928
- var body = '';
929
-
930
- while (this.next().type !== 'blockquote_end') {
931
- body += this.tok();
932
- }
933
-
934
- return '<blockquote>\n'
935
- + body
936
- + '</blockquote>\n';
937
- }
938
- case 'list_start':
939
- {
940
- var type = this.token.ordered ? 'ol' : 'ul'
941
- , body = '';
942
-
943
- while (this.next().type !== 'list_end') {
944
- body += this.tok();
945
- }
946
-
947
- return '<'
948
- + type
949
- + '>\n'
950
- + body
951
- + '</'
952
- + type
953
- + '>\n';
954
- }
955
- case 'list_item_start':
956
- {
957
- var body = '';
958
-
959
- while (this.next().type !== 'list_item_end') {
960
- body += this.token.type === 'text'
961
- ? this.parseText()
962
- : this.tok();
963
- }
964
-
965
- return '<li>'
966
- + body
967
- + '</li>\n';
968
- }
969
- case 'loose_item_start':
970
- {
971
- var body = '';
972
-
973
- while (this.next().type !== 'list_item_end') {
974
- body += this.tok();
975
- }
976
-
977
- return '<li>'
978
- + body
979
- + '</li>\n';
980
- }
981
- case 'html':
982
- {
983
- return !this.token.pre && !this.options.pedantic
984
- ? this.inline.output(this.token.text)
985
- : this.token.text;
986
- }
987
- case 'paragraph':
988
- {
989
- return '<p>'
990
- + this.inline.output(this.token.text)
991
- + '</p>\n';
992
- }
993
- case 'text':
994
- {
995
- return '<p>'
996
- + this.parseText()
997
- + '</p>\n';
998
- }
999
- }
1000
- };
1001
-
1002
- /**
1003
- * Helpers
1004
- */
1005
-
1006
- function escape(html, encode) {
1007
- return html
1008
- .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
1009
- .replace(/</g, '&lt;')
1010
- .replace(/>/g, '&gt;')
1011
- .replace(/"/g, '&quot;')
1012
- .replace(/'/g, '&#39;');
1013
- }
1014
-
1015
- function replace(regex, opt) {
1016
- regex = regex.source;
1017
- opt = opt || '';
1018
- return function self(name, val) {
1019
- if (!name) return new RegExp(regex, opt);
1020
- val = val.source || val;
1021
- val = val.replace(/(^|[^\[])\^/g, '$1');
1022
- regex = regex.replace(name, val);
1023
- return self;
1024
- };
1025
- }
1026
-
1027
- function noop() {
1028
- }
1029
-
1030
- noop.exec = noop;
1031
-
1032
- function merge(obj) {
1033
- var i = 1
1034
- , target
1035
- , key;
1036
-
1037
- for (; i < arguments.length; i++) {
1038
- target = arguments[i];
1039
- for (key in target) {
1040
- if (Object.prototype.hasOwnProperty.call(target, key)) {
1041
- obj[key] = target[key];
1042
- }
1043
- }
1044
- }
1045
-
1046
- return obj;
1047
- }
1048
-
1049
- /**
1050
- * Marked
1051
- */
1052
-
1053
- function marked(src, opt, callback) {
1054
- if (callback || typeof opt === 'function') {
1055
- if (!callback) {
1056
- callback = opt;
1057
- opt = null;
1058
- }
1059
-
1060
- opt = merge({}, marked.defaults, opt || {});
1061
-
1062
- var highlight = opt.highlight
1063
- , tokens
1064
- , pending
1065
- , i = 0;
1066
-
1067
- try {
1068
- tokens = Lexer.lex(src, opt)
1069
- } catch (e) {
1070
- return callback(e);
1071
- }
1072
-
1073
- pending = tokens.length;
1074
-
1075
- var done = function () {
1076
- var out, err;
1077
-
1078
- try {
1079
- out = Parser.parse(tokens, opt);
1080
- } catch (e) {
1081
- err = e;
1082
- }
1083
-
1084
- opt.highlight = highlight;
1085
-
1086
- return err
1087
- ? callback(err)
1088
- : callback(null, out);
1089
- };
1090
-
1091
- if (!highlight || highlight.length < 3) {
1092
- return done();
1093
- }
1094
-
1095
- delete opt.highlight;
1096
-
1097
- if (!pending) return done();
1098
-
1099
- for (; i < tokens.length; i++) {
1100
- (function (token) {
1101
- if (token.type !== 'code') {
1102
- return --pending || done();
1103
- }
1104
- return highlight(token.text, token.lang, function (err, code) {
1105
- if (code == null || code === token.text) {
1106
- return --pending || done();
1107
- }
1108
- token.text = code;
1109
- token.escaped = true;
1110
- --pending || done();
1111
- });
1112
- })(tokens[i]);
1113
- }
1114
-
1115
- return;
1116
- }
1117
- try {
1118
- if (opt) opt = merge({}, marked.defaults, opt);
1119
- return Parser.parse(Lexer.lex(src, opt), opt);
1120
- } catch (e) {
1121
- e.message += '\nPlease report this to https://github.com/chjj/marked.';
1122
- if ((opt || marked.defaults).silent) {
1123
- return '<p>An error occured:</p><pre>'
1124
- + escape(e.message + '', true)
1125
- + '</pre>';
1126
- }
1127
- throw e;
1128
- }
1129
- }
1130
-
1131
- /**
1132
- * Options
1133
- */
1134
-
1135
- marked.options =
1136
- marked.setOptions = function (opt) {
1137
- merge(marked.defaults, opt);
1138
- return marked;
1139
- };
1140
-
1141
- marked.defaults = {
1142
- gfm: true,
1143
- tables: true,
1144
- breaks: false,
1145
- pedantic: false,
1146
- sanitize: false,
1147
- smartLists: false,
1148
- silent: false,
1149
- highlight: null,
1150
- langPrefix: 'lang-',
1151
- smartypants: false
1152
- };
1153
-
1154
- /**
1155
- * Expose
1156
- */
1157
-
1158
- marked.Parser = Parser;
1159
- marked.parser = Parser.parse;
1160
-
1161
- marked.Lexer = Lexer;
1162
- marked.lexer = Lexer.lex;
1163
-
1164
- marked.InlineLexer = InlineLexer;
1165
- marked.inlineLexer = InlineLexer.output;
1166
-
1167
- marked.parse = marked;
1168
-
1169
- if (typeof exports === 'object') {
1170
- module.exports = marked;
1171
- } else if (typeof define === 'function' && define.amd) {
1172
- define(function () {
1173
- return marked;
1174
- });
1175
- } else {
1176
- this.marked = marked;
1177
- }
1178
-
1179
- }).call(function () {
1180
- return this || (typeof window !== 'undefined' ? window : global);
1181
- }());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/markdown/adminhtml/epiceditor.js ADDED
@@ -0,0 +1,1896 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * EpicEditor - An Embeddable JavaScript Markdown Editor (https://github.com/OscarGodson/EpicEditor)
3
+ * Copyright (c) 2011-2012, Oscar Godson. (MIT Licensed)
4
+ *
5
+ * @SchumacherFM: Version without marked() which is included in seperate file
6
+ */
7
+
8
+ (function (window, undefined) {
9
+ /**
10
+ * Applies attributes to a DOM object
11
+ * @param {object} context The DOM obj you want to apply the attributes to
12
+ * @param {object} attrs A key/value pair of attributes you want to apply
13
+ * @returns {undefined}
14
+ */
15
+ function _applyAttrs(context, attrs) {
16
+ for (var attr in attrs) {
17
+ if (attrs.hasOwnProperty(attr)) {
18
+ context.setAttribute(attr, attrs[attr]);
19
+ }
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Applies styles to a DOM object
25
+ * @param {object} context The DOM obj you want to apply the attributes to
26
+ * @param {object} attrs A key/value pair of attributes you want to apply
27
+ * @returns {undefined}
28
+ */
29
+ function _applyStyles(context, attrs) {
30
+ for (var attr in attrs) {
31
+ if (attrs.hasOwnProperty(attr)) {
32
+ context.style[attr] = attrs[attr];
33
+ }
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Returns a DOM objects computed style
39
+ * @param {object} el The element you want to get the style from
40
+ * @param {string} styleProp The property you want to get from the element
41
+ * @returns {string} Returns a string of the value. If property is not set it will return a blank string
42
+ */
43
+ function _getStyle(el, styleProp) {
44
+ var x = el
45
+ , y = null;
46
+ if (window.getComputedStyle) {
47
+ y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
48
+ }
49
+ else if (x.currentStyle) {
50
+ y = x.currentStyle[styleProp];
51
+ }
52
+ return y;
53
+ }
54
+
55
+ /**
56
+ * Saves the current style state for the styles requested, then applies styles
57
+ * to overwrite the existing one. The old styles are returned as an object so
58
+ * you can pass it back in when you want to revert back to the old style
59
+ * @param {object} el The element to get the styles of
60
+ * @param {string} type Can be "save" or "apply". apply will just apply styles you give it. Save will write styles
61
+ * @param {object} styles Key/value style/property pairs
62
+ * @returns {object}
63
+ */
64
+ function _saveStyleState(el, type, styles) {
65
+ var returnState = {}
66
+ , style;
67
+ if (type === 'save') {
68
+ for (style in styles) {
69
+ if (styles.hasOwnProperty(style)) {
70
+ returnState[style] = _getStyle(el, style);
71
+ }
72
+ }
73
+ // After it's all done saving all the previous states, change the styles
74
+ _applyStyles(el, styles);
75
+ }
76
+ else if (type === 'apply') {
77
+ _applyStyles(el, styles);
78
+ }
79
+ return returnState;
80
+ }
81
+
82
+ /**
83
+ * Gets an elements total width including it's borders and padding
84
+ * @param {object} el The element to get the total width of
85
+ * @returns {int}
86
+ */
87
+ function _outerWidth(el) {
88
+ var b = parseInt(_getStyle(el, 'border-left-width'), 10) + parseInt(_getStyle(el, 'border-right-width'), 10)
89
+ , p = parseInt(_getStyle(el, 'padding-left'), 10) + parseInt(_getStyle(el, 'padding-right'), 10)
90
+ , w = el.offsetWidth
91
+ , t;
92
+ // For IE in case no border is set and it defaults to "medium"
93
+ if (isNaN(b)) { b = 0; }
94
+ t = b + p + w;
95
+ return t;
96
+ }
97
+
98
+ /**
99
+ * Gets an elements total height including it's borders and padding
100
+ * @param {object} el The element to get the total width of
101
+ * @returns {int}
102
+ */
103
+ function _outerHeight(el) {
104
+ var b = parseInt(_getStyle(el, 'border-top-width'), 10) + parseInt(_getStyle(el, 'border-bottom-width'), 10)
105
+ , p = parseInt(_getStyle(el, 'padding-top'), 10) + parseInt(_getStyle(el, 'padding-bottom'), 10)
106
+ , w = parseInt(_getStyle(el, 'height'), 10)
107
+ , t;
108
+ // For IE in case no border is set and it defaults to "medium"
109
+ if (isNaN(b)) { b = 0; }
110
+ t = b + p + w;
111
+ return t;
112
+ }
113
+
114
+ /**
115
+ * Inserts a <link> tag specifically for CSS
116
+ * @param {string} path The path to the CSS file
117
+ * @param {object} context In what context you want to apply this to (document, iframe, etc)
118
+ * @param {string} id An id for you to reference later for changing properties of the <link>
119
+ * @returns {undefined}
120
+ */
121
+ function _insertCSSLink(path, context, id) {
122
+ id = id || '';
123
+ var headID = context.getElementsByTagName("head")[0]
124
+ , cssNode = context.createElement('link');
125
+
126
+ _applyAttrs(cssNode, {
127
+ type: 'text/css'
128
+ , id: id
129
+ , rel: 'stylesheet'
130
+ , href: path
131
+ , name: path
132
+ , media: 'screen'
133
+ });
134
+
135
+ headID.appendChild(cssNode);
136
+ }
137
+
138
+ // Simply replaces a class (o), to a new class (n) on an element provided (e)
139
+ function _replaceClass(e, o, n) {
140
+ e.className = e.className.replace(o, n);
141
+ }
142
+
143
+ // Feature detects an iframe to get the inner document for writing to
144
+ function _getIframeInnards(el) {
145
+ return el.contentDocument || el.contentWindow.document;
146
+ }
147
+
148
+ // Grabs the text from an element and preserves whitespace
149
+ function _getText(el) {
150
+ var theText;
151
+ // Make sure to check for type of string because if the body of the page
152
+ // doesn't have any text it'll be "" which is falsey and will go into
153
+ // the else which is meant for Firefox and shit will break
154
+ if (typeof document.body.innerText == 'string') {
155
+ theText = el.innerText;
156
+ }
157
+ else {
158
+ // First replace <br>s before replacing the rest of the HTML
159
+ theText = el.innerHTML.replace(/<br>/gi, "\n");
160
+ // Now we can clean the HTML
161
+ theText = theText.replace(/<(?:.|\n)*?>/gm, '');
162
+ // Now fix HTML entities
163
+ theText = theText.replace(/&lt;/gi, '<');
164
+ theText = theText.replace(/&gt;/gi, '>');
165
+ }
166
+ return theText;
167
+ }
168
+
169
+ function _setText(el, content) {
170
+ // Don't convert lt/gt characters as HTML when viewing the editor window
171
+ // TODO: Write a test to catch regressions for this
172
+ content = content.replace(/</g, '&lt;');
173
+ content = content.replace(/>/g, '&gt;');
174
+ content = content.replace(/\n/g, '<br>');
175
+
176
+ // Make sure to there aren't two spaces in a row (replace one with &nbsp;)
177
+ // If you find and replace every space with a &nbsp; text will not wrap.
178
+ // Hence the name (Non-Breaking-SPace).
179
+ // TODO: Probably need to test this somehow...
180
+ content = content.replace(/<br>\s/g, '<br>&nbsp;')
181
+ content = content.replace(/\s\s\s/g, '&nbsp; &nbsp;')
182
+ content = content.replace(/\s\s/g, '&nbsp; ')
183
+ content = content.replace(/^ /, '&nbsp;')
184
+
185
+ el.innerHTML = content;
186
+ return true;
187
+ }
188
+
189
+ /**
190
+ * Converts the 'raw' format of a file's contents into plaintext
191
+ * @param {string} content Contents of the file
192
+ * @returns {string} the sanitized content
193
+ */
194
+ function _sanitizeRawContent(content) {
195
+ // Get this, 2 spaces in a content editable actually converts to:
196
+ // 0020 00a0, meaning, "space no-break space". So, manually convert
197
+ // no-break spaces to spaces again before handing to marked.
198
+ // Also, WebKit converts no-break to unicode equivalent and FF HTML.
199
+ return content.replace(/\u00a0/g, ' ').replace(/&nbsp;/g, ' ');
200
+ }
201
+
202
+ /**
203
+ * Will return the version number if the browser is IE. If not will return -1
204
+ * TRY NEVER TO USE THIS AND USE FEATURE DETECTION IF POSSIBLE
205
+ * @returns {Number} -1 if false or the version number if true
206
+ */
207
+ function _isIE() {
208
+ var rv = -1 // Return value assumes failure.
209
+ , ua = navigator.userAgent
210
+ , re;
211
+ if (navigator.appName == 'Microsoft Internet Explorer') {
212
+ re = /MSIE ([0-9]{1,}[\.0-9]{0,})/;
213
+ if (re.exec(ua) != null) {
214
+ rv = parseFloat(RegExp.$1, 10);
215
+ }
216
+ }
217
+ return rv;
218
+ }
219
+
220
+ /**
221
+ * Same as the isIE(), but simply returns a boolean
222
+ * THIS IS TERRIBLE AND IS ONLY USED BECAUSE FULLSCREEN IN SAFARI IS BORKED
223
+ * If some other engine uses WebKit and has support for fullscreen they
224
+ * probably wont get native fullscreen until Safari's fullscreen is fixed
225
+ * @returns {Boolean} true if Safari
226
+ */
227
+ function _isSafari() {
228
+ var n = window.navigator;
229
+ return n.userAgent.indexOf('Safari') > -1 && n.userAgent.indexOf('Chrome') == -1;
230
+ }
231
+
232
+ /**
233
+ * Same as the isIE(), but simply returns a boolean
234
+ * THIS IS TERRIBLE ONLY USE IF ABSOLUTELY NEEDED
235
+ * @returns {Boolean} true if Safari
236
+ */
237
+ function _isFirefox() {
238
+ var n = window.navigator;
239
+ return n.userAgent.indexOf('Firefox') > -1 && n.userAgent.indexOf('Seamonkey') == -1;
240
+ }
241
+
242
+ /**
243
+ * Determines if supplied value is a function
244
+ * @param {object} object to determine type
245
+ */
246
+ function _isFunction(functionToCheck) {
247
+ var getType = {};
248
+ return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
249
+ }
250
+
251
+ /**
252
+ * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
253
+ * @param {boolean} [deepMerge=false] If true, will deep merge meaning it will merge sub-objects like {obj:obj2{foo:'bar'}}
254
+ * @param {object} first object
255
+ * @param {object} second object
256
+ * @returnss {object} a new object based on obj1 and obj2
257
+ */
258
+ function _mergeObjs() {
259
+ // copy reference to target object
260
+ var target = arguments[0] || {}
261
+ , i = 1
262
+ , length = arguments.length
263
+ , deep = false
264
+ , options
265
+ , name
266
+ , src
267
+ , copy
268
+
269
+ // Handle a deep copy situation
270
+ if (typeof target === "boolean") {
271
+ deep = target;
272
+ target = arguments[1] || {};
273
+ // skip the boolean and the target
274
+ i = 2;
275
+ }
276
+
277
+ // Handle case when target is a string or something (possible in deep copy)
278
+ if (typeof target !== "object" && !_isFunction(target)) {
279
+ target = {};
280
+ }
281
+ // extend jQuery itself if only one argument is passed
282
+ if (length === i) {
283
+ target = this;
284
+ --i;
285
+ }
286
+
287
+ for (; i < length; i++) {
288
+ // Only deal with non-null/undefined values
289
+ if ((options = arguments[i]) != null) {
290
+ // Extend the base object
291
+ for (name in options) {
292
+ // @NOTE: added hasOwnProperty check
293
+ if (options.hasOwnProperty(name)) {
294
+ src = target[name];
295
+ copy = options[name];
296
+ // Prevent never-ending loop
297
+ if (target === copy) {
298
+ continue;
299
+ }
300
+ // Recurse if we're merging object values
301
+ if (deep && copy && typeof copy === "object" && !copy.nodeType) {
302
+ target[name] = _mergeObjs(deep,
303
+ // Never move original objects, clone them
304
+ src || (copy.length != null ? [] : {})
305
+ , copy);
306
+ } else if (copy !== undefined) { // Don't bring in undefined values
307
+ target[name] = copy;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ // Return the modified object
315
+ return target;
316
+ }
317
+
318
+ /**
319
+ * Initiates the EpicEditor object and sets up offline storage as well
320
+ * @class Represents an EpicEditor instance
321
+ * @param {object} options An optional customization object
322
+ * @returns {object} EpicEditor will be returned
323
+ */
324
+ function EpicEditor(options) {
325
+ // Default settings will be overwritten/extended by options arg
326
+ var self = this
327
+ , opts = options || {}
328
+ , _defaultFileSchema
329
+ , _defaultFile
330
+ , defaults = { container: 'epiceditor'
331
+ , basePath: 'epiceditor'
332
+ , textarea: undefined
333
+ , clientSideStorage: true
334
+ , localStorageName: 'epiceditor'
335
+ , useNativeFullscreen: true
336
+ , file: { name: null
337
+ , defaultContent: ''
338
+ , autoSave: 100 // Set to false for no auto saving
339
+ }
340
+ , theme: { base: '/themes/base/epiceditor.css'
341
+ , preview: '/themes/preview/github.css'
342
+ , editor: '/themes/editor/epic-dark.css'
343
+ }
344
+ , focusOnLoad: false
345
+ , shortcut: { modifier: 18 // alt keycode
346
+ , fullscreen: 70 // f keycode
347
+ , preview: 80 // p keycode
348
+ }
349
+ , string: { togglePreview: 'Toggle Preview Mode'
350
+ , toggleEdit: 'Toggle Edit Mode'
351
+ , toggleFullscreen: 'Enter Fullscreen'
352
+ }
353
+ , parser: typeof marked == 'function' ? marked : null
354
+ , autogrow: false
355
+ , button: { fullscreen: true
356
+ , preview: true
357
+ , bar: "auto"
358
+ }
359
+ }
360
+ , defaultStorage
361
+ , autogrowDefaults = { minHeight: 80
362
+ , maxHeight: false
363
+ , scroll: true
364
+ };
365
+
366
+ self.settings = _mergeObjs(true, defaults, opts);
367
+
368
+ var buttons = self.settings.button;
369
+ self._fullscreenEnabled = typeof(buttons) === 'object' ? typeof buttons.fullscreen === 'undefined' || buttons.fullscreen : buttons === true;
370
+ self._editEnabled = typeof(buttons) === 'object' ? typeof buttons.edit === 'undefined' || buttons.edit : buttons === true;
371
+ self._previewEnabled = typeof(buttons) === 'object' ? typeof buttons.preview === 'undefined' || buttons.preview : buttons === true;
372
+
373
+ if (!(typeof self.settings.parser == 'function' && typeof self.settings.parser('TEST') == 'string')) {
374
+ self.settings.parser = function (str) {
375
+ return str;
376
+ }
377
+ }
378
+
379
+ if (self.settings.autogrow) {
380
+ if (self.settings.autogrow === true) {
381
+ self.settings.autogrow = autogrowDefaults;
382
+ }
383
+ else {
384
+ self.settings.autogrow = _mergeObjs(true, autogrowDefaults, self.settings.autogrow);
385
+ }
386
+ self._oldHeight = -1;
387
+ }
388
+
389
+ // If you put an absolute link as the path of any of the themes ignore the basePath
390
+ // preview theme
391
+ if (!self.settings.theme.preview.match(/^https?:\/\//)) {
392
+ self.settings.theme.preview = self.settings.basePath + self.settings.theme.preview;
393
+ }
394
+ // editor theme
395
+ if (!self.settings.theme.editor.match(/^https?:\/\//)) {
396
+ self.settings.theme.editor = self.settings.basePath + self.settings.theme.editor;
397
+ }
398
+ // base theme
399
+ if (!self.settings.theme.base.match(/^https?:\/\//)) {
400
+ self.settings.theme.base = self.settings.basePath + self.settings.theme.base;
401
+ }
402
+
403
+ // Grab the container element and save it to self.element
404
+ // if it's a string assume it's an ID and if it's an object
405
+ // assume it's a DOM element
406
+ if (typeof self.settings.container == 'string') {
407
+ self.element = document.getElementById(self.settings.container);
408
+ }
409
+ else if (typeof self.settings.container == 'object') {
410
+ self.element = self.settings.container;
411
+ }
412
+
413
+ if (typeof self.settings.textarea == 'undefined' && typeof self.element != 'undefined') {
414
+ var textareas = self.element.getElementsByTagName('textarea');
415
+ if (textareas.length > 0) {
416
+ self.settings.textarea = textareas[0];
417
+ _applyStyles(self.settings.textarea, {
418
+ display: 'none'
419
+ });
420
+ }
421
+ }
422
+
423
+ // Figure out the file name. If no file name is given we'll use the ID.
424
+ // If there's no ID either we'll use a namespaced file name that's incremented
425
+ // based on the calling order. As long as it doesn't change, drafts will be saved.
426
+ if (!self.settings.file.name) {
427
+ if (typeof self.settings.container == 'string') {
428
+ self.settings.file.name = self.settings.container;
429
+ }
430
+ else if (typeof self.settings.container == 'object') {
431
+ if (self.element.id) {
432
+ self.settings.file.name = self.element.id;
433
+ }
434
+ else {
435
+ if (!EpicEditor._data.unnamedEditors) {
436
+ EpicEditor._data.unnamedEditors = [];
437
+ }
438
+ EpicEditor._data.unnamedEditors.push(self);
439
+ self.settings.file.name = '__epiceditor-untitled-' + EpicEditor._data.unnamedEditors.length;
440
+ }
441
+ }
442
+ }
443
+
444
+ if (self.settings.button.bar === "show") {
445
+ self.settings.button.bar = true;
446
+ }
447
+
448
+ if (self.settings.button.bar === "hide") {
449
+ self.settings.button.bar = false;
450
+ }
451
+
452
+ // Protect the id and overwrite if passed in as an option
453
+ // TODO: Put underscrore to denote that this is private
454
+ self._instanceId = 'epiceditor-' + Math.round(Math.random() * 100000);
455
+ self._storage = {};
456
+ self._canSave = true;
457
+
458
+ // Setup local storage of files
459
+ self._defaultFileSchema = function () {
460
+ return {
461
+ content: self.settings.file.defaultContent
462
+ , created: new Date()
463
+ , modified: new Date()
464
+ }
465
+ }
466
+
467
+ if (localStorage && self.settings.clientSideStorage) {
468
+ this._storage = localStorage;
469
+ if (this._storage[self.settings.localStorageName] && self.getFiles(self.settings.file.name) === undefined) {
470
+ _defaultFile = self._defaultFileSchema();
471
+ _defaultFile.content = self.settings.file.defaultContent;
472
+ }
473
+ }
474
+
475
+ if (!this._storage[self.settings.localStorageName]) {
476
+ defaultStorage = {};
477
+ defaultStorage[self.settings.file.name] = self._defaultFileSchema();
478
+ defaultStorage = JSON.stringify(defaultStorage);
479
+ this._storage[self.settings.localStorageName] = defaultStorage;
480
+ }
481
+
482
+ // A string to prepend files with to save draft versions of files
483
+ // and reset all preview drafts on each load!
484
+ self._previewDraftLocation = '__draft-';
485
+ self._storage[self._previewDraftLocation + self.settings.localStorageName] = self._storage[self.settings.localStorageName];
486
+
487
+ // This needs to replace the use of classes to check the state of EE
488
+ self._eeState = {
489
+ fullscreen: false
490
+ , preview: false
491
+ , edit: false
492
+ , loaded: false
493
+ , unloaded: false
494
+ }
495
+
496
+ // Now that it exists, allow binding of events if it doesn't exist yet
497
+ if (!self.events) {
498
+ self.events = {};
499
+ }
500
+
501
+ return this;
502
+ }
503
+
504
+ /**
505
+ * Inserts the EpicEditor into the DOM via an iframe and gets it ready for editing and previewing
506
+ * @returns {object} EpicEditor will be returned
507
+ */
508
+ EpicEditor.prototype.load = function (callback) {
509
+
510
+ // Get out early if it's already loaded
511
+ if (this.is('loaded')) { return this; }
512
+
513
+ // TODO: Gotta get the privates with underscores!
514
+ // TODO: Gotta document what these are for...
515
+ var self = this
516
+ , _HtmlTemplates
517
+ , iframeElement
518
+ , baseTag
519
+ , utilBtns
520
+ , utilBar
521
+ , utilBarTimer
522
+ , keypressTimer
523
+ , mousePos = { y: -1, x: -1 }
524
+ , _elementStates
525
+ , _isInEdit
526
+ , nativeFs = false
527
+ , nativeFsWebkit = false
528
+ , nativeFsMoz = false
529
+ , nativeFsW3C = false
530
+ , fsElement
531
+ , isMod = false
532
+ , isCtrl = false
533
+ , eventableIframes
534
+ , i // i is reused for loops
535
+ , boundAutogrow;
536
+
537
+ // Startup is a way to check if this EpicEditor is starting up. Useful for
538
+ // checking and doing certain things before EpicEditor emits a load event.
539
+ self._eeState.startup = true;
540
+
541
+ if (self.settings.useNativeFullscreen) {
542
+ nativeFsWebkit = document.body.webkitRequestFullScreen ? true : false;
543
+ nativeFsMoz = document.body.mozRequestFullScreen ? true : false;
544
+ nativeFsW3C = document.body.requestFullscreen ? true : false;
545
+ nativeFs = nativeFsWebkit || nativeFsMoz || nativeFsW3C;
546
+ }
547
+
548
+ // Fucking Safari's native fullscreen works terribly
549
+ // REMOVE THIS IF SAFARI 7 WORKS BETTER
550
+ if (_isSafari()) {
551
+ nativeFs = false;
552
+ nativeFsWebkit = false;
553
+ }
554
+
555
+ // It opens edit mode by default (for now);
556
+ if (!self.is('edit') && !self.is('preview')) {
557
+ self._eeState.edit = true;
558
+ }
559
+
560
+ callback = callback || function () {};
561
+
562
+ // The editor HTML
563
+ // TODO: edit-mode class should be dynamically added
564
+ _HtmlTemplates = {
565
+ // This is wrapping iframe element. It contains the other two iframes and the utilbar
566
+ chrome: '<div id="epiceditor-wrapper" class="epiceditor-edit-mode">' +
567
+ '<iframe frameborder="0" id="epiceditor-editor-frame"></iframe>' +
568
+ '<iframe frameborder="0" id="epiceditor-previewer-frame"></iframe>' +
569
+ '<div id="epiceditor-utilbar">' +
570
+ (self._previewEnabled ? '<button title="' + this.settings.string.togglePreview + '" class="epiceditor-toggle-btn epiceditor-toggle-preview-btn"></button> ' : '') +
571
+ (self._editEnabled ? '<button title="' + this.settings.string.toggleEdit + '" class="epiceditor-toggle-btn epiceditor-toggle-edit-btn"></button> ' : '') +
572
+ (self._fullscreenEnabled ? '<button title="' + this.settings.string.toggleFullscreen + '" class="epiceditor-fullscreen-btn"></button>' : '') +
573
+ '</div>' +
574
+ '</div>'
575
+
576
+ // The previewer is just an empty box for the generated HTML to go into
577
+ , previewer: '<div id="epiceditor-preview"></div>'
578
+ , editor: '<!doctype HTML>'
579
+ };
580
+
581
+ // Write an iframe and then select it for the editor
582
+ iframeElement = document.createElement('iframe');
583
+ _applyAttrs(iframeElement, {
584
+ scrolling: 'no',
585
+ frameborder: 0,
586
+ id: self._instanceId
587
+ });
588
+
589
+
590
+ self.element.appendChild(iframeElement);
591
+
592
+ // Because browsers add things like invisible padding and margins and stuff
593
+ // to iframes, we need to set manually set the height so that the height
594
+ // doesn't keep increasing (by 2px?) every time reflow() is called.
595
+ // FIXME: Figure out how to fix this without setting this
596
+ self.element.style.height = self.element.offsetHeight + 'px';
597
+
598
+ // Store a reference to the iframeElement itself
599
+ self.iframeElement = iframeElement;
600
+
601
+ // Grab the innards of the iframe (returns the document.body)
602
+ // TODO: Change self.iframe to self.iframeDocument
603
+ self.iframe = _getIframeInnards(iframeElement);
604
+ self.iframe.open();
605
+ self.iframe.write(_HtmlTemplates.chrome);
606
+
607
+ // Now that we got the innards of the iframe, we can grab the other iframes
608
+ self.editorIframe = self.iframe.getElementById('epiceditor-editor-frame')
609
+ self.previewerIframe = self.iframe.getElementById('epiceditor-previewer-frame');
610
+
611
+ // Setup the editor iframe
612
+ self.editorIframeDocument = _getIframeInnards(self.editorIframe);
613
+ self.editorIframeDocument.open();
614
+ // Need something for... you guessed it, Firefox
615
+ self.editorIframeDocument.write(_HtmlTemplates.editor);
616
+ self.editorIframeDocument.close();
617
+
618
+ // Setup the previewer iframe
619
+ self.previewerIframeDocument = _getIframeInnards(self.previewerIframe);
620
+ self.previewerIframeDocument.open();
621
+ self.previewerIframeDocument.write(_HtmlTemplates.previewer);
622
+
623
+ // Base tag is added so that links will open a new tab and not inside of the iframes
624
+ baseTag = self.previewerIframeDocument.createElement('base');
625
+ baseTag.target = '_blank';
626
+ self.previewerIframeDocument.getElementsByTagName('head')[0].appendChild(baseTag);
627
+
628
+ self.previewerIframeDocument.close();
629
+
630
+ self.reflow();
631
+
632
+ // Insert Base Stylesheet
633
+ _insertCSSLink(self.settings.theme.base, self.iframe, 'theme');
634
+
635
+ // Insert Editor Stylesheet
636
+ _insertCSSLink(self.settings.theme.editor, self.editorIframeDocument, 'theme');
637
+
638
+ // Insert Previewer Stylesheet
639
+ _insertCSSLink(self.settings.theme.preview, self.previewerIframeDocument, 'theme');
640
+
641
+ // Add a relative style to the overall wrapper to keep CSS relative to the editor
642
+ self.iframe.getElementById('epiceditor-wrapper').style.position = 'relative';
643
+
644
+ // Set the position to relative so we hide them with left: -999999px
645
+ self.editorIframe.style.position = 'absolute';
646
+ self.previewerIframe.style.position = 'absolute';
647
+
648
+ // Now grab the editor and previewer for later use
649
+ self.editor = self.editorIframeDocument.body;
650
+ self.previewer = self.previewerIframeDocument.getElementById('epiceditor-preview');
651
+
652
+ self.editor.contentEditable = true;
653
+
654
+ // Firefox's <body> gets all fucked up so, to be sure, we need to hardcode it
655
+ self.iframe.body.style.height = this.element.offsetHeight + 'px';
656
+
657
+ // Should actually check what mode it's in!
658
+ self.previewerIframe.style.left = '-999999px';
659
+
660
+ // Keep long lines from being longer than the editor
661
+ this.editorIframeDocument.body.style.wordWrap = 'break-word';
662
+
663
+ // FIXME figure out why it needs +2 px
664
+ if (_isIE() > -1) {
665
+ this.previewer.style.height = parseInt(_getStyle(this.previewer, 'height'), 10) + 2;
666
+ }
667
+
668
+ // If there is a file to be opened with that filename and it has content...
669
+ this.open(self.settings.file.name);
670
+
671
+ if (self.settings.focusOnLoad) {
672
+ // We need to wait until all three iframes are done loading by waiting until the parent
673
+ // iframe's ready state == complete, then we can focus on the contenteditable
674
+ self.iframe.addEventListener('readystatechange', function () {
675
+ if (self.iframe.readyState == 'complete') {
676
+ self.focus();
677
+ }
678
+ });
679
+ }
680
+
681
+ // Because IE scrolls the whole window to hash links, we need our own
682
+ // method of scrolling the iframe to an ID from clicking a hash
683
+ self.previewerIframeDocument.addEventListener('click', function (e) {
684
+ var el = e.target
685
+ , body = self.previewerIframeDocument.body;
686
+ if (el.nodeName == 'A') {
687
+ // Make sure the link is a hash and the link is local to the iframe
688
+ if (el.hash && el.hostname == window.location.hostname) {
689
+ // Prevent the whole window from scrolling
690
+ e.preventDefault();
691
+ // Prevent opening a new window
692
+ el.target = '_self';
693
+ // Scroll to the matching element, if an element exists
694
+ if (body.querySelector(el.hash)) {
695
+ body.scrollTop = body.querySelector(el.hash).offsetTop;
696
+ }
697
+ }
698
+ }
699
+ });
700
+
701
+ utilBtns = self.iframe.getElementById('epiceditor-utilbar');
702
+
703
+ // TODO: Move into fullscreen setup function (_setupFullscreen)
704
+ _elementStates = {}
705
+ self._goFullscreen = function (el, callback) {
706
+ callback = callback || function () {};
707
+ var wait = 0;
708
+ this._fixScrollbars('auto');
709
+
710
+ if (self.is('fullscreen')) {
711
+ self._exitFullscreen(el, callback);
712
+ return;
713
+ }
714
+
715
+ if (nativeFs) {
716
+ if (nativeFsWebkit) {
717
+ el.webkitRequestFullScreen();
718
+ wait = 750;
719
+ }
720
+ else if (nativeFsMoz) {
721
+ el.mozRequestFullScreen();
722
+ }
723
+ else if (nativeFsW3C) {
724
+ el.requestFullscreen();
725
+ }
726
+ }
727
+
728
+ _isInEdit = self.is('edit');
729
+
730
+
731
+ // Why does this need to be in a randomly "750"ms setTimeout? WebKit's
732
+ // implementation of fullscreen seem to trigger the webkitfullscreenchange
733
+ // event _after_ everything is done. Instead, it triggers _during_ the
734
+ // transition. This means calculations of what's half, 100%, etc are wrong
735
+ // so to combat this we throw down the hammer with a setTimeout and wait
736
+ // to trigger our calculation code.
737
+ // See: https://code.google.com/p/chromium/issues/detail?id=181116
738
+ setTimeout(function () {
739
+ // Set the state of EE in fullscreen
740
+ // We set edit and preview to true also because they're visible
741
+ // we might want to allow fullscreen edit mode without preview (like a "zen" mode)
742
+ self._eeState.fullscreen = true;
743
+ self._eeState.edit = true;
744
+ self._eeState.preview = true;
745
+
746
+ // Cache calculations
747
+ var windowInnerWidth = window.innerWidth
748
+ , windowInnerHeight = window.innerHeight
749
+ , windowOuterWidth = window.outerWidth
750
+ , windowOuterHeight = window.outerHeight;
751
+
752
+ // Without this the scrollbars will get hidden when scrolled to the bottom in faux fullscreen (see #66)
753
+ if (!nativeFs) {
754
+ windowOuterHeight = window.innerHeight;
755
+ }
756
+
757
+ // This MUST come first because the editor is 100% width so if we change the width of the iframe or wrapper
758
+ // the editor's width wont be the same as before
759
+ _elementStates.editorIframe = _saveStyleState(self.editorIframe, 'save', {
760
+ 'width': windowOuterWidth / 2 + 'px'
761
+ , 'height': windowOuterHeight + 'px'
762
+ , 'float': 'left' // Most browsers
763
+ , 'cssFloat': 'left' // FF
764
+ , 'styleFloat': 'left' // Older IEs
765
+ , 'display': 'block'
766
+ , 'position': 'static'
767
+ , 'left': ''
768
+ });
769
+
770
+ // the previewer
771
+ _elementStates.previewerIframe = _saveStyleState(self.previewerIframe, 'save', {
772
+ 'width': windowOuterWidth / 2 + 'px'
773
+ , 'height': windowOuterHeight + 'px'
774
+ , 'float': 'right' // Most browsers
775
+ , 'cssFloat': 'right' // FF
776
+ , 'styleFloat': 'right' // Older IEs
777
+ , 'display': 'block'
778
+ , 'position': 'static'
779
+ , 'left': ''
780
+ });
781
+
782
+ // Setup the containing element CSS for fullscreen
783
+ _elementStates.element = _saveStyleState(self.element, 'save', {
784
+ 'position': 'fixed'
785
+ , 'top': '0'
786
+ , 'left': '0'
787
+ , 'width': '100%'
788
+ , 'z-index': '9999' // Most browsers
789
+ , 'zIndex': '9999' // Firefox
790
+ , 'border': 'none'
791
+ , 'margin': '0'
792
+ // Should use the base styles background!
793
+ , 'background': _getStyle(self.editor, 'background-color') // Try to hide the site below
794
+ , 'height': windowInnerHeight + 'px'
795
+ });
796
+
797
+ // The iframe element
798
+ _elementStates.iframeElement = _saveStyleState(self.iframeElement, 'save', {
799
+ 'width': windowOuterWidth + 'px'
800
+ , 'height': windowInnerHeight + 'px'
801
+ });
802
+
803
+ // ...Oh, and hide the buttons and prevent scrolling
804
+ utilBtns.style.visibility = 'hidden';
805
+
806
+ if (!nativeFs) {
807
+ document.body.style.overflow = 'hidden';
808
+ }
809
+
810
+ self.preview();
811
+
812
+ self.focus();
813
+
814
+ self.emit('fullscreenenter');
815
+
816
+ callback.call(self);
817
+ }, wait);
818
+
819
+ };
820
+
821
+ self._exitFullscreen = function (el, callback) {
822
+ callback = callback || function () {};
823
+ this._fixScrollbars();
824
+
825
+ _saveStyleState(self.element, 'apply', _elementStates.element);
826
+ _saveStyleState(self.iframeElement, 'apply', _elementStates.iframeElement);
827
+ _saveStyleState(self.editorIframe, 'apply', _elementStates.editorIframe);
828
+ _saveStyleState(self.previewerIframe, 'apply', _elementStates.previewerIframe);
829
+
830
+ // We want to always revert back to the original styles in the CSS so,
831
+ // if it's a fluid width container it will expand on resize and not get
832
+ // stuck at a specific width after closing fullscreen.
833
+ self.element.style.width = self._eeState.reflowWidth ? self._eeState.reflowWidth : '';
834
+ self.element.style.height = self._eeState.reflowHeight ? self._eeState.reflowHeight : '';
835
+
836
+ utilBtns.style.visibility = 'visible';
837
+
838
+ // Put the editor back in the right state
839
+ // TODO: This is ugly... how do we make this nicer?
840
+ // setting fullscreen to false here prevents the
841
+ // native fs callback from calling this function again
842
+ self._eeState.fullscreen = false;
843
+
844
+ if (!nativeFs) {
845
+ document.body.style.overflow = 'auto';
846
+ }
847
+ else {
848
+ if (nativeFsWebkit) {
849
+ document.webkitCancelFullScreen();
850
+ }
851
+ else if (nativeFsMoz) {
852
+ document.mozCancelFullScreen();
853
+ }
854
+ else if (nativeFsW3C) {
855
+ document.exitFullscreen();
856
+ }
857
+ }
858
+
859
+ if (_isInEdit) {
860
+ self.edit();
861
+ }
862
+ else {
863
+ self.preview();
864
+ }
865
+
866
+ self.reflow();
867
+
868
+ self.emit('fullscreenexit');
869
+
870
+ callback.call(self);
871
+ };
872
+
873
+ // This setups up live previews by triggering preview() IF in fullscreen on keyup
874
+ self.editor.addEventListener('keyup', function () {
875
+ if (keypressTimer) {
876
+ window.clearTimeout(keypressTimer);
877
+ }
878
+ keypressTimer = window.setTimeout(function () {
879
+ if (self.is('fullscreen')) {
880
+ self.preview();
881
+ }
882
+ }, 250);
883
+ });
884
+
885
+ fsElement = self.iframeElement;
886
+
887
+ // Sets up the onclick event on utility buttons
888
+ utilBtns.addEventListener('click', function (e) {
889
+ var targetClass = e.target.className;
890
+ if (targetClass.indexOf('epiceditor-toggle-preview-btn') > -1) {
891
+ self.preview();
892
+ }
893
+ else if (targetClass.indexOf('epiceditor-toggle-edit-btn') > -1) {
894
+ self.edit();
895
+ }
896
+ else if (targetClass.indexOf('epiceditor-fullscreen-btn') > -1) {
897
+ self._goFullscreen(fsElement);
898
+ }
899
+ });
900
+
901
+ // Sets up the NATIVE fullscreen editor/previewer for WebKit
902
+ if (nativeFsWebkit) {
903
+ document.addEventListener('webkitfullscreenchange', function () {
904
+ if (!document.webkitIsFullScreen && self._eeState.fullscreen) {
905
+ self._exitFullscreen(fsElement);
906
+ }
907
+ }, false);
908
+ }
909
+ else if (nativeFsMoz) {
910
+ document.addEventListener('mozfullscreenchange', function () {
911
+ if (!document.mozFullScreen && self._eeState.fullscreen) {
912
+ self._exitFullscreen(fsElement);
913
+ }
914
+ }, false);
915
+ }
916
+ else if (nativeFsW3C) {
917
+ document.addEventListener('fullscreenchange', function () {
918
+ if (document.fullscreenElement == null && self._eeState.fullscreen) {
919
+ self._exitFullscreen(fsElement);
920
+ }
921
+ }, false);
922
+ }
923
+
924
+ // TODO: Move utilBar stuff into a utilBar setup function (_setupUtilBar)
925
+ utilBar = self.iframe.getElementById('epiceditor-utilbar');
926
+
927
+ // Hide it at first until they move their mouse
928
+ if (self.settings.button.bar !== true) {
929
+ utilBar.style.display = 'none';
930
+ }
931
+
932
+ utilBar.addEventListener('mouseover', function () {
933
+ if (utilBarTimer) {
934
+ clearTimeout(utilBarTimer);
935
+ }
936
+ });
937
+
938
+ function utilBarHandler(e) {
939
+ if (self.settings.button.bar !== "auto") {
940
+ return;
941
+ }
942
+ // Here we check if the mouse has moves more than 5px in any direction before triggering the mousemove code
943
+ // we do this for 2 reasons:
944
+ // 1. On Mac OS X lion when you scroll and it does the iOS like "jump" when it hits the top/bottom of the page itll fire off
945
+ // a mousemove of a few pixels depending on how hard you scroll
946
+ // 2. We give a slight buffer to the user in case he barely touches his touchpad or mouse and not trigger the UI
947
+ if (Math.abs(mousePos.y - e.pageY) >= 5 || Math.abs(mousePos.x - e.pageX) >= 5) {
948
+ utilBar.style.display = 'block';
949
+ // if we have a timer already running, kill it out
950
+ if (utilBarTimer) {
951
+ clearTimeout(utilBarTimer);
952
+ }
953
+
954
+ // begin a new timer that hides our object after 1000 ms
955
+ utilBarTimer = window.setTimeout(function () {
956
+ utilBar.style.display = 'none';
957
+ }, 1000);
958
+ }
959
+ mousePos = { y: e.pageY, x: e.pageX };
960
+ }
961
+
962
+ // Add keyboard shortcuts for convenience.
963
+ function shortcutHandler(e) {
964
+ if (e.keyCode == self.settings.shortcut.modifier) { isMod = true } // check for modifier press(default is alt key), save to var
965
+ if (e.keyCode == 17) { isCtrl = true } // check for ctrl/cmnd press, in order to catch ctrl/cmnd + s
966
+
967
+ // Check for alt+p and make sure were not in fullscreen - default shortcut to switch to preview
968
+ if (isMod === true && e.keyCode == self.settings.shortcut.preview && !self.is('fullscreen')) {
969
+ e.preventDefault();
970
+ if (self.is('edit') && self._previewEnabled) {
971
+ self.preview();
972
+ }
973
+ else if (self._editEnabled) {
974
+ self.edit();
975
+ }
976
+ }
977
+ // Check for alt+f - default shortcut to make editor fullscreen
978
+ if (isMod === true && e.keyCode == self.settings.shortcut.fullscreen && self._fullscreenEnabled) {
979
+ e.preventDefault();
980
+ self._goFullscreen(fsElement);
981
+ }
982
+
983
+ // Set the modifier key to false once *any* key combo is completed
984
+ // or else, on Windows, hitting the alt key will lock the isMod state to true (ticket #133)
985
+ if (isMod === true && e.keyCode !== self.settings.shortcut.modifier) {
986
+ isMod = false;
987
+ }
988
+
989
+ // When a user presses "esc", revert everything!
990
+ if (e.keyCode == 27 && self.is('fullscreen')) {
991
+ self._exitFullscreen(fsElement);
992
+ }
993
+
994
+ // Check for ctrl + s (since a lot of people do it out of habit) and make it do nothing
995
+ if (isCtrl === true && e.keyCode == 83) {
996
+ self.save();
997
+ e.preventDefault();
998
+ isCtrl = false;
999
+ }
1000
+
1001
+ // Do the same for Mac now (metaKey == cmd).
1002
+ if (e.metaKey && e.keyCode == 83) {
1003
+ self.save();
1004
+ e.preventDefault();
1005
+ }
1006
+
1007
+ }
1008
+
1009
+ function shortcutUpHandler(e) {
1010
+ if (e.keyCode == self.settings.shortcut.modifier) { isMod = false }
1011
+ if (e.keyCode == 17) { isCtrl = false }
1012
+ }
1013
+
1014
+ function pasteHandler(e) {
1015
+ var content;
1016
+ if (e.clipboardData) {
1017
+ //FF 22, Webkit, "standards"
1018
+ e.preventDefault();
1019
+ content = e.clipboardData.getData("text/plain");
1020
+ self.editorIframeDocument.execCommand("insertText", false, content);
1021
+ }
1022
+ else if (window.clipboardData) {
1023
+ //IE, "nasty"
1024
+ e.preventDefault();
1025
+ content = window.clipboardData.getData("Text");
1026
+ content = content.replace(/</g, '&lt;');
1027
+ content = content.replace(/>/g, '&gt;');
1028
+ content = content.replace(/\n/g, '<br>');
1029
+ content = content.replace(/\r/g, ''); //fuck you, ie!
1030
+ content = content.replace(/<br>\s/g, '<br>&nbsp;')
1031
+ content = content.replace(/\s\s\s/g, '&nbsp; &nbsp;')
1032
+ content = content.replace(/\s\s/g, '&nbsp; ')
1033
+ self.editorIframeDocument.selection.createRange().pasteHTML(content);
1034
+ }
1035
+ }
1036
+
1037
+ // Hide and show the util bar based on mouse movements
1038
+ eventableIframes = [self.previewerIframeDocument, self.editorIframeDocument];
1039
+
1040
+ for (i = 0; i < eventableIframes.length; i++) {
1041
+ eventableIframes[i].addEventListener('mousemove', function (e) {
1042
+ utilBarHandler(e);
1043
+ });
1044
+ eventableIframes[i].addEventListener('scroll', function (e) {
1045
+ utilBarHandler(e);
1046
+ });
1047
+ eventableIframes[i].addEventListener('keyup', function (e) {
1048
+ shortcutUpHandler(e);
1049
+ });
1050
+ eventableIframes[i].addEventListener('keydown', function (e) {
1051
+ shortcutHandler(e);
1052
+ });
1053
+ eventableIframes[i].addEventListener('paste', function (e) {
1054
+ pasteHandler(e);
1055
+ });
1056
+ }
1057
+
1058
+ // Save the document every 100ms by default
1059
+ // TODO: Move into autosave setup function (_setupAutoSave)
1060
+ if (self.settings.file.autoSave) {
1061
+ self._saveIntervalTimer = window.setInterval(function () {
1062
+ if (!self._canSave) {
1063
+ return;
1064
+ }
1065
+ self.save(false, true);
1066
+ }, self.settings.file.autoSave);
1067
+ }
1068
+
1069
+ // Update a textarea automatically if a textarea is given so you don't need
1070
+ // AJAX to submit a form and instead fall back to normal form behavior
1071
+ if (self.settings.textarea) {
1072
+ self._setupTextareaSync();
1073
+ }
1074
+
1075
+ window.addEventListener('resize', function () {
1076
+ // If NOT webkit, and in fullscreen, we need to account for browser resizing
1077
+ // we don't care about webkit because you can't resize in webkit's fullscreen
1078
+ if (self.is('fullscreen')) {
1079
+ _applyStyles(self.iframeElement, {
1080
+ 'width': window.outerWidth + 'px'
1081
+ , 'height': window.innerHeight + 'px'
1082
+ });
1083
+
1084
+ _applyStyles(self.element, {
1085
+ 'height': window.innerHeight + 'px'
1086
+ });
1087
+
1088
+ _applyStyles(self.previewerIframe, {
1089
+ 'width': window.outerWidth / 2 + 'px'
1090
+ , 'height': window.innerHeight + 'px'
1091
+ });
1092
+
1093
+ _applyStyles(self.editorIframe, {
1094
+ 'width': window.outerWidth / 2 + 'px'
1095
+ , 'height': window.innerHeight + 'px'
1096
+ });
1097
+ }
1098
+ // Makes the editor support fluid width when not in fullscreen mode
1099
+ else if (!self.is('fullscreen')) {
1100
+ self.reflow();
1101
+ }
1102
+ });
1103
+
1104
+ // Set states before flipping edit and preview modes
1105
+ self._eeState.loaded = true;
1106
+ self._eeState.unloaded = false;
1107
+
1108
+ if (self.is('preview')) {
1109
+ self.preview();
1110
+ }
1111
+ else {
1112
+ self.edit();
1113
+ }
1114
+
1115
+ self.iframe.close();
1116
+ self._eeState.startup = false;
1117
+
1118
+ if (self.settings.autogrow) {
1119
+ self._fixScrollbars();
1120
+
1121
+ boundAutogrow = function () {
1122
+ setTimeout(function () {
1123
+ self._autogrow();
1124
+ }, 1);
1125
+ };
1126
+
1127
+ //for if autosave is disabled or very slow
1128
+ ['keydown', 'keyup', 'paste', 'cut'].forEach(function (ev) {
1129
+ self.getElement('editor').addEventListener(ev, boundAutogrow);
1130
+ });
1131
+
1132
+ self.on('__update', boundAutogrow);
1133
+ self.on('edit', function () {
1134
+ setTimeout(boundAutogrow, 50)
1135
+ });
1136
+ self.on('preview', function () {
1137
+ setTimeout(boundAutogrow, 50)
1138
+ });
1139
+
1140
+ //for browsers that have rendering delays
1141
+ setTimeout(boundAutogrow, 50);
1142
+ boundAutogrow();
1143
+ }
1144
+
1145
+ // The callback and call are the same thing, but different ways to access them
1146
+ callback.call(this);
1147
+ this.emit('load');
1148
+ return this;
1149
+ }
1150
+
1151
+ EpicEditor.prototype._setupTextareaSync = function () {
1152
+ var self = this
1153
+ , _syncTextarea;
1154
+
1155
+ // Even if autoSave is false, we want to make sure to keep the textarea synced
1156
+ // with the editor's content. One bad thing about this tho is that we're
1157
+ // creating two timers now in some configurations. We keep the textarea synced
1158
+ // by saving and opening the textarea content from the draft file storage.
1159
+ self._textareaSaveTimer = window.setInterval(function () {
1160
+ if (!self._canSave) {
1161
+ return;
1162
+ }
1163
+ self.save(true);
1164
+ }, 100);
1165
+
1166
+ _syncTextarea = function () {
1167
+ // TODO: Figure out root cause for having to do this ||.
1168
+ // This only happens for draft files. Probably has something to do with
1169
+ // the fact draft files haven't been saved by the time this is called.
1170
+ // TODO: Add test for this case.
1171
+ // Get the file.name each time as it can change. DO NOT save this to a
1172
+ // var outside of this closure or the editor will stop syncing when the
1173
+ // file is changed with importFile or open.
1174
+ self._textareaElement.value = self.exportFile(self.settings.file.name, 'text', true) || self.settings.file.defaultContent;
1175
+ }
1176
+
1177
+ if (typeof self.settings.textarea == 'string') {
1178
+ self._textareaElement = document.getElementById(self.settings.textarea);
1179
+ }
1180
+ else if (typeof self.settings.textarea == 'object') {
1181
+ self._textareaElement = self.settings.textarea;
1182
+ }
1183
+
1184
+ // On page load, if there's content in the textarea that means one of two
1185
+ // different things:
1186
+ //
1187
+ // 1. The editor didn't load and the user was writing in the textarea and
1188
+ // now he refreshed the page or the JS loaded and the textarea now has
1189
+ // content. If this is the case the user probably expects his content is
1190
+ // moved into the editor and not lose what he typed.
1191
+ //
1192
+ // 2. The developer put content in the textarea from some server side
1193
+ // code. In this case, the textarea will take precedence.
1194
+ //
1195
+ // If the developer wants drafts to be recoverable they should check if
1196
+ // the local file in localStorage's modified date is newer than the server.
1197
+ if (self._textareaElement.value !== '') {
1198
+ self.importFile(self.settings.file.name, self._textareaElement.value);
1199
+
1200
+ // manually save draft after import so there is no delay between the
1201
+ // import and exporting in _syncTextarea. Without this, _syncTextarea
1202
+ // will pull the saved data from localStorage which will be <=100ms old.
1203
+ self.save(true);
1204
+ }
1205
+
1206
+ // Update the textarea on load and pull from drafts
1207
+ _syncTextarea();
1208
+
1209
+ // Make sure to keep it updated
1210
+ self.on('__update', _syncTextarea);
1211
+ self.on('__create', _syncTextarea);
1212
+ self.on('__save', _syncTextarea);
1213
+ }
1214
+
1215
+ /**
1216
+ * Will NOT focus the editor if the editor is still starting up AND
1217
+ * focusOnLoad is set to false. This allows you to place this in code that
1218
+ * gets fired during .load() without worrying about it overriding the user's
1219
+ * option. For example use cases see preview() and edit().
1220
+ * @returns {undefined}
1221
+ */
1222
+
1223
+ // Prevent focus when the user sets focusOnLoad to false by checking if the
1224
+ // editor is starting up AND if focusOnLoad is true
1225
+ EpicEditor.prototype._focusExceptOnLoad = function () {
1226
+ var self = this;
1227
+ if ((self._eeState.startup && self.settings.focusOnLoad) || !self._eeState.startup) {
1228
+ self.focus();
1229
+ }
1230
+ }
1231
+
1232
+ /**
1233
+ * Will remove the editor, but not offline files
1234
+ * @returns {object} EpicEditor will be returned
1235
+ */
1236
+ EpicEditor.prototype.unload = function (callback) {
1237
+
1238
+ // Make sure the editor isn't already unloaded.
1239
+ if (this.is('unloaded')) {
1240
+ throw new Error('Editor isn\'t loaded');
1241
+ }
1242
+
1243
+ var self = this
1244
+ , editor = window.parent.document.getElementById(self._instanceId);
1245
+
1246
+ editor.parentNode.removeChild(editor);
1247
+ self._eeState.loaded = false;
1248
+ self._eeState.unloaded = true;
1249
+ callback = callback || function () {};
1250
+
1251
+ if (self.settings.textarea) {
1252
+ self.removeListener('__update');
1253
+ }
1254
+
1255
+ if (self._saveIntervalTimer) {
1256
+ window.clearInterval(self._saveIntervalTimer);
1257
+ }
1258
+ if (self._textareaSaveTimer) {
1259
+ window.clearInterval(self._textareaSaveTimer);
1260
+ }
1261
+
1262
+ callback.call(this);
1263
+ self.emit('unload');
1264
+ return self;
1265
+ }
1266
+
1267
+ /**
1268
+ * reflow allows you to dynamically re-fit the editor in the parent without
1269
+ * having to unload and then reload the editor again.
1270
+ *
1271
+ * reflow will also emit a `reflow` event and will return the new dimensions.
1272
+ * If it's called without params it'll return the new width and height and if
1273
+ * it's called with just width or just height it'll just return the width or
1274
+ * height. It's returned as an object like: { width: '100px', height: '1px' }
1275
+ *
1276
+ * @param {string|null} kind Can either be 'width' or 'height' or null
1277
+ * if null, both the height and width will be resized
1278
+ * @param {function} callback A function to fire after the reflow is finished.
1279
+ * Will return the width / height in an obj as the first param of the callback.
1280
+ * @returns {object} EpicEditor will be returned
1281
+ */
1282
+ EpicEditor.prototype.reflow = function (kind, callback) {
1283
+ var self = this
1284
+ , widthDiff = _outerWidth(self.element) - self.element.offsetWidth
1285
+ , heightDiff = _outerHeight(self.element) - self.element.offsetHeight
1286
+ , elements = [self.iframeElement, self.editorIframe, self.previewerIframe]
1287
+ , eventData = {}
1288
+ , newWidth
1289
+ , newHeight;
1290
+
1291
+ if (typeof kind == 'function') {
1292
+ callback = kind;
1293
+ kind = null;
1294
+ }
1295
+
1296
+ if (!callback) {
1297
+ callback = function () {};
1298
+ }
1299
+
1300
+ for (var x = 0; x < elements.length; x++) {
1301
+ if (!kind || kind == 'width') {
1302
+ newWidth = self.element.offsetWidth - widthDiff + 'px';
1303
+ elements[x].style.width = newWidth;
1304
+ self._eeState.reflowWidth = newWidth;
1305
+ eventData.width = newWidth;
1306
+ }
1307
+ if (!kind || kind == 'height') {
1308
+ newHeight = self.element.offsetHeight - heightDiff + 'px';
1309
+ elements[x].style.height = newHeight;
1310
+ self._eeState.reflowHeight = newHeight
1311
+ eventData.height = newHeight;
1312
+ }
1313
+ }
1314
+
1315
+ self.emit('reflow', eventData);
1316
+ callback.call(this, eventData);
1317
+ return self;
1318
+ }
1319
+
1320
+ /**
1321
+ * Will take the markdown and generate a preview view based on the theme
1322
+ * @returns {object} EpicEditor will be returned
1323
+ */
1324
+ EpicEditor.prototype.preview = function () {
1325
+ var self = this
1326
+ , x
1327
+ , theme = self.settings.theme.preview
1328
+ , anchors;
1329
+
1330
+ _replaceClass(self.getElement('wrapper'), 'epiceditor-edit-mode', 'epiceditor-preview-mode');
1331
+
1332
+ // Check if no CSS theme link exists
1333
+ if (!self.previewerIframeDocument.getElementById('theme')) {
1334
+ _insertCSSLink(theme, self.previewerIframeDocument, 'theme');
1335
+ }
1336
+ else if (self.previewerIframeDocument.getElementById('theme').name !== theme) {
1337
+ self.previewerIframeDocument.getElementById('theme').href = theme;
1338
+ }
1339
+
1340
+ // Save a preview draft since it might not be saved to the real file yet
1341
+ self.save(true);
1342
+
1343
+ // Add the generated draft HTML into the previewer
1344
+ self.previewer.innerHTML = self.exportFile(null, 'html', true);
1345
+
1346
+ // Hide the editor and display the previewer
1347
+ if (!self.is('fullscreen')) {
1348
+ self.editorIframe.style.left = '-999999px';
1349
+ self.previewerIframe.style.left = '';
1350
+ self._eeState.preview = true;
1351
+ self._eeState.edit = false;
1352
+ self._focusExceptOnLoad();
1353
+ }
1354
+
1355
+ self.emit('preview');
1356
+ return self;
1357
+ }
1358
+
1359
+ /**
1360
+ * Helper to focus on the editor iframe. Will figure out which iframe to
1361
+ * focus on based on which one is active and will handle the cross browser
1362
+ * issues with focusing on the iframe vs the document body.
1363
+ * @returns {object} EpicEditor will be returned
1364
+ */
1365
+ EpicEditor.prototype.focus = function (pageload) {
1366
+ var self = this
1367
+ , isPreview = self.is('preview')
1368
+ , focusElement = isPreview ? self.previewerIframeDocument.body
1369
+ : self.editorIframeDocument.body;
1370
+
1371
+ if (_isFirefox() && isPreview) {
1372
+ focusElement = self.previewerIframe;
1373
+ }
1374
+
1375
+ focusElement.focus();
1376
+ return this;
1377
+ }
1378
+
1379
+ /**
1380
+ * Puts the editor into fullscreen mode
1381
+ * @returns {object} EpicEditor will be returned
1382
+ */
1383
+ EpicEditor.prototype.enterFullscreen = function (callback) {
1384
+ callback = callback || function () {};
1385
+ if (this.is('fullscreen')) {
1386
+ callback.call(this);
1387
+ return this;
1388
+ }
1389
+ this._goFullscreen(this.iframeElement, callback);
1390
+ return this;
1391
+ }
1392
+
1393
+ /**
1394
+ * Closes fullscreen mode if opened
1395
+ * @returns {object} EpicEditor will be returned
1396
+ */
1397
+ EpicEditor.prototype.exitFullscreen = function (callback) {
1398
+ callback = callback || function () {};
1399
+ if (!this.is('fullscreen')) {
1400
+ callback.call(this);
1401
+ return this;
1402
+ }
1403
+ this._exitFullscreen(this.iframeElement, callback);
1404
+ return this;
1405
+ }
1406
+
1407
+ /**
1408
+ * Hides the preview and shows the editor again
1409
+ * @returns {object} EpicEditor will be returned
1410
+ */
1411
+ EpicEditor.prototype.edit = function () {
1412
+ var self = this;
1413
+ _replaceClass(self.getElement('wrapper'), 'epiceditor-preview-mode', 'epiceditor-edit-mode');
1414
+ self._eeState.preview = false;
1415
+ self._eeState.edit = true;
1416
+ self.editorIframe.style.left = '';
1417
+ self.previewerIframe.style.left = '-999999px';
1418
+ self._focusExceptOnLoad();
1419
+ self.emit('edit');
1420
+ return this;
1421
+ }
1422
+
1423
+ /**
1424
+ * Grabs a specificed HTML node. Use it as a shortcut to getting the iframe contents
1425
+ * @param {String} name The name of the node (can be document, body, editor, previewer, or wrapper)
1426
+ * @returns {Object|Null}
1427
+ */
1428
+ EpicEditor.prototype.getElement = function (name) {
1429
+ var available = {
1430
+ "container": this.element
1431
+ , "wrapper": this.iframe.getElementById('epiceditor-wrapper')
1432
+ , "wrapperIframe": this.iframeElement
1433
+ , "editor": this.editorIframeDocument
1434
+ , "editorIframe": this.editorIframe
1435
+ , "previewer": this.previewerIframeDocument
1436
+ , "previewerIframe": this.previewerIframe
1437
+ }
1438
+
1439
+ // Check that the given string is a possible option and verify the editor isn't unloaded
1440
+ // without this, you'd be given a reference to an object that no longer exists in the DOM
1441
+ if (!available[name] || this.is('unloaded')) {
1442
+ return null;
1443
+ }
1444
+ else {
1445
+ return available[name];
1446
+ }
1447
+ }
1448
+
1449
+ /**
1450
+ * Returns a boolean of each "state" of the editor. For example "editor.is('loaded')" // returns true/false
1451
+ * @param {String} what the state you want to check for
1452
+ * @returns {Boolean}
1453
+ */
1454
+ EpicEditor.prototype.is = function (what) {
1455
+ var self = this;
1456
+ switch (what) {
1457
+ case 'loaded':
1458
+ return self._eeState.loaded;
1459
+ case 'unloaded':
1460
+ return self._eeState.unloaded
1461
+ case 'preview':
1462
+ return self._eeState.preview
1463
+ case 'edit':
1464
+ return self._eeState.edit;
1465
+ case 'fullscreen':
1466
+ return self._eeState.fullscreen;
1467
+ // TODO: This "works", but the tests are saying otherwise. Come back to this
1468
+ // and figure out how to fix it.
1469
+ // case 'focused':
1470
+ // return document.activeElement == self.iframeElement;
1471
+ default:
1472
+ return false;
1473
+ }
1474
+ }
1475
+
1476
+ /**
1477
+ * Opens a file
1478
+ * @param {string} name The name of the file you want to open
1479
+ * @returns {object} EpicEditor will be returned
1480
+ */
1481
+ EpicEditor.prototype.open = function (name) {
1482
+ var self = this
1483
+ , defaultContent = self.settings.file.defaultContent
1484
+ , fileObj;
1485
+ name = name || self.settings.file.name;
1486
+ self.settings.file.name = name;
1487
+ if (this._storage[self.settings.localStorageName]) {
1488
+ fileObj = self.exportFile(name);
1489
+ if (fileObj !== undefined) {
1490
+ _setText(self.editor, fileObj);
1491
+ self.emit('read');
1492
+ }
1493
+ else {
1494
+ _setText(self.editor, defaultContent);
1495
+ self.save(); // ensure a save
1496
+ self.emit('create');
1497
+ }
1498
+ self.previewer.innerHTML = self.exportFile(null, 'html');
1499
+ self.emit('open');
1500
+ }
1501
+ return this;
1502
+ }
1503
+
1504
+ /**
1505
+ * Saves content for offline use
1506
+ * @returns {object} EpicEditor will be returned
1507
+ */
1508
+ EpicEditor.prototype.save = function (_isPreviewDraft, _isAuto) {
1509
+ var self = this
1510
+ , storage
1511
+ , isUpdate = false
1512
+ , isNew = false
1513
+ , file = self.settings.file.name
1514
+ , previewDraftName = ''
1515
+ , data = this._storage[previewDraftName + self.settings.localStorageName]
1516
+ , content = _getText(this.editor);
1517
+
1518
+ if (_isPreviewDraft) {
1519
+ previewDraftName = self._previewDraftLocation;
1520
+ }
1521
+
1522
+ // This could have been false but since we're manually saving
1523
+ // we know it's save to start autoSaving again
1524
+ this._canSave = true;
1525
+
1526
+ // Guard against storage being wiped out without EpicEditor knowing
1527
+ // TODO: Emit saving error - storage seems to have been wiped
1528
+ if (data) {
1529
+ storage = JSON.parse(this._storage[previewDraftName + self.settings.localStorageName]);
1530
+
1531
+ // If the file doesn't exist we need to create it
1532
+ if (storage[file] === undefined) {
1533
+ storage[file] = self._defaultFileSchema();
1534
+ isNew = true;
1535
+ }
1536
+
1537
+ // If it does, we need to check if the content is different and
1538
+ // if it is, send the update event and update the timestamp
1539
+ else if (content !== storage[file].content) {
1540
+ storage[file].modified = new Date();
1541
+ isUpdate = true;
1542
+ }
1543
+ //don't bother autosaving if the content hasn't actually changed
1544
+ else if (_isAuto) {
1545
+ return;
1546
+ }
1547
+
1548
+ storage[file].content = content;
1549
+ this._storage[previewDraftName + self.settings.localStorageName] = JSON.stringify(storage);
1550
+
1551
+ // If it's a new file, send a create event as well as a private one for
1552
+ // use internally.
1553
+ if (isNew) {
1554
+ self.emit('create');
1555
+ self.emit('__create');
1556
+ }
1557
+
1558
+ // After the content is actually changed, emit update so it emits the
1559
+ // updated content. Also send a private event for interal use.
1560
+ if (isUpdate) {
1561
+ self.emit('update');
1562
+ self.emit('__update');
1563
+ }
1564
+
1565
+ if (_isAuto) {
1566
+ this.emit('autosave');
1567
+ }
1568
+ else if (!_isPreviewDraft) {
1569
+ this.emit('save');
1570
+ self.emit('__save');
1571
+ }
1572
+ }
1573
+
1574
+ return this;
1575
+ }
1576
+
1577
+ /**
1578
+ * Removes a page
1579
+ * @param {string} name The name of the file you want to remove from localStorage
1580
+ * @returns {object} EpicEditor will be returned
1581
+ */
1582
+ EpicEditor.prototype.remove = function (name) {
1583
+ var self = this
1584
+ , s;
1585
+ name = name || self.settings.file.name;
1586
+
1587
+ // If you're trying to delete a page you have open, block saving
1588
+ if (name == self.settings.file.name) {
1589
+ self._canSave = false;
1590
+ }
1591
+
1592
+ s = JSON.parse(this._storage[self.settings.localStorageName]);
1593
+ delete s[name];
1594
+ this._storage[self.settings.localStorageName] = JSON.stringify(s);
1595
+ this.emit('remove');
1596
+ return this;
1597
+ };
1598
+
1599
+ /**
1600
+ * Renames a file
1601
+ * @param {string} oldName The old file name
1602
+ * @param {string} newName The new file name
1603
+ * @returns {object} EpicEditor will be returned
1604
+ */
1605
+ EpicEditor.prototype.rename = function (oldName, newName) {
1606
+ var self = this
1607
+ , s = JSON.parse(this._storage[self.settings.localStorageName]);
1608
+ s[newName] = s[oldName];
1609
+ delete s[oldName];
1610
+ this._storage[self.settings.localStorageName] = JSON.stringify(s);
1611
+ self.open(newName);
1612
+ return this;
1613
+ };
1614
+
1615
+ /**
1616
+ * Imports a file and it's contents and opens it
1617
+ * @param {string} name The name of the file you want to import (will overwrite existing files!)
1618
+ * @param {string} content Content of the file you want to import
1619
+ * @param {string} kind The kind of file you want to import (TBI)
1620
+ * @param {object} meta Meta data you want to save with your file.
1621
+ * @returns {object} EpicEditor will be returned
1622
+ */
1623
+ EpicEditor.prototype.importFile = function (name, content, kind, meta) {
1624
+ var self = this;
1625
+
1626
+ name = name || self.settings.file.name;
1627
+ content = content || '';
1628
+ kind = kind || 'md';
1629
+ meta = meta || {};
1630
+
1631
+ // Set our current file to the new file and update the content
1632
+ self.settings.file.name = name;
1633
+ _setText(self.editor, content);
1634
+
1635
+ self.save();
1636
+
1637
+ if (self.is('fullscreen')) {
1638
+ self.preview();
1639
+ }
1640
+
1641
+ //firefox has trouble with importing and working out the size right away
1642
+ if (self.settings.autogrow) {
1643
+ setTimeout(function () {
1644
+ self._autogrow();
1645
+ }, 50);
1646
+ }
1647
+
1648
+ return this;
1649
+ };
1650
+
1651
+ /**
1652
+ * Gets the local filestore
1653
+ * @param {string} name Name of the file in the store
1654
+ * @returns {object|undefined} the local filestore, or a specific file in the store, if a name is given
1655
+ */
1656
+ EpicEditor.prototype._getFileStore = function (name, _isPreviewDraft) {
1657
+ var previewDraftName = ''
1658
+ , store;
1659
+ if (_isPreviewDraft) {
1660
+ previewDraftName = this._previewDraftLocation;
1661
+ }
1662
+ store = JSON.parse(this._storage[previewDraftName + this.settings.localStorageName]);
1663
+ if (name) {
1664
+ return store[name];
1665
+ }
1666
+ else {
1667
+ return store;
1668
+ }
1669
+ }
1670
+
1671
+ /**
1672
+ * Exports a file as a string in a supported format
1673
+ * @param {string} name Name of the file you want to export (case sensitive)
1674
+ * @param {string} kind Kind of file you want the content in (currently supports html and text, default is the format the browser "wants")
1675
+ * @returns {string|undefined} The content of the file in the content given or undefined if it doesn't exist
1676
+ */
1677
+ EpicEditor.prototype.exportFile = function (name, kind, _isPreviewDraft) {
1678
+ var self = this
1679
+ , file
1680
+ , content;
1681
+
1682
+ name = name || self.settings.file.name;
1683
+ kind = kind || 'text';
1684
+
1685
+ file = self._getFileStore(name, _isPreviewDraft);
1686
+
1687
+ // If the file doesn't exist just return early with undefined
1688
+ if (file === undefined) {
1689
+ return;
1690
+ }
1691
+
1692
+ content = file.content;
1693
+
1694
+ switch (kind) {
1695
+ case 'html':
1696
+ content = _sanitizeRawContent(content);
1697
+ return self.settings.parser(content);
1698
+ case 'text':
1699
+ return _sanitizeRawContent(content);
1700
+ case 'json':
1701
+ file.content = _sanitizeRawContent(file.content);
1702
+ return JSON.stringify(file);
1703
+ case 'raw':
1704
+ return content;
1705
+ default:
1706
+ return content;
1707
+ }
1708
+ }
1709
+
1710
+ /**
1711
+ * Gets the contents and metadata for files
1712
+ * @param {string} name Name of the file whose data you want (case sensitive)
1713
+ * @param {boolean} excludeContent whether the contents of files should be excluded
1714
+ * @returns {object} An object with the names and data of every file, or just the data of one file if a name was given
1715
+ */
1716
+ EpicEditor.prototype.getFiles = function (name, excludeContent) {
1717
+ var file
1718
+ , data = this._getFileStore(name);
1719
+
1720
+ if (name) {
1721
+ if (data !== undefined) {
1722
+ if (excludeContent) {
1723
+ delete data.content;
1724
+ }
1725
+ else {
1726
+ data.content = _sanitizeRawContent(data.content);
1727
+ }
1728
+ }
1729
+ return data;
1730
+ }
1731
+ else {
1732
+ for (file in data) {
1733
+ if (data.hasOwnProperty(file)) {
1734
+ if (excludeContent) {
1735
+ delete data[file].content;
1736
+ }
1737
+ else {
1738
+ data[file].content = _sanitizeRawContent(data[file].content);
1739
+ }
1740
+ }
1741
+ }
1742
+ return data;
1743
+ }
1744
+ }
1745
+
1746
+ // EVENTS
1747
+ // TODO: Support for namespacing events like "preview.foo"
1748
+ /**
1749
+ * Sets up an event handler for a specified event
1750
+ * @param {string} ev The event name
1751
+ * @param {function} handler The callback to run when the event fires
1752
+ * @returns {object} EpicEditor will be returned
1753
+ */
1754
+ EpicEditor.prototype.on = function (ev, handler) {
1755
+ var self = this;
1756
+ if (!this.events[ev]) {
1757
+ this.events[ev] = [];
1758
+ }
1759
+ this.events[ev].push(handler);
1760
+ return self;
1761
+ };
1762
+
1763
+ /**
1764
+ * This will emit or "trigger" an event specified
1765
+ * @param {string} ev The event name
1766
+ * @param {any} data Any data you want to pass into the callback
1767
+ * @returns {object} EpicEditor will be returned
1768
+ */
1769
+ EpicEditor.prototype.emit = function (ev, data) {
1770
+ var self = this
1771
+ , x;
1772
+
1773
+ data = data || self.getFiles(self.settings.file.name);
1774
+
1775
+ if (!this.events[ev]) {
1776
+ return;
1777
+ }
1778
+
1779
+ function invokeHandler(handler) {
1780
+ handler.call(self, data);
1781
+ }
1782
+
1783
+ for (x = 0; x < self.events[ev].length; x++) {
1784
+ invokeHandler(self.events[ev][x]);
1785
+ }
1786
+
1787
+ return self;
1788
+ };
1789
+
1790
+ /**
1791
+ * Will remove any listeners added from EpicEditor.on()
1792
+ * @param {string} ev The event name
1793
+ * @param {function} handler Handler to remove
1794
+ * @returns {object} EpicEditor will be returned
1795
+ */
1796
+ EpicEditor.prototype.removeListener = function (ev, handler) {
1797
+ var self = this;
1798
+ if (!handler) {
1799
+ this.events[ev] = [];
1800
+ return self;
1801
+ }
1802
+ if (!this.events[ev]) {
1803
+ return self;
1804
+ }
1805
+ // Otherwise a handler and event exist, so take care of it
1806
+ this.events[ev].splice(this.events[ev].indexOf(handler), 1);
1807
+ return self;
1808
+ }
1809
+
1810
+ /**
1811
+ * Handles autogrowing the editor
1812
+ */
1813
+ EpicEditor.prototype._autogrow = function () {
1814
+ var editorHeight
1815
+ , newHeight
1816
+ , minHeight
1817
+ , maxHeight
1818
+ , el
1819
+ , style
1820
+ , maxedOut = false;
1821
+
1822
+ //autogrow in fullscreen is nonsensical
1823
+ if (!this.is('fullscreen')) {
1824
+ if (this.is('edit')) {
1825
+ el = this.getElement('editor').documentElement;
1826
+ }
1827
+ else {
1828
+ el = this.getElement('previewer').documentElement;
1829
+ }
1830
+
1831
+ editorHeight = _outerHeight(el);
1832
+ newHeight = editorHeight;
1833
+
1834
+ //handle minimum
1835
+ minHeight = this.settings.autogrow.minHeight;
1836
+ if (typeof minHeight === 'function') {
1837
+ minHeight = minHeight(this);
1838
+ }
1839
+
1840
+ if (minHeight && newHeight < minHeight) {
1841
+ newHeight = minHeight;
1842
+ }
1843
+
1844
+ //handle maximum
1845
+ maxHeight = this.settings.autogrow.maxHeight;
1846
+ if (typeof maxHeight === 'function') {
1847
+ maxHeight = maxHeight(this);
1848
+ }
1849
+
1850
+ if (maxHeight && newHeight > maxHeight) {
1851
+ newHeight = maxHeight;
1852
+ maxedOut = true;
1853
+ }
1854
+
1855
+ if (maxedOut) {
1856
+ this._fixScrollbars('auto');
1857
+ } else {
1858
+ this._fixScrollbars('hidden');
1859
+ }
1860
+
1861
+ //actual resize
1862
+ if (newHeight != this.oldHeight) {
1863
+ this.getElement('container').style.height = newHeight + 'px';
1864
+ this.reflow();
1865
+ if (this.settings.autogrow.scroll) {
1866
+ window.scrollBy(0, newHeight - this.oldHeight);
1867
+ }
1868
+ this.oldHeight = newHeight;
1869
+ }
1870
+ }
1871
+ }
1872
+
1873
+ /**
1874
+ * Shows or hides scrollbars based on the autogrow setting
1875
+ * @param {string} forceSetting a value to force the overflow to
1876
+ */
1877
+ EpicEditor.prototype._fixScrollbars = function (forceSetting) {
1878
+ var setting;
1879
+ if (this.settings.autogrow) {
1880
+ setting = 'hidden';
1881
+ }
1882
+ else {
1883
+ setting = 'auto';
1884
+ }
1885
+ setting = forceSetting || setting;
1886
+ this.getElement('editor').documentElement.style.overflow = setting;
1887
+ this.getElement('previewer').documentElement.style.overflow = setting;
1888
+ }
1889
+
1890
+ EpicEditor.version = '0.2.2';
1891
+
1892
+ // Used to store information to be shared across editors
1893
+ EpicEditor._data = {};
1894
+
1895
+ window.EpicEditor = EpicEditor;
1896
+ })(window);
js/markdown/adminhtml/filereader.js ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ FileReader.js - v0.9
3
+ A lightweight wrapper for common FileReader usage.
4
+ Copyright 2012 Brian Grinstead - MIT License.
5
+ See http://github.com/bgrins/filereader.js for documentation.
6
+ */
7
+
8
+ (function (window, document) {
9
+
10
+ var FileReader = window.FileReader;
11
+ var FileReaderSyncSupport = false;
12
+ var workerScript = "self.addEventListener('message', function(e) { var data=e.data; try { var reader = new FileReaderSync; postMessage({ result: reader[data.readAs](data.file), extra: data.extra, file: data.file})} catch(e){ postMessage({ result:'error', extra:data.extra, file:data.file}); } }, false);";
13
+ var syncDetectionScript = "self.addEventListener('message', function(e) { postMessage(!!FileReaderSync); }, false);";
14
+ var fileReaderEvents = ['loadstart', 'progress', 'load', 'abort', 'error', 'loadend'];
15
+
16
+ var FileReaderJS = window.FileReaderJS = {
17
+ enabled: false,
18
+ setupInput: setupInput,
19
+ setupDrop: setupDrop,
20
+ setupClipboard: setupClipboard,
21
+ sync: false,
22
+ output: [],
23
+ opts: {
24
+ dragClass: "drag",
25
+ accept: false,
26
+ readAsDefault: 'BinaryString',
27
+ readAsMap: {
28
+ 'image/*': 'DataURL',
29
+ 'text/*': 'Text'
30
+ },
31
+ on: {
32
+ loadstart: noop,
33
+ progress: noop,
34
+ load: noop,
35
+ abort: noop,
36
+ error: noop,
37
+ loadend: noop,
38
+ skip: noop,
39
+ groupstart: noop,
40
+ groupend: noop,
41
+ beforestart: noop
42
+ }
43
+ }
44
+ };
45
+
46
+ // Setup jQuery plugin (if available)
47
+ if (typeof(jQuery) !== "undefined") {
48
+ jQuery.fn.fileReaderJS = function (opts) {
49
+ return this.each(function () {
50
+ if ($(this).is("input")) {
51
+ setupInput(this, opts);
52
+ }
53
+ else {
54
+ setupDrop(this, opts);
55
+ }
56
+ });
57
+ };
58
+
59
+ jQuery.fn.fileClipboard = function (opts) {
60
+ return this.each(function () {
61
+ setupClipboard(this, opts);
62
+ });
63
+ };
64
+ }
65
+
66
+ // Not all browsers support the FileReader interface. Return with the enabled bit = false.
67
+ if (!FileReader) {
68
+ return;
69
+ }
70
+
71
+ // WorkerHelper is a little wrapper for generating web workers from strings
72
+ var WorkerHelper = (function () {
73
+
74
+ var URL = window.URL || window.webkitURL;
75
+ var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
76
+
77
+ // May need to get just the URL in case it is needed for things beyond just creating a worker.
78
+ function getURL(script) {
79
+ if (window.Worker && BlobBuilder && URL) {
80
+ var bb = new BlobBuilder();
81
+ bb.append(script);
82
+ return URL.createObjectURL(bb.getBlob());
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ // If there is no need to revoke a URL later, or do anything fancy then just return the worker.
89
+ function getWorker(script, onmessage) {
90
+ var url = getURL(script);
91
+ if (url) {
92
+ var worker = new Worker(url);
93
+ worker.onmessage = onmessage;
94
+ return worker;
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ return {
101
+ getURL: getURL,
102
+ getWorker: getWorker
103
+ };
104
+
105
+ })();
106
+
107
+ // setupClipboard: bind to clipboard events (intended for document.body)
108
+ function setupClipboard(element, opts) {
109
+
110
+ if (!FileReaderJS.enabled) {
111
+ return;
112
+ }
113
+ var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
114
+
115
+ element.addEventListener("paste", onpaste, false);
116
+
117
+ function onpaste(e) {
118
+ var files = [];
119
+ var clipboardData = e.clipboardData || {};
120
+ var items = clipboardData.items || [];
121
+
122
+ for (var i = 0; i < items.length; i++) {
123
+ var file = items[i].getAsFile();
124
+
125
+ if (file) {
126
+
127
+ // Create a fake file name for images from clipboard, since this data doesn't get sent
128
+ var matches = new RegExp("/\(.*\)").exec(file.type);
129
+ if (!file.name && matches) {
130
+ var extension = matches[1];
131
+ file.name = "clipboard" + i + "." + extension;
132
+ }
133
+
134
+ files.push(file);
135
+ }
136
+ }
137
+
138
+ if (files.length) {
139
+ processFileList(e, files, instanceOptions);
140
+ e.preventDefault();
141
+ e.stopPropagation();
142
+ }
143
+ }
144
+ }
145
+
146
+ // setupInput: bind the 'change' event to an input[type=file]
147
+ function setupInput(input, opts) {
148
+
149
+ if (!FileReaderJS.enabled) {
150
+ return;
151
+ }
152
+ var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
153
+
154
+ input.addEventListener("change", inputChange, false);
155
+ input.addEventListener("drop", inputDrop, false);
156
+
157
+ function inputChange(e) {
158
+ processFileList(e, input.files, instanceOptions);
159
+ }
160
+
161
+ function inputDrop(e) {
162
+ e.stopPropagation();
163
+ e.preventDefault();
164
+ processFileList(e, e.dataTransfer.files, instanceOptions);
165
+ }
166
+ }
167
+
168
+ // setupDrop: bind the 'drop' event for a DOM element
169
+ function setupDrop(dropbox, opts) {
170
+
171
+ if (!FileReaderJS.enabled) {
172
+ return;
173
+ }
174
+ var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
175
+ var dragClass = instanceOptions.dragClass;
176
+ var initializedOnBody = false;
177
+
178
+ // Bind drag events to the dropbox to add the class while dragging, and accept the drop data transfer.
179
+ dropbox.addEventListener("dragenter", onlyWithFiles(dragenter), false);
180
+ dropbox.addEventListener("dragleave", onlyWithFiles(dragleave), false);
181
+ dropbox.addEventListener("dragover", onlyWithFiles(dragover), false);
182
+ dropbox.addEventListener("drop", onlyWithFiles(drop), false);
183
+
184
+ // Bind to body to prevent the dropbox events from firing when it was initialized on the page.
185
+ document.body.addEventListener("dragstart", bodydragstart, true);
186
+ document.body.addEventListener("dragend", bodydragend, true);
187
+ document.body.addEventListener("drop", bodydrop, false);
188
+
189
+ function bodydragend(e) {
190
+ initializedOnBody = false;
191
+ }
192
+
193
+ function bodydragstart(e) {
194
+ initializedOnBody = true;
195
+ }
196
+
197
+ function bodydrop(e) {
198
+ if (e.dataTransfer.files && e.dataTransfer.files.length) {
199
+ e.stopPropagation();
200
+ e.preventDefault();
201
+ }
202
+ }
203
+
204
+ function onlyWithFiles(fn) {
205
+ return function () {
206
+ if (!initializedOnBody) {
207
+ fn.apply(this, arguments);
208
+ }
209
+ };
210
+ }
211
+
212
+ function drop(e) {
213
+ e.stopPropagation();
214
+ e.preventDefault();
215
+ if (dragClass) {
216
+ removeClass(dropbox, dragClass);
217
+ }
218
+ processFileList(e, e.dataTransfer.files, instanceOptions);
219
+ }
220
+
221
+ function dragenter(e) {
222
+ e.stopPropagation();
223
+ e.preventDefault();
224
+ if (dragClass) {
225
+ addClass(dropbox, dragClass);
226
+ }
227
+ }
228
+
229
+ function dragleave(e) {
230
+ if (dragClass) {
231
+ removeClass(dropbox, dragClass);
232
+ }
233
+ }
234
+
235
+ function dragover(e) {
236
+ e.stopPropagation();
237
+ e.preventDefault();
238
+ if (dragClass) {
239
+ addClass(dropbox, dragClass);
240
+ }
241
+ }
242
+ }
243
+
244
+ // setupCustomFileProperties: modify the file object with extra properties
245
+ function setupCustomFileProperties(files, groupID) {
246
+ for (var i = 0; i < files.length; i++) {
247
+ var file = files[i];
248
+ file.extra = {
249
+ nameNoExtension: file.name.substring(0, file.name.lastIndexOf('.')),
250
+ extension: file.name.substring(file.name.lastIndexOf('.') + 1),
251
+ fileID: i,
252
+ uniqueID: getUniqueID(),
253
+ groupID: groupID,
254
+ prettySize: prettySize(file.size)
255
+ };
256
+ }
257
+ }
258
+
259
+ // getReadAsMethod: return method name for 'readAs*' - http://www.w3.org/TR/FileAPI/#reading-a-file
260
+ function getReadAsMethod(type, readAsMap, readAsDefault) {
261
+ for (var r in readAsMap) {
262
+ if (type.match(new RegExp(r))) {
263
+ return 'readAs' + readAsMap[r];
264
+ }
265
+ }
266
+ return 'readAs' + readAsDefault;
267
+ }
268
+
269
+ // processFileList: read the files with FileReader, send off custom events.
270
+ function processFileList(e, files, opts) {
271
+
272
+ var filesLeft = files.length;
273
+ var group = {
274
+ groupID: getGroupID(),
275
+ files: files,
276
+ started: new Date()
277
+ };
278
+
279
+ function groupEnd() {
280
+ group.ended = new Date();
281
+ opts.on.groupend(group);
282
+ }
283
+
284
+ function groupFileDone() {
285
+ if (--filesLeft === 0) {
286
+ groupEnd();
287
+ }
288
+ }
289
+
290
+ FileReaderJS.output.push(group);
291
+ setupCustomFileProperties(files, group.groupID);
292
+
293
+ opts.on.groupstart(group);
294
+
295
+ // No files in group - end immediately
296
+ if (!files.length) {
297
+ groupEnd();
298
+ return;
299
+ }
300
+
301
+ var sync = FileReaderJS.sync && FileReaderSyncSupport;
302
+ var syncWorker;
303
+
304
+ // Only initialize the synchronous worker if the option is enabled - to prevent the overhead
305
+ if (sync) {
306
+ syncWorker = WorkerHelper.getWorker(workerScript, function (e) {
307
+ var file = e.data.file;
308
+ var result = e.data.result;
309
+
310
+ // Workers seem to lose the custom property on the file object.
311
+ if (!file.extra) {
312
+ file.extra = e.data.extra;
313
+ }
314
+
315
+ file.extra.ended = new Date();
316
+
317
+ // Call error or load event depending on success of the read from the worker.
318
+ opts.on[result === "error" ? "error" : "load"]({ target: { result: result } }, file);
319
+ groupFileDone();
320
+
321
+ });
322
+ }
323
+
324
+ Array.prototype.forEach.call(files, function (file) {
325
+
326
+ file.extra.started = new Date();
327
+
328
+ if (opts.accept && !file.type.match(new RegExp(opts.accept))) {
329
+ opts.on.skip(file);
330
+ groupFileDone();
331
+ return;
332
+ }
333
+
334
+ if (opts.on.beforestart(file) === false) {
335
+ opts.on.skip(file);
336
+ groupFileDone();
337
+ return;
338
+ }
339
+
340
+ var readAs = getReadAsMethod(file.type, opts.readAsMap, opts.readAsDefault);
341
+
342
+ if (sync && syncWorker) {
343
+ syncWorker.postMessage({
344
+ file: file,
345
+ extra: file.extra,
346
+ readAs: readAs
347
+ });
348
+ }
349
+ else {
350
+
351
+ var reader = new FileReader();
352
+ reader.originalEvent = e;
353
+
354
+ fileReaderEvents.forEach(function (eventName) {
355
+ reader['on' + eventName] = function (e) {
356
+ if (eventName == 'load' || eventName == 'error') {
357
+ file.extra.ended = new Date();
358
+ }
359
+ opts.on[eventName](e, file);
360
+ if (eventName == 'loadend') {
361
+ groupFileDone();
362
+ }
363
+ };
364
+ });
365
+
366
+ reader[readAs](file);
367
+ }
368
+ });
369
+ }
370
+
371
+ // checkFileReaderSyncSupport: Create a temporary worker and see if FileReaderSync exists
372
+ function checkFileReaderSyncSupport() {
373
+ var worker = WorkerHelper.getWorker(syncDetectionScript, function (e) {
374
+ FileReaderSyncSupport = e.data;
375
+ });
376
+
377
+ if (worker) {
378
+ worker.postMessage({});
379
+ }
380
+ }
381
+
382
+ // noop: do nothing
383
+ function noop() {
384
+
385
+ }
386
+
387
+ // extend: used to make deep copies of options object
388
+ function extend(destination, source) {
389
+ for (var property in source) {
390
+ if (source[property] && source[property].constructor &&
391
+ source[property].constructor === Object) {
392
+ destination[property] = destination[property] || {};
393
+ arguments.callee(destination[property], source[property]);
394
+ }
395
+ else {
396
+ destination[property] = source[property];
397
+ }
398
+ }
399
+ return destination;
400
+ }
401
+
402
+ // hasClass: does an element have the css class?
403
+ function hasClass(el, name) {
404
+ return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(el.className);
405
+ }
406
+
407
+ // addClass: add the css class for the element.
408
+ function addClass(el, name) {
409
+ if (!hasClass(el, name)) {
410
+ el.className = el.className ? [el.className, name].join(' ') : name;
411
+ }
412
+ }
413
+
414
+ // removeClass: remove the css class from the element.
415
+ function removeClass(el, name) {
416
+ if (hasClass(el, name)) {
417
+ var c = el.className;
418
+ el.className = c.replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
419
+ }
420
+ }
421
+
422
+ // prettySize: convert bytes to a more readable string.
423
+ function prettySize(bytes) {
424
+ var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
425
+ var e = Math.floor(Math.log(bytes) / Math.log(1024));
426
+ return (bytes / Math.pow(1024, Math.floor(e))).toFixed(2) + " " + s[e];
427
+ }
428
+
429
+ // getGroupID: generate a unique int ID for groups.
430
+ var getGroupID = (function (id) {
431
+ return function () {
432
+ return id++;
433
+ };
434
+ })(0);
435
+
436
+ // getUniqueID: generate a unique int ID for files
437
+ var getUniqueID = (function (id) {
438
+ return function () {
439
+ return id++;
440
+ };
441
+ })(0);
442
+
443
+ // The interface is supported, bind the FileReaderJS callbacks
444
+ FileReaderJS.enabled = true;
445
+
446
+ })(this, document);
js/markdown/adminhtml/highlight.pack.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ /* https://github.com/isagalaev/highlight.js */
2
+ var hljs=new function(){function l(o){return o.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o<p.length;o++){if(e[p[o]]||p[o]=="no-highlight"){return p[o]}}}function c(q){var o=[];(function p(r,s){for(var t=r.firstChild;t;t=t.nextSibling){if(t.nodeType==3){s+=t.nodeValue.length}else{if(t.nodeName=="BR"){s+=1}else{if(t.nodeType==1){o.push({event:"start",offset:s,node:t});s=p(t,s);o.push({event:"stop",offset:s,node:t})}}}}return s})(q,0);return o}function j(x,v,w){var p=0;var y="";var r=[];function t(){if(x.length&&v.length){if(x[0].offset!=v[0].offset){return(x[0].offset<v[0].offset)?x:v}else{return v[0].event=="start"?x:v}}else{return x.length?x:v}}function s(A){function z(B){return" "+B.nodeName+'="'+l(B.value)+'"'}return"<"+A.nodeName+Array.prototype.map.call(A.attributes,z).join("")+">"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("</"+o.nodeName.toLowerCase()+">")}while(o!=u.node);r.splice(q,1);while(q<r.length){y+=s(r[q]);q++}}}}return y+l(w.substr(p))}function f(q){function o(s,r){return RegExp(s,"m"+(q.cI?"i":"")+(r?"g":""))}function p(y,w){if(y.compiled){return}y.compiled=true;var s=[];if(y.k){var r={};function z(A,t){t.split(" ").forEach(function(B){var C=B.split("|");r[C[0]]=[A,C[1]?Number(C[1]):1];s.push(C[0])})}y.lR=o(y.l||hljs.IR,true);if(typeof y.k=="string"){z("keyword",y.k)}else{for(var x in y.k){if(!y.k.hasOwnProperty(x)){continue}z(x,y.k[x])}}y.k=r}if(w){if(y.bWK){y.b="\\b("+s.join("|")+")\\s"}y.bR=o(y.b?y.b:"\\B|\\b");if(!y.e&&!y.eW){y.e="\\B|\\b"}if(y.e){y.eR=o(y.e)}y.tE=y.e||"";if(y.eW&&w.tE){y.tE+=(y.e?"|":"")+w.tE}}if(y.i){y.iR=o(y.i)}if(y.r===undefined){y.r=1}if(!y.c){y.c=[]}for(var v=0;v<y.c.length;v++){if(y.c[v]=="self"){y.c[v]=y}p(y.c[v],y)}if(y.starts){p(y.starts,w)}var u=[];for(var v=0;v<y.c.length;v++){u.push(y.c[v].b)}if(y.tE){u.push(y.tE)}if(y.i){u.push(y.i)}y.t=u.length?o(u.join("|"),true):{exec:function(t){return null}}}p(q)}function d(D,E){function o(r,M){for(var L=0;L<M.c.length;L++){var K=M.c[L].bR.exec(r);if(K&&K.index==0){return M.c[L]}}}function s(K,r){if(K.e&&K.eR.test(r)){return K}if(K.eW){return s(K.parent,r)}}function t(r,K){return K.i&&K.iR.test(r)}function y(L,r){var K=F.cI?r[0].toLowerCase():r[0];return L.k.hasOwnProperty(K)&&L.k[K]}function G(){var K=l(w);if(!A.k){return K}var r="";var N=0;A.lR.lastIndex=0;var L=A.lR.exec(K);while(L){r+=K.substr(N,L.index-N);var M=y(A,L);if(M){v+=M[1];r+='<span class="'+M[0]+'">'+L[0]+"</span>"}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return'<span class="'+r.language+'">'+r.value+"</span>"}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'<span class="'+L.cN+'">':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+="</span>"}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"<br>")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"<!--",e:"-->",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{title:"style"},c:[b],starts:{e:"</style>",rE:true,sL:"css"}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs);hljs.LANGUAGES.markdown=function(a){return{c:[{cN:"header",b:"^#{1,3}",e:"$"},{cN:"header",b:"^.+?\\n[=-]{2,}$"},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",b:"\\*.+?\\*"},{cN:"emphasis",b:"_.+?_",r:0},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",b:"`.+?`"},{cN:"code",b:"^ ",e:"$",r:0},{cN:"horizontal_rule",b:"^-{3,}",e:"$"},{b:"\\[.+?\\]\\(.+?\\)",rB:true,c:[{cN:"link_label",b:"\\[.+\\]"},{cN:"link_url",b:"\\(",e:"\\)",eB:true,eE:true}]}]}}(hljs);
js/markdown/adminhtml/highlight/styles/xcode.css ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+
3
+ XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>
4
+
5
+ */
6
+
7
+ pre code {
8
+ display: block; padding: 0.5em;
9
+ background: #fff; color: black;
10
+ }
11
+
12
+ pre .comment,
13
+ pre .template_comment,
14
+ pre .javadoc,
15
+ pre .comment * {
16
+ color: rgb(0,106,0);
17
+ }
18
+
19
+ pre .keyword,
20
+ pre .literal,
21
+ pre .nginx .title {
22
+ color: rgb(170,13,145);
23
+ }
24
+ pre .method,
25
+ pre .list .title,
26
+ pre .tag .title,
27
+ pre .setting .value,
28
+ pre .winutils,
29
+ pre .tex .command,
30
+ pre .http .title,
31
+ pre .request,
32
+ pre .status {
33
+ color: #008;
34
+ }
35
+
36
+ pre .envvar,
37
+ pre .tex .special {
38
+ color: #660;
39
+ }
40
+
41
+ pre .string {
42
+ color: rgb(196,26,22);
43
+ }
44
+ pre .tag .value,
45
+ pre .cdata,
46
+ pre .filter .argument,
47
+ pre .attr_selector,
48
+ pre .apache .cbracket,
49
+ pre .date,
50
+ pre .regexp {
51
+ color: #080;
52
+ }
53
+
54
+ pre .sub .identifier,
55
+ pre .pi,
56
+ pre .tag,
57
+ pre .tag .keyword,
58
+ pre .decorator,
59
+ pre .ini .title,
60
+ pre .shebang,
61
+ pre .prompt,
62
+ pre .hexcolor,
63
+ pre .rules .value,
64
+ pre .css .value .number,
65
+ pre .symbol,
66
+ pre .symbol .string,
67
+ pre .number,
68
+ pre .css .function,
69
+ pre .clojure .title,
70
+ pre .clojure .built_in {
71
+ color: rgb(28,0,207);
72
+ }
73
+
74
+ pre .class .title,
75
+ pre .haskell .type,
76
+ pre .smalltalk .class,
77
+ pre .javadoctag,
78
+ pre .yardoctag,
79
+ pre .phpdoc,
80
+ pre .typename,
81
+ pre .tag .attribute,
82
+ pre .doctype,
83
+ pre .class .id,
84
+ pre .built_in,
85
+ pre .setting,
86
+ pre .params,
87
+ pre .clojure .attribute {
88
+ color: rgb(92,38,153);
89
+ }
90
+
91
+ pre .variable {
92
+ color: rgb(63,110,116);
93
+ }
94
+ pre .css .tag,
95
+ pre .rules .property,
96
+ pre .pseudo,
97
+ pre .subst {
98
+ color: #000;
99
+ }
100
+
101
+ pre .css .class, pre .css .id {
102
+ color: #9B703F;
103
+ }
104
+
105
+ pre .value .important {
106
+ color: #ff7700;
107
+ font-weight: bold;
108
+ }
109
+
110
+ pre .rules .keyword {
111
+ color: #C5AF75;
112
+ }
113
+
114
+ pre .annotation,
115
+ pre .apache .sqbracket,
116
+ pre .nginx .built_in {
117
+ color: #9B859D;
118
+ }
119
+
120
+ pre .preprocessor,
121
+ pre .preprocessor * {
122
+ color: rgb(100,56,32);
123
+ }
124
+
125
+ pre .tex .formula {
126
+ background-color: #EEE;
127
+ font-style: italic;
128
+ }
129
+
130
+ pre .diff .header,
131
+ pre .chunk {
132
+ color: #808080;
133
+ font-weight: bold;
134
+ }
135
+
136
+ pre .diff .change {
137
+ background-color: #BCCFF9;
138
+ }
139
+
140
+ pre .addition {
141
+ background-color: #BAEEBA;
142
+ }
143
+
144
+ pre .deletion {
145
+ background-color: #FFC8BD;
146
+ }
147
+
148
+ pre .comment .yardoctag {
149
+ font-weight: bold;
150
+ }
151
+
152
+ pre .method .id {
153
+ color: #000;
154
+ }
js/markdown/adminhtml/markdown.js ADDED
@@ -0,0 +1,715 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @category SchumacherFM_Markdown
3
+ * @package JavaScript
4
+ * @author Cyrill at Schumacher dot fm / @SchumacherFM
5
+ * @copyright Copyright (c)
6
+ */
7
+ /*global $,marked,varienGlobalEvents,Ajax,hljs,FileReaderJS,Event,encode_base64,reMarked*/
8
+ ;
9
+ (function () {
10
+ 'use strict';
11
+ var
12
+ _markDownGlobalConfig = {},
13
+ epicEditorInstances = {},
14
+ EPIC_EDITOR_PREFIX = 'epiceditor_EE_',
15
+ isViewMarkdownSourceHtml = false,
16
+ _initializedFileReaderContainer = {},
17
+ _textAreaCurrentCaretObject = {}, // set by the onClick event
18
+ _toggleMarkdownSourceOriginalMarkdown = '';
19
+
20
+ /**
21
+ *
22
+ * @param str string
23
+ * @returns boolean|string
24
+ * @private
25
+ */
26
+ function _checkHttp(str) {
27
+
28
+ if (!str || false === str || str.indexOf('http') === -1) {
29
+ return false;
30
+ }
31
+ return str;
32
+ }
33
+
34
+ /**
35
+ * inits the global md config
36
+ * @returns bool
37
+ * @private
38
+ */
39
+ function _initGlobalConfig() {
40
+
41
+ var config = JSON.parse($('markdownGlobalConfig').readAttribute('data-config') || '{}');
42
+ if (config.dt === undefined) {
43
+ return console.log('Markdown Global Config not found. General error!');
44
+ }
45
+
46
+ _markDownGlobalConfig = {
47
+ tag: decodeURIComponent(config.dt),
48
+ uploadUrl: _checkHttp(config.fuu || false),
49
+ mediaBaseUrl: _checkHttp(config.phi || false),
50
+ extraRendererUrl: _checkHttp(config.eru || false),
51
+ eeLoadOnClick: config.eeloc || false,
52
+ reMarkedCfg: decodeURIComponent(config.rmc || '{}').evalJSON(true)
53
+ };
54
+ return true;
55
+ }
56
+
57
+ /**
58
+ *
59
+ * @param variable mixed
60
+ * @returns {boolean}
61
+ * @private
62
+ */
63
+ function _isObject(variable) {
64
+ return Object.prototype.toString.call(variable) === '[object Object]';
65
+ }
66
+
67
+ /**
68
+ *
69
+ * @param variable mixed
70
+ * @returns {boolean}
71
+ * @private
72
+ */
73
+ function _isFunction(variable) {
74
+ return Object.prototype.toString.call(variable) === '[object Function]';
75
+ }
76
+
77
+ /**
78
+ *
79
+ * @returns {boolean}
80
+ * @private
81
+ */
82
+ function _isEpicEditorEnabled() {
83
+ return window.EpicEditor !== undefined;
84
+ }
85
+
86
+ /**
87
+ *
88
+ * @returns {boolean}
89
+ * @private
90
+ */
91
+ function _isFileReaderEnabled() {
92
+ return window.FileReader !== undefined;
93
+ }
94
+
95
+ function mdExternalUrl(url) {
96
+ window.open(url);
97
+ }
98
+
99
+ /**
100
+ *
101
+ * @param textareaId
102
+ */
103
+ function toggleMarkdown(textareaId) {
104
+
105
+
106
+ if ($(textareaId).value.indexOf(_markDownGlobalConfig.tag) === -1) {
107
+
108
+ var instance = epicEditorInstances[textareaId] || false;
109
+ if (instance && instance.is('loaded')) {
110
+ instance.getElement('editor').body.innerHTML = _markDownGlobalConfig.tag + "<br>\n" + instance.getElement('editor').body.innerHTML;
111
+ } else {
112
+ $(textareaId).value = _markDownGlobalConfig.tag + "\n" + $(textareaId).value;
113
+ }
114
+ }
115
+ alert('Markdown enabled with tag: "' + _markDownGlobalConfig.tag + '"');
116
+ }
117
+
118
+ /**
119
+ * Shows the generated source html code
120
+ * @param object element
121
+ * @param string textAreaId
122
+ * @returns uninteresting
123
+ */
124
+ function toggleMarkdownSource(element, textAreaId) {
125
+ var _loadEpic = false,
126
+ _instance,
127
+ $textAreaId = $(textAreaId);
128
+
129
+ if (true === isViewMarkdownSourceHtml) {
130
+ isViewMarkdownSourceHtml = false;
131
+ element.removeClassName('success');
132
+
133
+ // restore original markdown, if not it is lost
134
+ if (_toggleMarkdownSourceOriginalMarkdown.length > 10) {
135
+ $textAreaId.writeAttribute('readonly', false);
136
+ $textAreaId.value = _toggleMarkdownSourceOriginalMarkdown;
137
+ _toggleMarkdownSourceOriginalMarkdown = '';
138
+ }
139
+ return;
140
+ }
141
+
142
+ if (_markDownGlobalConfig.tag !== '' && $textAreaId.value.indexOf(_markDownGlobalConfig.tag) === -1) {
143
+ alert('Markdown not found');
144
+ return false;
145
+ }
146
+
147
+ _instance = epicEditorInstances[textAreaId] || false;
148
+ _loadEpic = _isEpicEditorEnabled() && (false === _instance || (false !== _instance && _instance.is('unloaded')));
149
+
150
+ if (true === _loadEpic) {
151
+ toggleEpicEditor(element, textAreaId);
152
+ _instance = epicEditorInstances[textAreaId] || false;
153
+ }
154
+
155
+ isViewMarkdownSourceHtml = true;
156
+ element.addClassName('success');
157
+
158
+ if (_instance && _isObject(_instance)) {
159
+ _instance.preview();
160
+ } else {
161
+ _toggleMarkdownSourceOriginalMarkdown = $textAreaId.value;
162
+ // no cache available oroginal MD is lost.
163
+ $textAreaId.value = _parserDefault(_toggleMarkdownSourceOriginalMarkdown, $textAreaId);
164
+ $textAreaId.writeAttribute('readonly', true);
165
+ }
166
+ }
167
+
168
+ /**
169
+ *
170
+ * @param string content
171
+ * @returns {promise.Promise}
172
+ * @private
173
+ */
174
+ function _mdExtraRender(content) {
175
+
176
+ var p = new promise.Promise(),
177
+ ar = new Ajax.Request(_markDownGlobalConfig.extraRendererUrl, {
178
+ onSuccess: function (response) {
179
+ p.done(null, response.responseText);
180
+ },
181
+ method: 'post',
182
+ parameters: {
183
+ 'content': content
184
+ }
185
+ });
186
+
187
+ return p;
188
+ }
189
+
190
+ /**
191
+ * so rendering via markdown extra works only if there is one textarea field on the page
192
+ * which creates one instance ... this limitation is due to the promise -> then() ... maybe there are better ways
193
+ * fallback is marked()
194
+ * @private
195
+ */
196
+ function _getEpicEditorActiveInstance() {
197
+
198
+ if (Object.keys(epicEditorInstances).length !== 1) {
199
+ return false;
200
+ }
201
+
202
+ var keys = Object.keys(epicEditorInstances),
203
+ oneKey = keys[0];
204
+
205
+ return epicEditorInstances[oneKey];
206
+ }
207
+
208
+ /**
209
+ *
210
+ * @param string htmlString
211
+ * @returns string
212
+ * @private
213
+ */
214
+ function _highlight(htmlString) {
215
+ if (true === isViewMarkdownSourceHtml) {
216
+ htmlString = '<pre class="hljs">' + hljs.highlight('xml', htmlString).value + '</pre>';
217
+ }
218
+ return htmlString;
219
+ }
220
+
221
+ /**
222
+ *
223
+ * @param content string
224
+ * @returns string
225
+ * @private
226
+ */
227
+ function _parserBefore(content) {
228
+ var imgUrl = '',
229
+ mediaRegex = /\{\{media\s+url="([^"]+)"\s*\}\}/i,
230
+ matches = null;
231
+
232
+ if (_markDownGlobalConfig.tag !== '') {
233
+ content = content.replace(_markDownGlobalConfig.tag, '');
234
+ }
235
+
236
+ if (false !== _markDownGlobalConfig.mediaBaseUrl) {
237
+ while (mediaRegex.test(content)) {
238
+ matches = mediaRegex.exec(content);
239
+ if (null !== matches && matches[1] !== undefined) {
240
+ imgUrl = _markDownGlobalConfig.mediaBaseUrl + matches[1];
241
+ content = content.replace(matches[0], imgUrl);
242
+ }
243
+ }
244
+ }
245
+ return content;
246
+ }
247
+
248
+ /**
249
+ *
250
+ * default parsing without syntax highlightning
251
+ *
252
+ * @param string content
253
+ * @param object $textArea
254
+ * @returns string
255
+ * @private
256
+ */
257
+ function _parserDefault(content, $textArea) {
258
+ var pContent = {};
259
+
260
+ if (content.length > 10 && _markDownGlobalConfig.extraRendererUrl) {
261
+ pContent = _mdExtraRender(content);
262
+ pContent.then(function (error, html) {
263
+ $textArea.value = html;
264
+ });
265
+ return '<h3>Preview will be available shortly ...</h3>';
266
+ }
267
+ return marked(_parserBefore(content));
268
+ }
269
+
270
+ /**
271
+ * todo replace {{media url=""}} with a dummy preview image, otherwise loading errors will occur
272
+ * also test that in product and categorie desc fields
273
+ * @param string content
274
+ * @param object $textArea
275
+ * @returns string
276
+ * @private
277
+ */
278
+ function _parserEpicEditor(content, $textArea) {
279
+ var currentActiveInstance = _getEpicEditorActiveInstance(),
280
+ pContent = {};
281
+
282
+ if (content.length > 10 && _markDownGlobalConfig.extraRendererUrl) {
283
+ pContent = _mdExtraRender(content);
284
+ pContent.then(function (error, html) {
285
+ if (currentActiveInstance && currentActiveInstance.is('loaded')) {
286
+ currentActiveInstance.getElement('previewer').body.innerHTML = _highlight(html);
287
+ } else {
288
+ $textArea.value = html;
289
+ }
290
+ });
291
+ return _highlight('<h3>Preview will be available shortly ...</h3>');
292
+ }
293
+
294
+ return _highlight(marked(_parserBefore(content)));
295
+ }
296
+
297
+ /**
298
+ *
299
+ * @returns {{container: null, textarea: null, basePath: string, clientSideStorage: boolean, parser: Function, localStorageName: string, useNativeFullscreen: boolean, file: {name: string, defaultContent: string, autoSave: number}, theme: {base: string, preview: string, editor: string}, button: {preview: boolean, fullscreen: boolean, bar: string}, focusOnLoad: boolean, shortcut: {modifier: number, fullscreen: number, preview: number}, string: {togglePreview: string, toggleEdit: string, toggleFullscreen: string}, autogrow: {minHeight: number, maxHeight: number, scroll: boolean}}}
300
+ * @private
301
+ */
302
+ function _getDefaultEpicEditorOptions() {
303
+ return {
304
+ container: null,
305
+ textarea: null,
306
+ basePath: null, // will be set via Mage Helper
307
+ clientSideStorage: true,
308
+ parser: _parserEpicEditor,
309
+ localStorageName: 'epiceditor',
310
+ useNativeFullscreen: true,
311
+ file: {
312
+ name: 'epiceditor',
313
+ defaultContent: '',
314
+ autoSave: 100
315
+ },
316
+ theme: {
317
+ base: 'themes/base/epiceditor.css',
318
+ preview: 'themes/preview/githubNxcode.css',
319
+ editor: 'themes/editor/epic-light.css'
320
+ },
321
+ button: {
322
+ preview: true,
323
+ fullscreen: true,
324
+ bar: "show"
325
+ },
326
+ focusOnLoad: false,
327
+ shortcut: {
328
+ modifier: 18,
329
+ fullscreen: 70,
330
+ preview: 80
331
+ },
332
+ string: {
333
+ togglePreview: 'Toggle Preview Mode',
334
+ toggleEdit: 'Toggle Edit Mode',
335
+ toggleFullscreen: 'Enter Fullscreen'
336
+ },
337
+ autogrow: {
338
+ minHeight: 400,
339
+ maxHeight: 700,
340
+ scroll: true
341
+ }
342
+ };
343
+ }
344
+
345
+ /**
346
+ *
347
+ * @param event
348
+ * @param element
349
+ * @private
350
+ */
351
+ function _createEpicEditorInstances(event, element) {
352
+
353
+ if (element === null || element === undefined) {
354
+ throw 'Wysiwyg only bug ...';
355
+ }
356
+
357
+ var
358
+ epicHtmlId = EPIC_EDITOR_PREFIX + (element.id || ''),
359
+ $epicHtmlId = $(epicHtmlId),
360
+ textAreaId = element.id || '',
361
+ editorOptions = _getDefaultEpicEditorOptions(),
362
+ instanceId = textAreaId,
363
+ epicEditorInstance = {},
364
+ userConfig = {};
365
+
366
+ if (!epicEditorInstances[instanceId]) {
367
+ userConfig = decodeURIComponent($epicHtmlId.readAttribute('data-config') || '{}').evalJSON(true);
368
+
369
+ Object.extend(editorOptions, userConfig);
370
+ editorOptions.container = epicHtmlId;
371
+ editorOptions.textarea = textAreaId;
372
+ editorOptions.localStorageName = textAreaId;
373
+
374
+ element.hide();
375
+ epicEditorInstance = new window.EpicEditor(editorOptions);
376
+ epicEditorInstance
377
+ .on('load', function () {
378
+ $epicHtmlId.setStyle({
379
+ display: 'block',
380
+ height: parseInt(editorOptions.autogrow.maxHeight || 700, 10) + 'px'
381
+ });
382
+ epicEditorInstance.reflow();
383
+ })
384
+ .on('unload', function () {
385
+ $epicHtmlId.setStyle({
386
+ display: 'none'
387
+ });
388
+ });
389
+ epicEditorInstances[instanceId] = epicEditorInstance.load();
390
+ }
391
+ }
392
+
393
+ /**
394
+ *
395
+ * @param element this
396
+ * @param textAreaId string
397
+ * @return false
398
+ */
399
+ function toggleEpicEditor(element, textAreaId) {
400
+
401
+ var
402
+ instanceId = textAreaId,
403
+ instance = epicEditorInstances[instanceId] || false;
404
+
405
+ if (false === instance) {
406
+ _createEpicEditorInstances(null, $(textAreaId));
407
+ element.addClassName('success');
408
+ return false;
409
+ }
410
+
411
+ if (instance.is('loaded')) {
412
+ instance.unload();
413
+ $(textAreaId).show();
414
+ element.removeClassName('success');
415
+ } else {
416
+ $(textAreaId).hide();
417
+ instance.load();
418
+ element.addClassName('success');
419
+ }
420
+ return false;
421
+ }
422
+
423
+ /**
424
+ *
425
+ * @param fileUrl
426
+ * @returns {boolean}
427
+ * @private
428
+ */
429
+ function _fileReaderAddImageToMarkdown(fileUrl) {
430
+
431
+ var
432
+ mdTpl = ' ![Alt_Text](' + fileUrl + ' "Logo_Title_Text") ',
433
+ prefix = _textAreaCurrentCaretObject.value.substring(0, _textAreaCurrentCaretObject.selectionEnd),
434
+ suffix = _textAreaCurrentCaretObject.value.substring(_textAreaCurrentCaretObject.selectionEnd);
435
+
436
+ _textAreaCurrentCaretObject.value = prefix + mdTpl + suffix;
437
+ prefix = '';
438
+ suffix = '';
439
+ return true;
440
+ }
441
+
442
+ /**
443
+ *
444
+ * @param target event.target
445
+ * @private
446
+ */
447
+ function _createFileReaderInstance(target) {
448
+
449
+ if (encode_base64 === undefined) {
450
+ return console.log('FileReader not available because method encode_base64() is missing!');
451
+ }
452
+
453
+ if (false === _markDownGlobalConfig.uploadUrl) {
454
+ return console.log('FileReader upload url not available!');
455
+ }
456
+
457
+ var opts = {
458
+ dragClass: 'fReaderDrag',
459
+ accept: 'image/*',
460
+ readAsMap: {
461
+ 'image/*': 'BinaryString'
462
+ },
463
+ readAsDefault: 'BinaryString',
464
+ on: {
465
+ load: function (e, file) {
466
+
467
+ var ar = new Ajax.Request(_markDownGlobalConfig.uploadUrl, {
468
+ onSuccess: function (response) {
469
+ var result = JSON.parse(response.responseText);
470
+ if (result && _isObject(result)) {
471
+ if (result.err === false) {
472
+ return _fileReaderAddImageToMarkdown(result.fileUrl);
473
+ }
474
+ if (result.err === true) {
475
+ alert('An error occurred:\n' + result.msg);
476
+ }
477
+ } else {
478
+ alert('An error occurred after uploading. No JSON found ...');
479
+ }
480
+ return false;
481
+ },
482
+ method: 'post',
483
+ parameters: {
484
+ 'binaryData': encode_base64(e.target.result),
485
+ 'file': JSON.stringify(file)
486
+ }
487
+ });
488
+
489
+ },
490
+ error: function (e, file) {
491
+ // Native ProgressEvent
492
+ alert('An error occurred. Please see console.log');
493
+ return console.log('error: ', e, file);
494
+ },
495
+ skip: function (e, file) {
496
+ return console.log('File format is not supported', file);
497
+ }
498
+ }
499
+ };
500
+
501
+ FileReaderJS.setupDrop(target, opts);
502
+ _initializedFileReaderContainer[target.id] = true;
503
+ }
504
+
505
+ /**
506
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FileReader
507
+ * @param _epicEditorInstance window.EpicEditor loaded
508
+ * @private
509
+ */
510
+ function _createFileReader(event) {
511
+ var target = event.target || event.srcElement;
512
+
513
+ _textAreaCurrentCaretObject = target;
514
+
515
+ // check if already initialized
516
+ if (_initializedFileReaderContainer[target.id] === undefined) {
517
+ _createFileReaderInstance(target);
518
+ }
519
+ }
520
+
521
+ /**
522
+ *
523
+ * @returns {reMarked}
524
+ * @private
525
+ */
526
+ function _getReMarked() {
527
+ var options = {
528
+ link_list: false, // render links as references, create link list as appendix
529
+ h1_setext: true, // underline h1 headers
530
+ h2_setext: true, // underline h2 headers
531
+ h_atx_suf: false, // header suffixes (###)
532
+ gfm_code: false, // gfm code blocks (```)
533
+ li_bullet: "*", // list item bullet style
534
+ hr_char: "-", // hr style
535
+ indnt_str: " ", // indentation string
536
+ bold_char: "*", // char used for strong
537
+ emph_char: "_", // char used for em
538
+ gfm_del: true, // ~~strikeout~~ for <del>strikeout</del>
539
+ gfm_tbls: false, // markdown-extra tables @SchumacherFM: if true the error on line 518 in remarked.js :-(
540
+ tbl_edges: false, // show side edges on tables
541
+ hash_lnks: false, // anchors w/hash hrefs as links
542
+ br_only: false // avoid using " " as line break indicator
543
+ };
544
+ Object.extend(options, _markDownGlobalConfig.reMarkedCfg);
545
+ return new reMarked(options);
546
+ }
547
+
548
+ /**
549
+ * renders html to markdown
550
+ * @param textAreaId string
551
+ */
552
+ function htmlToMarkDown(element, textAreaId) {
553
+ var html = $(textAreaId).value || '';
554
+
555
+ var _instance = epicEditorInstances[textAreaId] || false;
556
+ var _loadedEpic = _isEpicEditorEnabled() && false !== _instance && _instance.is('loaded');
557
+ if (true === _loadedEpic) {
558
+ toggleEpicEditor(element, textAreaId);
559
+ }
560
+
561
+
562
+ if (_markDownGlobalConfig.tag !== '' && html.indexOf(_markDownGlobalConfig.tag) === -1) {
563
+ $(textAreaId).value = _markDownGlobalConfig.tag + '\n' + _getReMarked().render(html);
564
+ }
565
+ if (_markDownGlobalConfig.tag === '') {
566
+ $(textAreaId).value = _getReMarked().render(html);
567
+ }
568
+ }
569
+
570
+ /**
571
+ * loads the filereader, epiceditor
572
+ */
573
+ function _mdInitialize() {
574
+ _initGlobalConfig();
575
+ var parentElementIds = ['product_edit_form', 'edit_form', 'category-edit-container', 'email_template_edit_form'];
576
+ if (varienGlobalEvents) {
577
+ varienGlobalEvents.fireEvent('mdLoadForms', parentElementIds);
578
+ }
579
+
580
+ // loading multiple instances on one page
581
+ // only works with event delegation due category edit page ...
582
+ // fire event for customization varienGlobalEvents.attachEventHandler('showTab', function (e) {...}
583
+ parentElementIds.forEach(function (elementId) {
584
+ var $elementId = $(elementId);
585
+ if ($elementId) {
586
+ // some things are only possible with event delegation ...
587
+ if (true === _isEpicEditorEnabled() && true === _markDownGlobalConfig.eeLoadOnClick) {
588
+ $elementId.on('click', 'textarea.initEpicEditor', _createEpicEditorInstances);
589
+ }
590
+ if (true === _isFileReaderEnabled()) {
591
+ $elementId.on('click', 'textarea.initFileReader', _createFileReader);
592
+ }
593
+
594
+ }
595
+ });
596
+ }
597
+
598
+ this.mdExternalUrl = mdExternalUrl;
599
+ this.toggleMarkdown = toggleMarkdown;
600
+ this.toggleEpicEditor = toggleEpicEditor;
601
+ this.toggleMarkdownSource = toggleMarkdownSource;
602
+ this.htmlToMarkDown = htmlToMarkDown;
603
+
604
+ document.observe('dom:loaded', _mdInitialize);
605
+
606
+ }).
607
+ call(function () {
608
+ return this || (typeof window !== 'undefined' ? window : global);
609
+ }());
610
+
611
+ /*
612
+ * Copyright 2012-2013 (c) Pierre Duquesne <stackp@online.fr>
613
+ * Licensed under the New BSD License.
614
+ * https://github.com/stackp/promisejs
615
+ * https://raw.github.com/stackp/promisejs/master/promise.js
616
+ * modified by @SchumacherFM
617
+ */
618
+
619
+ (function (exports) {
620
+ 'use strict';
621
+
622
+ function Promise() {
623
+ this._callbacks = [];
624
+ }
625
+
626
+ Promise.prototype.then = function (func, context) {
627
+ var p;
628
+ if (this._isdone) {
629
+ p = func.apply(context, this.result);
630
+ } else {
631
+ p = new Promise();
632
+ this._callbacks.push(function () {
633
+ var res = func.apply(context, arguments);
634
+ if (res && _isFunction(res.then))
635
+ res.then(p.done, p);
636
+ });
637
+ }
638
+ return p;
639
+ };
640
+
641
+ Promise.prototype.done = function () {
642
+ this.result = arguments;
643
+ this._isdone = true;
644
+ for (var i = 0; i < this._callbacks.length; i++) {
645
+ this._callbacks[i].apply(null, arguments);
646
+ }
647
+ this._callbacks = [];
648
+ };
649
+
650
+ function join(promises) {
651
+ var p = new Promise();
652
+ var results = [];
653
+
654
+ if (!promises || !promises.length) {
655
+ p.done(results);
656
+ return p;
657
+ }
658
+
659
+ var numdone = 0;
660
+ var total = promises.length;
661
+
662
+ function notifier(i) {
663
+ return function () {
664
+ numdone += 1;
665
+ results[i] = Array.prototype.slice.call(arguments);
666
+ if (numdone === total) {
667
+ p.done(results);
668
+ }
669
+ };
670
+ }
671
+
672
+ for (var i = 0; i < total; i++) {
673
+ promises[i].then(notifier(i));
674
+ }
675
+
676
+ return p;
677
+ }
678
+
679
+ function chain(funcs, args) {
680
+ var p = new Promise();
681
+ if (funcs.length === 0) {
682
+ p.done.apply(p, args);
683
+ } else {
684
+ funcs[0].apply(null, args).then(function () {
685
+ funcs.splice(0, 1);
686
+ chain(funcs, arguments).then(function () {
687
+ p.done.apply(p, arguments);
688
+ });
689
+ });
690
+ }
691
+ return p;
692
+ }
693
+
694
+
695
+ var promise = {
696
+ Promise: Promise,
697
+ join: join,
698
+ chain: chain,
699
+
700
+ /* Error codes */
701
+ ENOXHR: 1,
702
+ ETIMEOUT: 2
703
+
704
+ };
705
+
706
+ if (typeof define === 'function' && define.amd) {
707
+ /* AMD support */
708
+ define(function () {
709
+ return promise;
710
+ });
711
+ } else {
712
+ exports.promise = promise;
713
+ }
714
+
715
+ })(this);
js/markdown/adminhtml/marked.js ADDED
@@ -0,0 +1,1165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * marked - a markdown parser
3
+ * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
4
+ * https://github.com/chjj/marked
5
+ */
6
+
7
+ ;(function() {
8
+
9
+ /**
10
+ * Block-Level Grammar
11
+ */
12
+
13
+ var block = {
14
+ newline: /^\n+/,
15
+ code: /^( {4}[^\n]+\n*)+/,
16
+ fences: noop,
17
+ hr: /^( *[-*_]){3,} *(?:\n+|$)/,
18
+ heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
19
+ nptable: noop,
20
+ lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
21
+ blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
22
+ list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
23
+ html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
24
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
25
+ table: noop,
26
+ paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
27
+ text: /^[^\n]+/
28
+ };
29
+
30
+ block.bullet = /(?:[*+-]|\d+\.)/;
31
+ block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
32
+ block.item = replace(block.item, 'gm')
33
+ (/bull/g, block.bullet)
34
+ ();
35
+
36
+ block.list = replace(block.list)
37
+ (/bull/g, block.bullet)
38
+ ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
39
+ ();
40
+
41
+ block._tag = '(?!(?:'
42
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
43
+ + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
44
+ + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
45
+
46
+ block.html = replace(block.html)
47
+ ('comment', /<!--[\s\S]*?-->/)
48
+ ('closed', /<(tag)[\s\S]+?<\/\1>/)
49
+ ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
50
+ (/tag/g, block._tag)
51
+ ();
52
+
53
+ block.paragraph = replace(block.paragraph)
54
+ ('hr', block.hr)
55
+ ('heading', block.heading)
56
+ ('lheading', block.lheading)
57
+ ('blockquote', block.blockquote)
58
+ ('tag', '<' + block._tag)
59
+ ('def', block.def)
60
+ ();
61
+
62
+ /**
63
+ * Normal Block Grammar
64
+ */
65
+
66
+ block.normal = merge({}, block);
67
+
68
+ /**
69
+ * GFM Block Grammar
70
+ */
71
+
72
+ block.gfm = merge({}, block.normal, {
73
+ fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
74
+ paragraph: /^/
75
+ });
76
+
77
+ block.gfm.paragraph = replace(block.paragraph)
78
+ ('(?!', '(?!'
79
+ + block.gfm.fences.source.replace('\\1', '\\2') + '|'
80
+ + block.list.source.replace('\\1', '\\3') + '|')
81
+ ();
82
+
83
+ /**
84
+ * GFM + Tables Block Grammar
85
+ */
86
+
87
+ block.tables = merge({}, block.gfm, {
88
+ nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
89
+ table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
90
+ });
91
+
92
+ /**
93
+ * Block Lexer
94
+ */
95
+
96
+ function Lexer(options) {
97
+ this.tokens = [];
98
+ this.tokens.links = {};
99
+ this.options = options || marked.defaults;
100
+ this.rules = block.normal;
101
+
102
+ if (this.options.gfm) {
103
+ if (this.options.tables) {
104
+ this.rules = block.tables;
105
+ } else {
106
+ this.rules = block.gfm;
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Expose Block Rules
113
+ */
114
+
115
+ Lexer.rules = block;
116
+
117
+ /**
118
+ * Static Lex Method
119
+ */
120
+
121
+ Lexer.lex = function(src, options) {
122
+ var lexer = new Lexer(options);
123
+ return lexer.lex(src);
124
+ };
125
+
126
+ /**
127
+ * Preprocessing
128
+ */
129
+
130
+ Lexer.prototype.lex = function(src) {
131
+ src = src
132
+ .replace(/\r\n|\r/g, '\n')
133
+ .replace(/\t/g, ' ')
134
+ .replace(/\u00a0/g, ' ')
135
+ .replace(/\u2424/g, '\n');
136
+
137
+ return this.token(src, true);
138
+ };
139
+
140
+ /**
141
+ * Lexing
142
+ */
143
+
144
+ Lexer.prototype.token = function(src, top) {
145
+ var src = src.replace(/^ +$/gm, '')
146
+ , next
147
+ , loose
148
+ , cap
149
+ , bull
150
+ , b
151
+ , item
152
+ , space
153
+ , i
154
+ , l;
155
+
156
+ while (src) {
157
+ // newline
158
+ if (cap = this.rules.newline.exec(src)) {
159
+ src = src.substring(cap[0].length);
160
+ if (cap[0].length > 1) {
161
+ this.tokens.push({
162
+ type: 'space'
163
+ });
164
+ }
165
+ }
166
+
167
+ // code
168
+ if (cap = this.rules.code.exec(src)) {
169
+ src = src.substring(cap[0].length);
170
+ cap = cap[0].replace(/^ {4}/gm, '');
171
+ this.tokens.push({
172
+ type: 'code',
173
+ text: !this.options.pedantic
174
+ ? cap.replace(/\n+$/, '')
175
+ : cap
176
+ });
177
+ continue;
178
+ }
179
+
180
+ // fences (gfm)
181
+ if (cap = this.rules.fences.exec(src)) {
182
+ src = src.substring(cap[0].length);
183
+ this.tokens.push({
184
+ type: 'code',
185
+ lang: cap[2],
186
+ text: cap[3]
187
+ });
188
+ continue;
189
+ }
190
+
191
+ // heading
192
+ if (cap = this.rules.heading.exec(src)) {
193
+ src = src.substring(cap[0].length);
194
+ this.tokens.push({
195
+ type: 'heading',
196
+ depth: cap[1].length,
197
+ text: cap[2]
198
+ });
199
+ continue;
200
+ }
201
+
202
+ // table no leading pipe (gfm)
203
+ if (top && (cap = this.rules.nptable.exec(src))) {
204
+ src = src.substring(cap[0].length);
205
+
206
+ item = {
207
+ type: 'table',
208
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
209
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
210
+ cells: cap[3].replace(/\n$/, '').split('\n')
211
+ };
212
+
213
+ for (i = 0; i < item.align.length; i++) {
214
+ if (/^ *-+: *$/.test(item.align[i])) {
215
+ item.align[i] = 'right';
216
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
217
+ item.align[i] = 'center';
218
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
219
+ item.align[i] = 'left';
220
+ } else {
221
+ item.align[i] = null;
222
+ }
223
+ }
224
+
225
+ for (i = 0; i < item.cells.length; i++) {
226
+ item.cells[i] = item.cells[i].split(/ *\| */);
227
+ }
228
+
229
+ this.tokens.push(item);
230
+
231
+ continue;
232
+ }
233
+
234
+ // lheading
235
+ if (cap = this.rules.lheading.exec(src)) {
236
+ src = src.substring(cap[0].length);
237
+ this.tokens.push({
238
+ type: 'heading',
239
+ depth: cap[2] === '=' ? 1 : 2,
240
+ text: cap[1]
241
+ });
242
+ continue;
243
+ }
244
+
245
+ // hr
246
+ if (cap = this.rules.hr.exec(src)) {
247
+ src = src.substring(cap[0].length);
248
+ this.tokens.push({
249
+ type: 'hr'
250
+ });
251
+ continue;
252
+ }
253
+
254
+ // blockquote
255
+ if (cap = this.rules.blockquote.exec(src)) {
256
+ src = src.substring(cap[0].length);
257
+
258
+ this.tokens.push({
259
+ type: 'blockquote_start'
260
+ });
261
+
262
+ cap = cap[0].replace(/^ *> ?/gm, '');
263
+
264
+ // Pass `top` to keep the current
265
+ // "toplevel" state. This is exactly
266
+ // how markdown.pl works.
267
+ this.token(cap, top);
268
+
269
+ this.tokens.push({
270
+ type: 'blockquote_end'
271
+ });
272
+
273
+ continue;
274
+ }
275
+
276
+ // list
277
+ if (cap = this.rules.list.exec(src)) {
278
+ src = src.substring(cap[0].length);
279
+ bull = cap[2];
280
+
281
+ this.tokens.push({
282
+ type: 'list_start',
283
+ ordered: bull.length > 1
284
+ });
285
+
286
+ // Get each top-level item.
287
+ cap = cap[0].match(this.rules.item);
288
+
289
+ next = false;
290
+ l = cap.length;
291
+ i = 0;
292
+
293
+ for (; i < l; i++) {
294
+ item = cap[i];
295
+
296
+ // Remove the list item's bullet
297
+ // so it is seen as the next token.
298
+ space = item.length;
299
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
300
+
301
+ // Outdent whatever the
302
+ // list item contains. Hacky.
303
+ if (~item.indexOf('\n ')) {
304
+ space -= item.length;
305
+ item = !this.options.pedantic
306
+ ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
307
+ : item.replace(/^ {1,4}/gm, '');
308
+ }
309
+
310
+ // Determine whether the next list item belongs here.
311
+ // Backpedal if it does not belong in this list.
312
+ if (this.options.smartLists && i !== l - 1) {
313
+ b = block.bullet.exec(cap[i + 1])[0];
314
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
315
+ src = cap.slice(i + 1).join('\n') + src;
316
+ i = l - 1;
317
+ }
318
+ }
319
+
320
+ // Determine whether item is loose or not.
321
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
322
+ // for discount behavior.
323
+ loose = next || /\n\n(?!\s*$)/.test(item);
324
+ if (i !== l - 1) {
325
+ next = item.charAt(item.length - 1) === '\n';
326
+ if (!loose) loose = next;
327
+ }
328
+
329
+ this.tokens.push({
330
+ type: loose
331
+ ? 'loose_item_start'
332
+ : 'list_item_start'
333
+ });
334
+
335
+ // Recurse.
336
+ this.token(item, false);
337
+
338
+ this.tokens.push({
339
+ type: 'list_item_end'
340
+ });
341
+ }
342
+
343
+ this.tokens.push({
344
+ type: 'list_end'
345
+ });
346
+
347
+ continue;
348
+ }
349
+
350
+ // html
351
+ if (cap = this.rules.html.exec(src)) {
352
+ src = src.substring(cap[0].length);
353
+ this.tokens.push({
354
+ type: this.options.sanitize
355
+ ? 'paragraph'
356
+ : 'html',
357
+ pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
358
+ text: cap[0]
359
+ });
360
+ continue;
361
+ }
362
+
363
+ // def
364
+ if (top && (cap = this.rules.def.exec(src))) {
365
+ src = src.substring(cap[0].length);
366
+ this.tokens.links[cap[1].toLowerCase()] = {
367
+ href: cap[2],
368
+ title: cap[3]
369
+ };
370
+ continue;
371
+ }
372
+
373
+ // table (gfm)
374
+ if (top && (cap = this.rules.table.exec(src))) {
375
+ src = src.substring(cap[0].length);
376
+
377
+ item = {
378
+ type: 'table',
379
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
380
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
381
+ cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
382
+ };
383
+
384
+ for (i = 0; i < item.align.length; i++) {
385
+ if (/^ *-+: *$/.test(item.align[i])) {
386
+ item.align[i] = 'right';
387
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
388
+ item.align[i] = 'center';
389
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
390
+ item.align[i] = 'left';
391
+ } else {
392
+ item.align[i] = null;
393
+ }
394
+ }
395
+
396
+ for (i = 0; i < item.cells.length; i++) {
397
+ item.cells[i] = item.cells[i]
398
+ .replace(/^ *\| *| *\| *$/g, '')
399
+ .split(/ *\| */);
400
+ }
401
+
402
+ this.tokens.push(item);
403
+
404
+ continue;
405
+ }
406
+
407
+ // top-level paragraph
408
+ if (top && (cap = this.rules.paragraph.exec(src))) {
409
+ src = src.substring(cap[0].length);
410
+ this.tokens.push({
411
+ type: 'paragraph',
412
+ text: cap[1].charAt(cap[1].length - 1) === '\n'
413
+ ? cap[1].slice(0, -1)
414
+ : cap[1]
415
+ });
416
+ continue;
417
+ }
418
+
419
+ // text
420
+ if (cap = this.rules.text.exec(src)) {
421
+ // Top-level should never reach here.
422
+ src = src.substring(cap[0].length);
423
+ this.tokens.push({
424
+ type: 'text',
425
+ text: cap[0]
426
+ });
427
+ continue;
428
+ }
429
+
430
+ if (src) {
431
+ throw new
432
+ Error('Infinite loop on byte: ' + src.charCodeAt(0));
433
+ }
434
+ }
435
+
436
+ return this.tokens;
437
+ };
438
+
439
+ /**
440
+ * Inline-Level Grammar
441
+ */
442
+
443
+ var inline = {
444
+ escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
445
+ autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
446
+ url: noop,
447
+ tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
448
+ link: /^!?\[(inside)\]\(href\)/,
449
+ reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
450
+ nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
451
+ strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
452
+ em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
453
+ code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
454
+ br: /^ {2,}\n(?!\s*$)/,
455
+ del: noop,
456
+ text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
457
+ };
458
+
459
+ inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
460
+ inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
461
+
462
+ inline.link = replace(inline.link)
463
+ ('inside', inline._inside)
464
+ ('href', inline._href)
465
+ ();
466
+
467
+ inline.reflink = replace(inline.reflink)
468
+ ('inside', inline._inside)
469
+ ();
470
+
471
+ /**
472
+ * Normal Inline Grammar
473
+ */
474
+
475
+ inline.normal = merge({}, inline);
476
+
477
+ /**
478
+ * Pedantic Inline Grammar
479
+ */
480
+
481
+ inline.pedantic = merge({}, inline.normal, {
482
+ strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
483
+ em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
484
+ });
485
+
486
+ /**
487
+ * GFM Inline Grammar
488
+ */
489
+
490
+ inline.gfm = merge({}, inline.normal, {
491
+ escape: replace(inline.escape)('])', '~|])')(),
492
+ url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
493
+ del: /^~~(?=\S)([\s\S]*?\S)~~/,
494
+ text: replace(inline.text)
495
+ (']|', '~]|')
496
+ ('|', '|https?://|')
497
+ ()
498
+ });
499
+
500
+ /**
501
+ * GFM + Line Breaks Inline Grammar
502
+ */
503
+
504
+ inline.breaks = merge({}, inline.gfm, {
505
+ br: replace(inline.br)('{2,}', '*')(),
506
+ text: replace(inline.gfm.text)('{2,}', '*')()
507
+ });
508
+
509
+ /**
510
+ * Inline Lexer & Compiler
511
+ */
512
+
513
+ function InlineLexer(links, options) {
514
+ this.options = options || marked.defaults;
515
+ this.links = links;
516
+ this.rules = inline.normal;
517
+
518
+ if (!this.links) {
519
+ throw new
520
+ Error('Tokens array requires a `links` property.');
521
+ }
522
+
523
+ if (this.options.gfm) {
524
+ if (this.options.breaks) {
525
+ this.rules = inline.breaks;
526
+ } else {
527
+ this.rules = inline.gfm;
528
+ }
529
+ } else if (this.options.pedantic) {
530
+ this.rules = inline.pedantic;
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Expose Inline Rules
536
+ */
537
+
538
+ InlineLexer.rules = inline;
539
+
540
+ /**
541
+ * Static Lexing/Compiling Method
542
+ */
543
+
544
+ InlineLexer.output = function(src, links, options) {
545
+ var inline = new InlineLexer(links, options);
546
+ return inline.output(src);
547
+ };
548
+
549
+ /**
550
+ * Lexing/Compiling
551
+ */
552
+
553
+ InlineLexer.prototype.output = function(src) {
554
+ var out = ''
555
+ , link
556
+ , text
557
+ , href
558
+ , cap;
559
+
560
+ while (src) {
561
+ // escape
562
+ if (cap = this.rules.escape.exec(src)) {
563
+ src = src.substring(cap[0].length);
564
+ out += cap[1];
565
+ continue;
566
+ }
567
+
568
+ // autolink
569
+ if (cap = this.rules.autolink.exec(src)) {
570
+ src = src.substring(cap[0].length);
571
+ if (cap[2] === '@') {
572
+ text = cap[1].charAt(6) === ':'
573
+ ? this.mangle(cap[1].substring(7))
574
+ : this.mangle(cap[1]);
575
+ href = this.mangle('mailto:') + text;
576
+ } else {
577
+ text = escape(cap[1]);
578
+ href = text;
579
+ }
580
+ out += '<a href="'
581
+ + href
582
+ + '">'
583
+ + text
584
+ + '</a>';
585
+ continue;
586
+ }
587
+
588
+ // url (gfm)
589
+ if (cap = this.rules.url.exec(src)) {
590
+ src = src.substring(cap[0].length);
591
+ text = escape(cap[1]);
592
+ href = text;
593
+ out += '<a href="'
594
+ + href
595
+ + '">'
596
+ + text
597
+ + '</a>';
598
+ continue;
599
+ }
600
+
601
+ // tag
602
+ if (cap = this.rules.tag.exec(src)) {
603
+ src = src.substring(cap[0].length);
604
+ out += this.options.sanitize
605
+ ? escape(cap[0])
606
+ : cap[0];
607
+ continue;
608
+ }
609
+
610
+ // link
611
+ if (cap = this.rules.link.exec(src)) {
612
+ src = src.substring(cap[0].length);
613
+ out += this.outputLink(cap, {
614
+ href: cap[2],
615
+ title: cap[3]
616
+ });
617
+ continue;
618
+ }
619
+
620
+ // reflink, nolink
621
+ if ((cap = this.rules.reflink.exec(src))
622
+ || (cap = this.rules.nolink.exec(src))) {
623
+ src = src.substring(cap[0].length);
624
+ link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
625
+ link = this.links[link.toLowerCase()];
626
+ if (!link || !link.href) {
627
+ out += cap[0].charAt(0);
628
+ src = cap[0].substring(1) + src;
629
+ continue;
630
+ }
631
+ out += this.outputLink(cap, link);
632
+ continue;
633
+ }
634
+
635
+ // strong
636
+ if (cap = this.rules.strong.exec(src)) {
637
+ src = src.substring(cap[0].length);
638
+ out += '<strong>'
639
+ + this.output(cap[2] || cap[1])
640
+ + '</strong>';
641
+ continue;
642
+ }
643
+
644
+ // em
645
+ if (cap = this.rules.em.exec(src)) {
646
+ src = src.substring(cap[0].length);
647
+ out += '<em>'
648
+ + this.output(cap[2] || cap[1])
649
+ + '</em>';
650
+ continue;
651
+ }
652
+
653
+ // code
654
+ if (cap = this.rules.code.exec(src)) {
655
+ src = src.substring(cap[0].length);
656
+ out += '<code>'
657
+ + escape(cap[2], true)
658
+ + '</code>';
659
+ continue;
660
+ }
661
+
662
+ // br
663
+ if (cap = this.rules.br.exec(src)) {
664
+ src = src.substring(cap[0].length);
665
+ out += '<br>';
666
+ continue;
667
+ }
668
+
669
+ // del (gfm)
670
+ if (cap = this.rules.del.exec(src)) {
671
+ src = src.substring(cap[0].length);
672
+ out += '<del>'
673
+ + this.output(cap[1])
674
+ + '</del>';
675
+ continue;
676
+ }
677
+
678
+ // text
679
+ if (cap = this.rules.text.exec(src)) {
680
+ src = src.substring(cap[0].length);
681
+ out += escape(this.smartypants(cap[0]));
682
+ continue;
683
+ }
684
+
685
+ if (src) {
686
+ throw new
687
+ Error('Infinite loop on byte: ' + src.charCodeAt(0));
688
+ }
689
+ }
690
+
691
+ return out;
692
+ };
693
+
694
+ /**
695
+ * Compile Link
696
+ */
697
+
698
+ InlineLexer.prototype.outputLink = function(cap, link) {
699
+ if (cap[0].charAt(0) !== '!') {
700
+ return '<a href="'
701
+ + escape(link.href)
702
+ + '"'
703
+ + (link.title
704
+ ? ' title="'
705
+ + escape(link.title)
706
+ + '"'
707
+ : '')
708
+ + '>'
709
+ + this.output(cap[1])
710
+ + '</a>';
711
+ } else {
712
+ return '<img src="'
713
+ + escape(link.href)
714
+ + '" alt="'
715
+ + escape(cap[1])
716
+ + '"'
717
+ + (link.title
718
+ ? ' title="'
719
+ + escape(link.title)
720
+ + '"'
721
+ : '')
722
+ + '>';
723
+ }
724
+ };
725
+
726
+ /**
727
+ * Smartypants Transformations
728
+ */
729
+
730
+ InlineLexer.prototype.smartypants = function(text) {
731
+ if (!this.options.smartypants) return text;
732
+ return text
733
+ // em-dashes
734
+ .replace(/--/g, '\u2014')
735
+ // opening singles
736
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
737
+ // closing singles & apostrophes
738
+ .replace(/'/g, '\u2019')
739
+ // opening doubles
740
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
741
+ // closing doubles
742
+ .replace(/"/g, '\u201d')
743
+ // ellipses
744
+ .replace(/\.{3}/g, '\u2026');
745
+ };
746
+
747
+ /**
748
+ * Mangle Links
749
+ */
750
+
751
+ InlineLexer.prototype.mangle = function(text) {
752
+ var out = ''
753
+ , l = text.length
754
+ , i = 0
755
+ , ch;
756
+
757
+ for (; i < l; i++) {
758
+ ch = text.charCodeAt(i);
759
+ if (Math.random() > 0.5) {
760
+ ch = 'x' + ch.toString(16);
761
+ }
762
+ out += '&#' + ch + ';';
763
+ }
764
+
765
+ return out;
766
+ };
767
+
768
+ /**
769
+ * Parsing & Compiling
770
+ */
771
+
772
+ function Parser(options) {
773
+ this.tokens = [];
774
+ this.token = null;
775
+ this.options = options || marked.defaults;
776
+ }
777
+
778
+ /**
779
+ * Static Parse Method
780
+ */
781
+
782
+ Parser.parse = function(src, options) {
783
+ var parser = new Parser(options);
784
+ return parser.parse(src);
785
+ };
786
+
787
+ /**
788
+ * Parse Loop
789
+ */
790
+
791
+ Parser.prototype.parse = function(src) {
792
+ this.inline = new InlineLexer(src.links, this.options);
793
+ this.tokens = src.reverse();
794
+
795
+ var out = '';
796
+ while (this.next()) {
797
+ out += this.tok();
798
+ }
799
+
800
+ return out;
801
+ };
802
+
803
+ /**
804
+ * Next Token
805
+ */
806
+
807
+ Parser.prototype.next = function() {
808
+ return this.token = this.tokens.pop();
809
+ };
810
+
811
+ /**
812
+ * Preview Next Token
813
+ */
814
+
815
+ Parser.prototype.peek = function() {
816
+ return this.tokens[this.tokens.length - 1] || 0;
817
+ };
818
+
819
+ /**
820
+ * Parse Text Tokens
821
+ */
822
+
823
+ Parser.prototype.parseText = function() {
824
+ var body = this.token.text;
825
+
826
+ while (this.peek().type === 'text') {
827
+ body += '\n' + this.next().text;
828
+ }
829
+
830
+ return this.inline.output(body);
831
+ };
832
+
833
+ /**
834
+ * Parse Current Token
835
+ */
836
+
837
+ Parser.prototype.tok = function() {
838
+ switch (this.token.type) {
839
+ case 'space': {
840
+ return '';
841
+ }
842
+ case 'hr': {
843
+ return '<hr>\n';
844
+ }
845
+ case 'heading': {
846
+ return '<h'
847
+ + this.token.depth
848
+ + ' id="'
849
+ + this.token.text.toLowerCase().replace(/[^\w]+/g, '-')
850
+ + '">'
851
+ + this.inline.output(this.token.text)
852
+ + '</h'
853
+ + this.token.depth
854
+ + '>\n';
855
+ }
856
+ case 'code': {
857
+ if (this.options.highlight) {
858
+ var code = this.options.highlight(this.token.text, this.token.lang);
859
+ if (code != null && code !== this.token.text) {
860
+ this.token.escaped = true;
861
+ this.token.text = code;
862
+ }
863
+ }
864
+
865
+ if (!this.token.escaped) {
866
+ this.token.text = escape(this.token.text, true);
867
+ }
868
+
869
+ return '<pre><code'
870
+ + (this.token.lang
871
+ ? ' class="'
872
+ + this.options.langPrefix
873
+ + this.token.lang
874
+ + '"'
875
+ : '')
876
+ + '>'
877
+ + this.token.text
878
+ + '</code></pre>\n';
879
+ }
880
+ case 'table': {
881
+ var body = ''
882
+ , heading
883
+ , i
884
+ , row
885
+ , cell
886
+ , j;
887
+
888
+ // header
889
+ body += '<thead>\n<tr>\n';
890
+ for (i = 0; i < this.token.header.length; i++) {
891
+ heading = this.inline.output(this.token.header[i]);
892
+ body += '<th';
893
+ if (this.token.align[i]) {
894
+ body += ' style="text-align:' + this.token.align[i] + '"';
895
+ }
896
+ body += '>' + heading + '</th>\n';
897
+ }
898
+ body += '</tr>\n</thead>\n';
899
+
900
+ // body
901
+ body += '<tbody>\n'
902
+ for (i = 0; i < this.token.cells.length; i++) {
903
+ row = this.token.cells[i];
904
+ body += '<tr>\n';
905
+ for (j = 0; j < row.length; j++) {
906
+ cell = this.inline.output(row[j]);
907
+ body += '<td';
908
+ if (this.token.align[j]) {
909
+ body += ' style="text-align:' + this.token.align[j] + '"';
910
+ }
911
+ body += '>' + cell + '</td>\n';
912
+ }
913
+ body += '</tr>\n';
914
+ }
915
+ body += '</tbody>\n';
916
+
917
+ return '<table>\n'
918
+ + body
919
+ + '</table>\n';
920
+ }
921
+ case 'blockquote_start': {
922
+ var body = '';
923
+
924
+ while (this.next().type !== 'blockquote_end') {
925
+ body += this.tok();
926
+ }
927
+
928
+ return '<blockquote>\n'
929
+ + body
930
+ + '</blockquote>\n';
931
+ }
932
+ case 'list_start': {
933
+ var type = this.token.ordered ? 'ol' : 'ul'
934
+ , body = '';
935
+
936
+ while (this.next().type !== 'list_end') {
937
+ body += this.tok();
938
+ }
939
+
940
+ return '<'
941
+ + type
942
+ + '>\n'
943
+ + body
944
+ + '</'
945
+ + type
946
+ + '>\n';
947
+ }
948
+ case 'list_item_start': {
949
+ var body = '';
950
+
951
+ while (this.next().type !== 'list_item_end') {
952
+ body += this.token.type === 'text'
953
+ ? this.parseText()
954
+ : this.tok();
955
+ }
956
+
957
+ return '<li>'
958
+ + body
959
+ + '</li>\n';
960
+ }
961
+ case 'loose_item_start': {
962
+ var body = '';
963
+
964
+ while (this.next().type !== 'list_item_end') {
965
+ body += this.tok();
966
+ }
967
+
968
+ return '<li>'
969
+ + body
970
+ + '</li>\n';
971
+ }
972
+ case 'html': {
973
+ return !this.token.pre && !this.options.pedantic
974
+ ? this.inline.output(this.token.text)
975
+ : this.token.text;
976
+ }
977
+ case 'paragraph': {
978
+ return '<p>'
979
+ + this.inline.output(this.token.text)
980
+ + '</p>\n';
981
+ }
982
+ case 'text': {
983
+ return '<p>'
984
+ + this.parseText()
985
+ + '</p>\n';
986
+ }
987
+ }
988
+ };
989
+
990
+ /**
991
+ * Helpers
992
+ */
993
+
994
+ function escape(html, encode) {
995
+ return html
996
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
997
+ .replace(/</g, '&lt;')
998
+ .replace(/>/g, '&gt;')
999
+ .replace(/"/g, '&quot;')
1000
+ .replace(/'/g, '&#39;');
1001
+ }
1002
+
1003
+ function replace(regex, opt) {
1004
+ regex = regex.source;
1005
+ opt = opt || '';
1006
+ return function self(name, val) {
1007
+ if (!name) return new RegExp(regex, opt);
1008
+ val = val.source || val;
1009
+ val = val.replace(/(^|[^\[])\^/g, '$1');
1010
+ regex = regex.replace(name, val);
1011
+ return self;
1012
+ };
1013
+ }
1014
+
1015
+ function noop() {}
1016
+ noop.exec = noop;
1017
+
1018
+ function merge(obj) {
1019
+ var i = 1
1020
+ , target
1021
+ , key;
1022
+
1023
+ for (; i < arguments.length; i++) {
1024
+ target = arguments[i];
1025
+ for (key in target) {
1026
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
1027
+ obj[key] = target[key];
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ return obj;
1033
+ }
1034
+
1035
+ /**
1036
+ * Marked
1037
+ */
1038
+
1039
+ function marked(src, opt, callback) {
1040
+ if (callback || typeof opt === 'function') {
1041
+ if (!callback) {
1042
+ callback = opt;
1043
+ opt = null;
1044
+ }
1045
+
1046
+ opt = merge({}, marked.defaults, opt || {});
1047
+
1048
+ var highlight = opt.highlight
1049
+ , tokens
1050
+ , pending
1051
+ , i = 0;
1052
+
1053
+ try {
1054
+ tokens = Lexer.lex(src, opt)
1055
+ } catch (e) {
1056
+ return callback(e);
1057
+ }
1058
+
1059
+ pending = tokens.length;
1060
+
1061
+ var done = function() {
1062
+ var out, err;
1063
+
1064
+ try {
1065
+ out = Parser.parse(tokens, opt);
1066
+ } catch (e) {
1067
+ err = e;
1068
+ }
1069
+
1070
+ opt.highlight = highlight;
1071
+
1072
+ return err
1073
+ ? callback(err)
1074
+ : callback(null, out);
1075
+ };
1076
+
1077
+ if (!highlight || highlight.length < 3) {
1078
+ return done();
1079
+ }
1080
+
1081
+ delete opt.highlight;
1082
+
1083
+ if (!pending) return done();
1084
+
1085
+ for (; i < tokens.length; i++) {
1086
+ (function(token) {
1087
+ if (token.type !== 'code') {
1088
+ return --pending || done();
1089
+ }
1090
+ return highlight(token.text, token.lang, function(err, code) {
1091
+ if (code == null || code === token.text) {
1092
+ return --pending || done();
1093
+ }
1094
+ token.text = code;
1095
+ token.escaped = true;
1096
+ --pending || done();
1097
+ });
1098
+ })(tokens[i]);
1099
+ }
1100
+
1101
+ return;
1102
+ }
1103
+ try {
1104
+ if (opt) opt = merge({}, marked.defaults, opt);
1105
+ return Parser.parse(Lexer.lex(src, opt), opt);
1106
+ } catch (e) {
1107
+ e.message += '\nPlease report this to https://github.com/chjj/marked.';
1108
+ if ((opt || marked.defaults).silent) {
1109
+ return '<p>An error occured:</p><pre>'
1110
+ + escape(e.message + '', true)
1111
+ + '</pre>';
1112
+ }
1113
+ throw e;
1114
+ }
1115
+ }
1116
+
1117
+ /**
1118
+ * Options
1119
+ */
1120
+
1121
+ marked.options =
1122
+ marked.setOptions = function(opt) {
1123
+ merge(marked.defaults, opt);
1124
+ return marked;
1125
+ };
1126
+
1127
+ marked.defaults = {
1128
+ gfm: true,
1129
+ tables: true,
1130
+ breaks: false,
1131
+ pedantic: false,
1132
+ sanitize: false,
1133
+ smartLists: false,
1134
+ silent: false,
1135
+ highlight: null,
1136
+ langPrefix: 'lang-',
1137
+ smartypants: false
1138
+ };
1139
+
1140
+ /**
1141
+ * Expose
1142
+ */
1143
+
1144
+ marked.Parser = Parser;
1145
+ marked.parser = Parser.parse;
1146
+
1147
+ marked.Lexer = Lexer;
1148
+ marked.lexer = Lexer.lex;
1149
+
1150
+ marked.InlineLexer = InlineLexer;
1151
+ marked.inlineLexer = InlineLexer.output;
1152
+
1153
+ marked.parse = marked;
1154
+
1155
+ if (typeof exports === 'object') {
1156
+ module.exports = marked;
1157
+ } else if (typeof define === 'function' && define.amd) {
1158
+ define(function() { return marked; });
1159
+ } else {
1160
+ this.marked = marked;
1161
+ }
1162
+
1163
+ }).call(function() {
1164
+ return this || (typeof window !== 'undefined' ? window : global);
1165
+ }());
js/markdown/adminhtml/reMarked.js ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) 2013, Leon Sorokin
3
+ * All rights reserved. (MIT Licensed)
4
+ *
5
+ * reMarked.js - DOM > markdown
6
+ */
7
+
8
+ reMarked = function(opts) {
9
+
10
+ var links = [];
11
+ var cfg = {
12
+ link_list: false, // render links as references, create link list as appendix
13
+ // link_near: // cite links immediately after blocks
14
+ h1_setext: true, // underline h1 headers
15
+ h2_setext: true, // underline h2 headers
16
+ h_atx_suf: false, // header suffix (###)
17
+ // h_compact: true, // compact headers (except h1)
18
+ gfm_code: false, // render code blocks as via ``` delims
19
+ li_bullet: "*-+"[0], // list item bullet style
20
+ // list_indnt: // indent top-level lists
21
+ hr_char: "-_*"[0], // hr style
22
+ indnt_str: [" ","\t"," "][0], // indentation string
23
+ bold_char: "*_"[0], // char used for strong
24
+ emph_char: "*_"[1], // char used for em
25
+ gfm_del: true, // ~~strikeout~~ for <del>strikeout</del>
26
+ gfm_tbls: true, // markdown-extra tables
27
+ tbl_edges: false, // show side edges on tables
28
+ hash_lnks: false, // anchors w/hash hrefs as links
29
+ br_only: false, // avoid using " " as line break indicator
30
+ // comp_style: false, // use getComputedStyle instead of hardcoded tag list to discern block/inline
31
+ unsup_tags: { // handling of unsupported tags, defined in terms of desired output style. if not listed, output = outerHTML
32
+ // no output
33
+ ignore: "script style noscript",
34
+ // eg: "<tag>some content</tag>"
35
+ inline: "span sup sub i u b center big",
36
+ // eg: "\n<tag>\n\tsome content\n</tag>"
37
+ // block1: "",
38
+ // eg: "\n\n<tag>\n\tsome content\n</tag>"
39
+ block2: "div form fieldset dl header footer address article aside figure hgroup section",
40
+ // eg: "\n<tag>some content</tag>"
41
+ block1c: "dt dd caption legend figcaption output",
42
+ // eg: "\n\n<tag>some content</tag>"
43
+ block2c: "canvas audio video iframe",
44
+ /* // direct remap of unsuported tags
45
+ convert: {
46
+ i: "em",
47
+ b: "strong"
48
+ }
49
+ */
50
+ }
51
+ };
52
+
53
+ extend(cfg, opts);
54
+
55
+ function extend(a, b) {
56
+ if (!b) return a;
57
+ for (var i in a) {
58
+ if (typeOf(b[i]) == "Object")
59
+ extend(a[i], b[i]);
60
+ else if (typeof b[i] !== "undefined")
61
+ a[i] = b[i];
62
+ }
63
+ }
64
+
65
+ function typeOf(val) {
66
+ return Object.prototype.toString.call(val).slice(8,-1);
67
+ }
68
+
69
+ function rep(str, num) {
70
+ var s = "";
71
+ while (num-- > 0)
72
+ s += str;
73
+ return s;
74
+ }
75
+
76
+ function trim12(str) {
77
+ var str = str.replace(/^\s\s*/, ''),
78
+ ws = /\s/,
79
+ i = str.length;
80
+ while (ws.test(str.charAt(--i)));
81
+ return str.slice(0, i + 1);
82
+ }
83
+
84
+ function lpad(targ, padStr, len) {
85
+ return rep(padStr, len - targ.length) + targ;
86
+ }
87
+
88
+ function rpad(targ, padStr, len) {
89
+ return targ + rep(padStr, len - targ.length);
90
+ }
91
+
92
+ function otag(tag, e) {
93
+ if (!tag) return "";
94
+
95
+ var buf = "<" + tag;
96
+
97
+ for (var attr, i=0, attrs=e.attributes, l=attrs.length; i<l; i++) {
98
+ attr = attrs.item(i);
99
+ buf += " " + attr.nodeName + '="' + attr.nodeValue + '"';
100
+ }
101
+
102
+ return buf + ">";
103
+ }
104
+
105
+ function ctag(tag) {
106
+ if (!tag) return "";
107
+ return "</" + tag + ">";
108
+ }
109
+
110
+ function pfxLines(txt, pfx) {
111
+ return txt.replace(/^/gm, pfx);
112
+ }
113
+
114
+ function nodeName(e) {
115
+ return (e.nodeName == "#text" ? "txt" : e.nodeName).toLowerCase();
116
+ }
117
+
118
+ function wrap(str, opts) {
119
+ var pre, suf;
120
+
121
+ if (opts instanceof Array) {
122
+ pre = opts[0];
123
+ suf = opts[1];
124
+ }
125
+ else
126
+ pre = suf = opts;
127
+
128
+ pre = pre instanceof Function ? pre.call(this, str) : pre;
129
+ suf = suf instanceof Function ? suf.call(this, str) : suf;
130
+
131
+ return pre + str + suf;
132
+ }
133
+
134
+ // http://stackoverflow.com/a/3819589/973988
135
+ function outerHTML(node) {
136
+ // if IE, Chrome take the internal method otherwise build one
137
+ return node.outerHTML || (
138
+ function(n){
139
+ var div = document.createElement('div'), h;
140
+ div.appendChild( n.cloneNode(true) );
141
+ h = div.innerHTML;
142
+ div = null;
143
+ return h;
144
+ })(node);
145
+ }
146
+
147
+ this.render = function(ctr) {
148
+ links = [];
149
+
150
+ if (typeof ctr == "string") {
151
+ var htmlstr = ctr;
152
+ ctr = document.createElement("div");
153
+ ctr.innerHTML = htmlstr;
154
+ }
155
+ var s = new lib.tag(ctr, null, 0);
156
+ var re = s.rend().replace(/^[\t ]+\n/gm, "\n");
157
+ if (cfg.link_list && links.length > 0) {
158
+ // hack
159
+ re += "\n\n";
160
+ var maxlen = 0;
161
+ // get longest link href with title, TODO: use getAttribute?
162
+ for (var y in links) {
163
+ if (!links[y].e.title) continue;
164
+ var len = links[y].e.href.length;
165
+ if (len && len > maxlen)
166
+ maxlen = len;
167
+ }
168
+
169
+ for (var k in links) {
170
+ var title = links[k].e.title ? rep(" ", (maxlen + 2) - links[k].e.href.length) + '"' + links[k].e.title + '"' : "";
171
+ re += " [" + (+k+1) + "]: " + (nodeName(links[k].e) == "a" ? links[k].e.href : links[k].e.src) + title + "\n";
172
+ }
173
+ }
174
+
175
+ return re.replace(/^[\t ]+\n/gm, "\n");
176
+ };
177
+
178
+ var lib = {};
179
+
180
+ lib.tag = klass({
181
+ wrap: "",
182
+ lnPfx: "", // only block
183
+ lnInd: 0, // only block
184
+ init: function(e, p, i)
185
+ {
186
+ this.e = e;
187
+ this.p = p;
188
+ this.i = i;
189
+ this.c = [];
190
+ this.tag = nodeName(e);
191
+
192
+ this.initK();
193
+ },
194
+
195
+ initK: function()
196
+ {
197
+ var i;
198
+ if (this.e.hasChildNodes()) {
199
+ // inline elems allowing adjacent whitespace text nodes to be rendered
200
+ var inlRe = cfg.unsup_tags.inline, n, name;
201
+
202
+ for (i in this.e.childNodes) {
203
+ if (!/\d+/.test(i)) continue;
204
+
205
+ n = this.e.childNodes[i];
206
+ name = nodeName(n);
207
+
208
+ // ignored tags
209
+ if (cfg.unsup_tags.ignore.test(name))
210
+ continue;
211
+
212
+ // empty whitespace handling
213
+ if (name == "txt" && /^\s+$/.test(n.textContent)) {
214
+ // ignore if first or last child (trim)
215
+ if (i == 0 || i == this.e.childNodes.length - 1)
216
+ continue;
217
+
218
+ // only ouput when has an adjacent inline elem
219
+ var prev = this.e.childNodes[i-1],
220
+ next = this.e.childNodes[i+1];
221
+ if (prev && !nodeName(prev).match(inlRe) || next && !nodeName(next).match(inlRe))
222
+ continue;
223
+ }
224
+
225
+ var wrap = null;
226
+
227
+ if (!lib[name]) {
228
+ var unsup = cfg.unsup_tags;
229
+
230
+ if (unsup.inline.test(name))
231
+ name = "tinl";
232
+ else if (unsup.block2.test(name))
233
+ name = "tblk";
234
+ else if (unsup.block1c.test(name))
235
+ name = "ctblk";
236
+ else if (unsup.block2c.test(name)) {
237
+ name = "ctblk";
238
+ wrap = ["\n\n", ""];
239
+ }
240
+ else
241
+ name = "rawhtml";
242
+ }
243
+
244
+ var node = new lib[name](n, this, this.c.length);
245
+
246
+ if (wrap)
247
+ node.wrap = wrap;
248
+
249
+ if (node instanceof lib.a && n.href || node instanceof lib.img) {
250
+ node.lnkid = links.length;
251
+ links.push(node);
252
+ }
253
+
254
+ this.c.push(node);
255
+ }
256
+ }
257
+ },
258
+
259
+ rend: function()
260
+ {
261
+ return this.rendK().replace(/\n{3,}/gm, "\n\n"); // can screw up pre and code :(
262
+ },
263
+
264
+ rendK: function()
265
+ {
266
+ var n, buf = "";
267
+ for (var i in this.c) {
268
+ n = this.c[i];
269
+
270
+ // @SchumacherFM: added typeof n.rend
271
+ buf += (n.bef || "") +( typeof n.rend === 'function' ? n.rend() : '' ) + (n.aft || "");
272
+ }
273
+ return buf.replace(/^\n+|\n+$/, "");
274
+ }
275
+ });
276
+
277
+ lib.blk = lib.tag.extend({
278
+ wrap: ["\n\n", ""],
279
+ wrapK: null,
280
+ tagr: false,
281
+ lnInd: null,
282
+ init: function(e, p ,i) {
283
+ this.supr(e,p,i);
284
+
285
+ // kids indented
286
+ if (this.lnInd === null) {
287
+ if (this.p && this.tagr && this.c[0] instanceof lib.blk)
288
+ this.lnInd = 4;
289
+ else
290
+ this.lnInd = 0;
291
+ }
292
+
293
+ // kids wrapped?
294
+ if (this.wrapK === null) {
295
+ if (this.tagr && this.c[0] instanceof lib.blk)
296
+ this.wrapK = "\n";
297
+ else
298
+ this.wrapK = "";
299
+ }
300
+ },
301
+
302
+ rend: function()
303
+ {
304
+ return wrap.call(this, (this.tagr ? otag(this.tag, this.e) : "") + wrap.call(this, pfxLines(pfxLines(this.rendK(), this.lnPfx), rep(" ", this.lnInd)), this.wrapK) + (this.tagr ? ctag(this.tag) : ""), this.wrap);
305
+ },
306
+
307
+ rendK: function()
308
+ {
309
+ var kids = this.supr();
310
+ // remove min uniform leading spaces from block children. marked.js's list outdent algo sometimes leaves these
311
+ if (this.p instanceof lib.li) {
312
+ var repl = null, spcs = kids.match(/^[\t ]+/gm);
313
+ if (!spcs) return kids;
314
+ for (var i in spcs) {
315
+ if (repl === null || spcs[i][0].length < repl.length)
316
+ repl = spcs[i][0];
317
+ }
318
+ return kids.replace(new RegExp("^" + repl), "");
319
+ }
320
+ return kids;
321
+ }
322
+ });
323
+
324
+ lib.tblk = lib.blk.extend({tagr: true});
325
+
326
+ lib.cblk = lib.blk.extend({wrap: ["\n", ""]});
327
+
328
+ lib.ctblk = lib.cblk.extend({tagr: true});
329
+
330
+ lib.inl = lib.tag.extend({
331
+ rend: function()
332
+ {
333
+ return wrap.call(this, this.rendK(), this.wrap);
334
+ }
335
+ });
336
+
337
+ lib.tinl = lib.inl.extend({
338
+ tagr: true,
339
+ rend: function()
340
+ {
341
+ return otag(this.tag, this.e) + wrap.call(this, this.rendK(), this.wrap) + ctag(this.tag);
342
+ }
343
+ });
344
+
345
+ lib.p = lib.blk.extend({
346
+ rendK: function() {
347
+ return this.supr().replace(/^\s+/gm, "");
348
+ }
349
+ });
350
+
351
+ lib.list = lib.blk.extend({
352
+ expn: false,
353
+ wrap: [function(){return this.p instanceof lib.li ? "\n" : "\n\n";}, ""]
354
+ });
355
+
356
+ lib.ul = lib.list.extend({});
357
+
358
+ lib.ol = lib.list.extend({});
359
+
360
+ lib.li = lib.cblk.extend({
361
+ wrap: ["\n", function(kids) {
362
+ return this.p.expn || kids.match(/\n{2}/gm) ? "\n" : ""; // || this.kids.match(\n)
363
+ }],
364
+ wrapK: [function() {
365
+ return this.p.tag == "ul" ? cfg.li_bullet + " " : (this.i + 1) + ". ";
366
+ }, ""],
367
+ rendK: function() {
368
+ return this.supr().replace(/\n([^\n])/gm, "\n" + cfg.indnt_str + "$1");
369
+ }
370
+ });
371
+
372
+ lib.hr = lib.blk.extend({
373
+ wrap: ["\n\n", rep(cfg.hr_char, 3)]
374
+ });
375
+
376
+ lib.h = lib.blk.extend({});
377
+
378
+ lib.h_setext = lib.h.extend({});
379
+
380
+ cfg.h1_setext && (lib.h1 = lib.h_setext.extend({
381
+ wrapK: ["", function(kids) {
382
+ return "\n" + rep("=", kids.length);
383
+ }]
384
+ }));
385
+
386
+ cfg.h2_setext && (lib.h2 = lib.h_setext.extend({
387
+ wrapK: ["", function(kids) {
388
+ return "\n" + rep("-", kids.length);
389
+ }]
390
+ }));
391
+
392
+ lib.h_atx = lib.h.extend({
393
+ wrapK: [
394
+ function(kids) {
395
+ return rep("#", this.tag[1]) + " ";
396
+ },
397
+ function(kids) {
398
+ return cfg.h_atx_suf ? " " + rep("#", this.tag[1]) : "";
399
+ }
400
+ ]
401
+ });
402
+ !cfg.h1_setext && (lib.h1 = lib.h_atx.extend({}));
403
+
404
+ !cfg.h2_setext && (lib.h2 = lib.h_atx.extend({}));
405
+
406
+ lib.h3 = lib.h_atx.extend({});
407
+
408
+ lib.h4 = lib.h_atx.extend({});
409
+
410
+ lib.h5 = lib.h_atx.extend({});
411
+
412
+ lib.h6 = lib.h_atx.extend({});
413
+
414
+ lib.a = lib.inl.extend({
415
+ lnkid: null,
416
+ rend: function() {
417
+ var kids = this.rendK(),
418
+ href = this.e.getAttribute("href"),
419
+ title = this.e.title ? ' "' + this.e.title + '"' : "";
420
+
421
+ if (!href || href == kids || href[0] == "#" && !cfg.hash_lnks)
422
+ return kids;
423
+
424
+ if (cfg.link_list)
425
+ return "[" + kids + "] [" + (this.lnkid + 1) + "]";
426
+
427
+ return "[" + kids + "](" + href + title + ")";
428
+ }
429
+ });
430
+
431
+ // almost identical to links, maybe merge
432
+ lib.img = lib.inl.extend({
433
+ lnkid: null,
434
+ rend: function() {
435
+ var kids = this.e.alt,
436
+ src = this.e.getAttribute("src");
437
+
438
+ if (cfg.link_list)
439
+ return "![" + kids + "] [" + (this.lnkid + 1) + "]";
440
+
441
+ var title = this.e.title ? ' "'+ this.e.title + '"' : "";
442
+
443
+ return "![" + kids + "](" + src + title + ")";
444
+ }
445
+ });
446
+
447
+
448
+ lib.em = lib.inl.extend({wrap: cfg.emph_char});
449
+
450
+ lib.del = cfg.gfm_del ? lib.inl.extend({wrap: "~~"}) : lib.tinl.extend();
451
+
452
+ lib.br = lib.inl.extend({
453
+ wrap: ["", function() {
454
+ var end = cfg.br_only ? "<br>" : " ";
455
+ // br in headers output as html
456
+ return this.p instanceof lib.h ? "<br>" : end + "\n";
457
+ }]
458
+ });
459
+
460
+ lib.strong = lib.inl.extend({wrap: rep(cfg.bold_char, 2)});
461
+
462
+ lib.blockquote = lib.blk.extend({
463
+ lnPfx: "> ",
464
+ rend: function() {
465
+ return this.supr().replace(/>[ \t]$/gm, ">");
466
+ }
467
+ });
468
+
469
+ // can render with or without tags
470
+ lib.pre = lib.blk.extend({
471
+ tagr: true,
472
+ wrapK: "\n",
473
+ lnInd: 0
474
+ });
475
+
476
+ // can morph into inline based on context
477
+ lib.code = lib.blk.extend({
478
+ tagr: false,
479
+ wrap: "",
480
+ wrapK: function(kids) {
481
+ return kids.indexOf("`") !== -1 ? "``" : "`"; // esc double backticks
482
+ },
483
+ lnInd: 0,
484
+ init: function(e, p, i) {
485
+ this.supr(e, p, i);
486
+
487
+ if (this.p instanceof lib.pre) {
488
+ this.p.tagr = false;
489
+
490
+ if (cfg.gfm_code) {
491
+ var cls = this.e.getAttribute("class");
492
+ cls = (cls || "").split(" ")[0];
493
+
494
+ if (cls.indexOf("lang-") === 0) // marked uses "lang-" prefix now
495
+ cls = cls.substr(5);
496
+
497
+ this.wrapK = ["```" + cls + "\n", "\n```"];
498
+ }
499
+ else {
500
+ this.wrapK = "";
501
+ this.p.lnInd = 4;
502
+ }
503
+ }
504
+ }
505
+ });
506
+
507
+ lib.table = cfg.gfm_tbls ? lib.blk.extend({
508
+ cols: [],
509
+ init: function(e, p, i) {
510
+ this.supr(e, p, i);
511
+ this.cols = [];
512
+ },
513
+ rend: function() {
514
+ // run prep on all cells to get max col widths
515
+ for (var tsec in this.c)
516
+ for (var row in this.c[tsec].c)
517
+ for (var cell in this.c[tsec].c[row].c)
518
+ this.c[tsec].c[row].c[cell].prep();
519
+
520
+ return this.supr();
521
+ }
522
+ }) : lib.tblk.extend();
523
+
524
+ lib.thead = cfg.gfm_tbls ? lib.cblk.extend({
525
+ wrap: ["\n", function(kids) {
526
+ var buf = "";
527
+ for (var i in this.p.cols) {
528
+ var col = this.p.cols[i],
529
+ al = col.a[0] == "c" ? ":" : " ",
530
+ ar = col.a[0] == "r" || col.a[0] == "c" ? ":" : " ";
531
+
532
+ buf += (i == 0 && cfg.tbl_edges ? "|" : "") + al + rep("-", col.w) + ar + (i < this.p.cols.length-1 || cfg.tbl_edges ? "|" : "");
533
+ }
534
+ return "\n" + trim12(buf);
535
+ }]
536
+ }) : lib.ctblk.extend();
537
+
538
+ lib.tbody = cfg.gfm_tbls ? lib.cblk.extend() : lib.ctblk.extend();
539
+
540
+ lib.tfoot = cfg.gfm_tbls ? lib.cblk.extend() : lib.ctblk.extend();
541
+
542
+ lib.tr = cfg.gfm_tbls ? lib.cblk.extend({
543
+ wrapK: [cfg.tbl_edges ? "| " : "", cfg.tbl_edges ? " |" : ""],
544
+ }) : lib.ctblk.extend();
545
+
546
+ lib.th = cfg.gfm_tbls ? lib.inl.extend({
547
+ guts: null,
548
+ // TODO: DRY?
549
+ wrap: [function() {
550
+ var col = this.p.p.p.cols[this.i],
551
+ spc = this.i == 0 ? "" : " ",
552
+ pad, fill = col.w - this.guts.length;
553
+
554
+ switch (col.a[0]) {
555
+ case "r": pad = rep(" ", fill); break;
556
+ case "c": pad = rep(" ", Math.floor(fill/2)); break;
557
+ default: pad = "";
558
+ }
559
+
560
+ return spc + pad;
561
+ }, function() {
562
+ var col = this.p.p.p.cols[this.i],
563
+ edg = this.i == this.p.c.length - 1 ? "" : " |",
564
+ pad, fill = col.w - this.guts.length;
565
+
566
+ switch (col.a[0]) {
567
+ case "r": pad = ""; break;
568
+ case "c": pad = rep(" ", Math.ceil(fill/2)); break;
569
+ default: pad = rep(" ", fill);
570
+ }
571
+
572
+ return pad + edg;
573
+ }],
574
+ prep: function() {
575
+ this.guts = this.rendK(); // pre-render
576
+ this.rendK = function() {return this.guts};
577
+
578
+ var cols = this.p.p.p.cols;
579
+ if (!cols[this.i])
580
+ cols[this.i] = {w: null, a: ""}; // width and alignment
581
+ var col = cols[this.i];
582
+ col.w = Math.max(col.w || 0, this.guts.length);
583
+ if (this.e.align)
584
+ col.a = this.e.align;
585
+ }
586
+ }) : lib.ctblk.extend();
587
+
588
+ lib.td = lib.th.extend();
589
+
590
+ lib.txt = lib.inl.extend({
591
+ initK: function()
592
+ {
593
+ this.c = this.e.textContent.split(/^/gm);
594
+ },
595
+ rendK: function()
596
+ {
597
+ var kids = this.c.join("").replace(/\r/gm, "");
598
+
599
+ // this is strange, cause inside of code, inline should not be processed, but is?
600
+ if (!(this.p instanceof lib.code || this.p instanceof lib.pre)) {
601
+ kids = kids
602
+ .replace(/^\s*#/gm,"\\#")
603
+ .replace(/\*/gm,"\\*");
604
+ }
605
+
606
+ if (this.i == 0)
607
+ kids = kids.replace(/^\n+/, "");
608
+ if (this.i == this.p.c.length - 1)
609
+ kids = kids.replace(/\n+$/, "");
610
+
611
+ return kids;
612
+ }
613
+ });
614
+
615
+ lib.rawhtml = lib.blk.extend({
616
+ initK: function()
617
+ {
618
+ this.guts = outerHTML(this.e);
619
+ },
620
+ rendK: function()
621
+ {
622
+ return this.guts;
623
+ }
624
+ });
625
+
626
+ // compile regexes
627
+ for (var i in cfg.unsup_tags)
628
+ cfg.unsup_tags[i] = new RegExp("^(?:" + (i == "inline" ? "a|em|strong|img|code|del|" : "") + cfg.unsup_tags[i].replace(/\s/g, "|") + ")$");
629
+ };
630
+
631
+ /*!
632
+ * klass: a classical JS OOP façade
633
+ * https://github.com/ded/klass
634
+ * License MIT (c) Dustin Diaz & Jacob Thornton 2012
635
+ */
636
+ !function(a,b){typeof define=="function"?define(b):typeof module!="undefined"?module.exports=b():this[a]=b()}("klass",function(){function f(a){return j.call(g(a)?a:function(){},a,1)}function g(a){return typeof a===c}function h(a,b,c){return function(){var d=this.supr;this.supr=c[e][a];var f=b.apply(this,arguments);return this.supr=d,f}}function i(a,b,c){for(var f in b)b.hasOwnProperty(f)&&(a[f]=g(b[f])&&g(c[e][f])&&d.test(b[f])?h(f,b[f],c):b[f])}function j(a,b){function c(){}function l(){this.init?this.init.apply(this,arguments):(b||h&&d.apply(this,arguments),j.apply(this,arguments))}c[e]=this[e];var d=this,f=new c,h=g(a),j=h?a:this,k=h?{}:a;return l.methods=function(a){return i(f,a,d),l[e]=f,this},l.methods.call(l,k).prototype.constructor=l,l.extend=arguments.callee,l[e].implement=l.statics=function(a,b){return a=typeof a=="string"?function(){var c={};return c[a]=b,c}():a,i(this,a,d),this},l}var a=this,b=a.klass,c="function",d=/xyz/.test(function(){xyz})?/\bsupr\b/:/.*/,e="prototype";return f.noConflict=function(){return a.klass=b,this},a.klass=f,f});
package.xml CHANGED
@@ -1,30 +1,33 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>markdown</name>
4
- <version>1.4.2</version>
5
  <stability>stable</stability>
6
- <license uri="https://github.com/SchumacherFM/Magento-Markdown">Custom</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Mage Markdown as module for Magento! Replaces the TinyMCE editor. Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).</summary>
10
- <description>Mage Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).&#xD;
 
 
11
  &#xD;
12
- Full documentation of Markdown's syntax is available on John's Markdown page: http://daringfireball.net/projects/markdown/&#xD;
13
- &#xD;
14
- Full support of Markdown Extra: http://michelf.ca/projects/php-markdown/extra/&#xD;
15
- &#xD;
16
- This module renders all CMS pages and CMS blocks.&#xD;
17
- &#xD;
18
- Rendering of catalog description fields have to be implemented in the phtml files by yourself.&#xD;
19
- &#xD;
20
- Preview in the backend. Live preview available in CMS Pages.&#xD;
21
  &#xD;
22
  For further information please see the github repository: https://github.com/SchumacherFM/Magento-Markdown</description>
23
- <notes>https://github.com/SchumacherFM/Magento-Markdown</notes>
 
 
24
  <authors><author><name>Cyrill Schumacher</name><user>cyrills</user><email>cyrill@schumacher.fm</email></author></authors>
25
- <date>2013-09-11</date>
26
- <time>09:03:12</time>
27
- <contents><target name="magecommunity"><dir name="SchumacherFM"><dir name="Markdown"><dir><dir name="Helper"><file name="Data.php" hash="35789036d57008a39effa53ed597d737"/></dir><dir name="Model"><dir name="Editor"><file name="Config.php" hash="44c6244ec440e8150778079cd184e5de"/><file name="Observer.php" hash="ad29b486cbe198b89e04db266d10c16b"/></dir><dir name="Markdown"><file name="Abstract.php" hash="496f33e550c20301a8a4c62c5cbf35a2"/><file name="Interface.php" hash="05df0ecf7a7c6bc09481f213cdc98255"/><file name="Observer.php" hash="e357583f380bcc925cc663d44f6120da"/><file name="Render.php" hash="df0addc5cfbd8d805c0b8a1661bbd496"/></dir><dir name="Michelf"><dir name="Markdown"><file name="Extra.php" hash="aaee7a66298007f8c003ba96d53deab9"/><file name="TmpImpl.php" hash="5ec68c28519d8ecb4a43324f5f197a80"/></dir><file name="Markdown.php" hash="76219ba32f37cad14755c8613b390c83"/></dir><dir name="Observer"><file name="AdminhtmlBlock.php" hash="50f7c3f848f847ef7b35206f7f48914f"/></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="MarkdownController.php" hash="9a7e8a64624159af49a18ad135d721ae"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="1f4034751ea0e0eece21d0015ee95e1a"/><file name="config.xml" hash="e398afbbb64dc97de74baf605ebc64fc"/><file name="system.xml" hash="0cee296bcf061251c7eab51dfefe224f"/></dir><dir name="sql"><dir name="markdown_setup"><file name="install-1.0.0.php" hash="0fe5a9650dd224f691caf585a34c52e6"/></dir></dir></dir><file name=".DS_Store" hash="29236c1e2932a8a54035bfbe9e734f49"/></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="markdown.xml" hash="ceece632ae2ebda2c33f7a7a37f9d7d2"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="SchumacherFM_Markdown.xml" hash="db8742448ef30a5af1bd60865f429c26"/></dir></target><target name="magelocale"><dir><dir name="de_DE"><file name="SchumacherFM_Markdown.csv" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir><dir name="en_US"><file name="SchumacherFM_Markdown.csv" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir></dir></target><target name="mage"><dir name="js"><dir name="mage"><dir name="adminhtml"><file name="markdown.js" hash="62a8ef8105db7defd76fa9f8bca6bfce"/><file name="marked.js" hash="5dbb480da6528c11cd99f3d17cc6aad5"/></dir></dir></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><file name="markdown.css" hash="68699f62ea8666484819346ff143ee88"/></dir></dir></dir></target></contents>
28
  <compatible/>
29
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
30
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>markdown</name>
4
+ <version>2.0.0</version>
5
  <stability>stable</stability>
6
+ <license uri="https://github.com/SchumacherFM/Magento-Markdown">BSD-3-Clause</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Mage Markdown as module for Magento! Replaces the TinyMCE editor. Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).</summary>
10
+ <description>Markdown is a text-to-HTML conversion tool for web writers. Markdown&#xD;
11
+ allows you to write using an easy-to-read, easy-to-write plain text&#xD;
12
+ format, then convert it to structurally valid XHTML (or HTML).&#xD;
13
  &#xD;
14
+ - Full support of Markdown Extra&#xD;
15
+ - Renders all CMS pages and all CMS blocks (Mage_Cms_Block_Block and Mage_Cms_Block_Widget_Block)&#xD;
16
+ - Renders every transactional email as Markdown (or MD Extra)&#xD;
17
+ - Rendering of catalog product and category short and long description fields have to be implemented in the phtml files by yourself.&#xD;
18
+ - Integrates the awesome http://epiceditor.com: split fullscreen editing, live previewing, automatic draft saving and offline support.&#xD;
19
+ - Drag'n'Drop of images supported in textarea fields. Automatic image uploading integrated (&gt;=&#xD;
20
+ IE10, Safari 6.0.2, FX3.6, Chrome 7, Opera 12.02)&#xD;
21
+ - Converting of HTML into Markdown. Client side via JavaScript.&#xD;
 
22
  &#xD;
23
  For further information please see the github repository: https://github.com/SchumacherFM/Magento-Markdown</description>
24
+ <notes>https://github.com/SchumacherFM/Magento-Markdown&#xD;
25
+ &#xD;
26
+ Tons of new features! See the github pages for the version history.</notes>
27
  <authors><author><name>Cyrill Schumacher</name><user>cyrills</user><email>cyrill@schumacher.fm</email></author></authors>
28
+ <date>2013-10-13</date>
29
+ <time>07:21:10</time>
30
+ <contents><target name="magecommunity"><dir name="SchumacherFM"><dir name="Markdown"><dir><dir name="Helper"><file name="Data.php" hash="56a3abd176deb0f2318bd4c6be98f950"/></dir><dir name="Model"><dir name="Editor"><file name="Observer.php" hash="0683e4ed2e97c9efbabea6aad92c1f44"/></dir><dir name="Markdown"><file name="Abstract.php" hash="237784e5f749406fca86e88172cab2ea"/><file name="Interface.php" hash="05df0ecf7a7c6bc09481f213cdc98255"/><file name="Observer.php" hash="2d8d754cf465bb3a85cd826a11074d0a"/><file name="Render.php" hash="df0addc5cfbd8d805c0b8a1661bbd496"/></dir><dir name="Michelf"><dir name="Markdown"><file name="Extra.php" hash="aaee7a66298007f8c003ba96d53deab9"/><file name="TmpImpl.php" hash="5ec68c28519d8ecb4a43324f5f197a80"/></dir><file name="Markdown.php" hash="76219ba32f37cad14755c8613b390c83"/></dir><dir name="Observer"><dir name="Adminhtml"><file name="Block.php" hash="efe852cb712631d2bdeb56ed4be02a48"/><file name="EpicEditor.php" hash="ac01b829d5ad29c2c5412ace1448b194"/><file name="LayoutUpdate.php" hash="a01b599ebed057f9a99266cb91441a99"/></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="MarkdownController.php" hash="d8180e18071016883615d8f727a3300e"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="27c358363c32632f51affbecae3a24a0"/><file name="config.xml" hash="5e48b57a0fd5cf6b1665c4696ccadf22"/><file name="system.xml" hash="d31cf185d664c7f3424214ebc5239aaf"/></dir><dir name="sql"><dir name="markdown_setup"><file name="install-1.0.0.php" hash="0fe5a9650dd224f691caf585a34c52e6"/></dir></dir></dir><file name=".DS_Store" hash="29236c1e2932a8a54035bfbe9e734f49"/></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="markdown.xml" hash="5f073545b849100ed7c1f1ba1685fb4b"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="SchumacherFM_Markdown.xml" hash="db8742448ef30a5af1bd60865f429c26"/></dir></target><target name="magelocale"><dir><dir name="de_DE"><file name="SchumacherFM_Markdown.csv" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir><dir name="en_US"><file name="SchumacherFM_Markdown.csv" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir></dir></target><target name="mage"><dir name="js"><dir name="markdown"><dir><dir name="adminhtml"><file name="epiceditor.js" hash="5c29b9d4d25d897ba63e9109ba6e3564"/><file name="filereader.js" hash="3ed749a9b0843d0911b41f8be26364fa"/><dir name="highlight"><dir name="styles"><file name="xcode.css" hash="d99d394b8a22d59c8546f84b683dc648"/></dir></dir><file name="highlight.pack.js" hash="b60b28a732e22d75e690bc32d8606354"/><file name="markdown.js" hash="ac58ac234a377e3b3a4e2cab828268d7"/><file name="marked.js" hash="8af08dcb5d207265f604dce4c250e6ec"/><file name="reMarked.js" hash="9adbec6c65fb488a754103e63b94ad58"/></dir></dir></dir></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="markdown"><file name="mdm.css" hash="19b49b67b9b52b25f5f7d2106cc73892"/></dir><dir name="epiceditor"><dir><dir name="highlight"><dir name="styles"><file name="default.css" hash="05519f4875102535137ef0141f26864b"/><file name="github.css" hash="ca6ea80f87a205fdf919439bad4c27db"/><file name="googlecode.css" hash="a4c13deaf6eeb2858cf7b0b300992cab"/></dir></dir><dir name="themes"><dir name="base"><file name="epiceditor.css" hash="72d9ff0a1296bf28b815d696129bb086"/></dir><dir name="editor"><file name="epic-dark.css" hash="872c9540cedbf5cdf69ca2b0accb3cef"/><file name="epic-light.css" hash="68d9441f297c73180dd83545ee70117a"/></dir><dir name="preview"><file name="bartik.css" hash="a8a3efffd02c3acf6e3dc62ee5e92936"/><file name="github.css" hash="6085b7303f9a3c3cc3d2d8039487bf5d"/><file name="githubNxcode.css" hash="c5639fe4cd88f0416c15892f2f9b90b2"/><file name="preview-dark.css" hash="660b85886b844d592e2c722937b438e1"/></dir></dir></dir></dir></dir></dir></dir></target></contents>
31
  <compatible/>
32
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
33
  </package>
skin/adminhtml/default/default/epiceditor/highlight/styles/default.css ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+
3
+ Original style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>
4
+
5
+ */
6
+
7
+ pre code {
8
+ display: block; padding: 0.5em;
9
+ background: #F0F0F0;
10
+ }
11
+
12
+ pre code,
13
+ pre .subst,
14
+ pre .tag .title,
15
+ pre .lisp .title,
16
+ pre .clojure .built_in,
17
+ pre .nginx .title {
18
+ color: black;
19
+ }
20
+
21
+ pre .string,
22
+ pre .title,
23
+ pre .constant,
24
+ pre .parent,
25
+ pre .tag .value,
26
+ pre .rules .value,
27
+ pre .rules .value .number,
28
+ pre .preprocessor,
29
+ pre .ruby .symbol,
30
+ pre .ruby .symbol .string,
31
+ pre .aggregate,
32
+ pre .template_tag,
33
+ pre .django .variable,
34
+ pre .smalltalk .class,
35
+ pre .addition,
36
+ pre .flow,
37
+ pre .stream,
38
+ pre .bash .variable,
39
+ pre .apache .tag,
40
+ pre .apache .cbracket,
41
+ pre .tex .command,
42
+ pre .tex .special,
43
+ pre .erlang_repl .function_or_atom,
44
+ pre .markdown .header {
45
+ color: #800;
46
+ }
47
+
48
+ pre .comment,
49
+ pre .annotation,
50
+ pre .template_comment,
51
+ pre .diff .header,
52
+ pre .chunk,
53
+ pre .markdown .blockquote {
54
+ color: #888;
55
+ }
56
+
57
+ pre .number,
58
+ pre .date,
59
+ pre .regexp,
60
+ pre .literal,
61
+ pre .smalltalk .symbol,
62
+ pre .smalltalk .char,
63
+ pre .go .constant,
64
+ pre .change,
65
+ pre .markdown .bullet,
66
+ pre .markdown .link_url {
67
+ color: #080;
68
+ }
69
+
70
+ pre .label,
71
+ pre .javadoc,
72
+ pre .ruby .string,
73
+ pre .decorator,
74
+ pre .filter .argument,
75
+ pre .localvars,
76
+ pre .array,
77
+ pre .attr_selector,
78
+ pre .important,
79
+ pre .pseudo,
80
+ pre .pi,
81
+ pre .doctype,
82
+ pre .deletion,
83
+ pre .envvar,
84
+ pre .shebang,
85
+ pre .apache .sqbracket,
86
+ pre .nginx .built_in,
87
+ pre .tex .formula,
88
+ pre .erlang_repl .reserved,
89
+ pre .prompt,
90
+ pre .markdown .link_label,
91
+ pre .vhdl .attribute,
92
+ pre .clojure .attribute,
93
+ pre .coffeescript .property {
94
+ color: #88F
95
+ }
96
+
97
+ pre .keyword,
98
+ pre .id,
99
+ pre .phpdoc,
100
+ pre .title,
101
+ pre .built_in,
102
+ pre .aggregate,
103
+ pre .css .tag,
104
+ pre .javadoctag,
105
+ pre .phpdoc,
106
+ pre .yardoctag,
107
+ pre .smalltalk .class,
108
+ pre .winutils,
109
+ pre .bash .variable,
110
+ pre .apache .tag,
111
+ pre .go .typename,
112
+ pre .tex .command,
113
+ pre .markdown .strong,
114
+ pre .request,
115
+ pre .status {
116
+ font-weight: bold;
117
+ }
118
+
119
+ pre .markdown .emphasis {
120
+ font-style: italic;
121
+ }
122
+
123
+ pre .nginx .built_in {
124
+ font-weight: normal;
125
+ }
126
+
127
+ pre .coffeescript .javascript,
128
+ pre .javascript .xml,
129
+ pre .tex .formula,
130
+ pre .xml .javascript,
131
+ pre .xml .vbscript,
132
+ pre .xml .css,
133
+ pre .xml .cdata {
134
+ opacity: 0.5;
135
+ }
skin/adminhtml/default/default/epiceditor/highlight/styles/github.css ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+
3
+ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
4
+
5
+ */
6
+
7
+ pre code {
8
+ display: block; padding: 0.5em;
9
+ color: #333;
10
+ background: #f8f8ff
11
+ }
12
+
13
+ pre .comment,
14
+ pre .template_comment,
15
+ pre .diff .header,
16
+ pre .javadoc {
17
+ color: #998;
18
+ font-style: italic
19
+ }
20
+
21
+ pre .keyword,
22
+ pre .css .rule .keyword,
23
+ pre .winutils,
24
+ pre .javascript .title,
25
+ pre .nginx .title,
26
+ pre .subst,
27
+ pre .request,
28
+ pre .status {
29
+ color: #333;
30
+ font-weight: bold
31
+ }
32
+
33
+ pre .number,
34
+ pre .hexcolor,
35
+ pre .ruby .constant {
36
+ color: #099;
37
+ }
38
+
39
+ pre .string,
40
+ pre .tag .value,
41
+ pre .phpdoc,
42
+ pre .tex .formula {
43
+ color: #d14
44
+ }
45
+
46
+ pre .title,
47
+ pre .id {
48
+ color: #900;
49
+ font-weight: bold
50
+ }
51
+
52
+ pre .javascript .title,
53
+ pre .lisp .title,
54
+ pre .clojure .title,
55
+ pre .subst {
56
+ font-weight: normal
57
+ }
58
+
59
+ pre .class .title,
60
+ pre .haskell .type,
61
+ pre .vhdl .literal,
62
+ pre .tex .command {
63
+ color: #458;
64
+ font-weight: bold
65
+ }
66
+
67
+ pre .tag,
68
+ pre .tag .title,
69
+ pre .rules .property,
70
+ pre .django .tag .keyword {
71
+ color: #000080;
72
+ font-weight: normal
73
+ }
74
+
75
+ pre .attribute,
76
+ pre .variable,
77
+ pre .lisp .body {
78
+ color: #008080
79
+ }
80
+
81
+ pre .regexp {
82
+ color: #009926
83
+ }
84
+
85
+ pre .class {
86
+ color: #458;
87
+ font-weight: bold
88
+ }
89
+
90
+ pre .symbol,
91
+ pre .ruby .symbol .string,
92
+ pre .lisp .keyword,
93
+ pre .tex .special,
94
+ pre .prompt {
95
+ color: #990073
96
+ }
97
+
98
+ pre .built_in,
99
+ pre .lisp .title,
100
+ pre .clojure .built_in {
101
+ color: #0086b3
102
+ }
103
+
104
+ pre .preprocessor,
105
+ pre .pi,
106
+ pre .doctype,
107
+ pre .shebang,
108
+ pre .cdata {
109
+ color: #999;
110
+ font-weight: bold
111
+ }
112
+
113
+ pre .deletion {
114
+ background: #fdd
115
+ }
116
+
117
+ pre .addition {
118
+ background: #dfd
119
+ }
120
+
121
+ pre .diff .change {
122
+ background: #0086b3
123
+ }
124
+
125
+ pre .chunk {
126
+ color: #aaa
127
+ }
skin/adminhtml/default/default/epiceditor/highlight/styles/googlecode.css ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+
3
+ Google Code style (c) Aahan Krish <geekpanth3r@gmail.com>
4
+
5
+ */
6
+
7
+ pre code {
8
+ display: block; padding: 0.5em;
9
+ background: white; color: black;
10
+ }
11
+
12
+ pre .comment,
13
+ pre .template_comment,
14
+ pre .javadoc,
15
+ pre .comment * {
16
+ color: #800;
17
+ }
18
+
19
+ pre .keyword,
20
+ pre .method,
21
+ pre .list .title,
22
+ pre .clojure .built_in,
23
+ pre .nginx .title,
24
+ pre .tag .title,
25
+ pre .setting .value,
26
+ pre .winutils,
27
+ pre .tex .command,
28
+ pre .http .title,
29
+ pre .request,
30
+ pre .status {
31
+ color: #008;
32
+ }
33
+
34
+ pre .envvar,
35
+ pre .tex .special {
36
+ color: #660;
37
+ }
38
+
39
+ pre .string,
40
+ pre .tag .value,
41
+ pre .cdata,
42
+ pre .filter .argument,
43
+ pre .attr_selector,
44
+ pre .apache .cbracket,
45
+ pre .date,
46
+ pre .regexp {
47
+ color: #080;
48
+ }
49
+
50
+ pre .sub .identifier,
51
+ pre .pi,
52
+ pre .tag,
53
+ pre .tag .keyword,
54
+ pre .decorator,
55
+ pre .ini .title,
56
+ pre .shebang,
57
+ pre .prompt,
58
+ pre .hexcolor,
59
+ pre .rules .value,
60
+ pre .css .value .number,
61
+ pre .literal,
62
+ pre .symbol,
63
+ pre .ruby .symbol .string,
64
+ pre .number,
65
+ pre .css .function,
66
+ pre .clojure .attribute {
67
+ color: #066;
68
+ }
69
+
70
+ pre .class .title,
71
+ pre .haskell .type,
72
+ pre .smalltalk .class,
73
+ pre .javadoctag,
74
+ pre .yardoctag,
75
+ pre .phpdoc,
76
+ pre .typename,
77
+ pre .tag .attribute,
78
+ pre .doctype,
79
+ pre .class .id,
80
+ pre .built_in,
81
+ pre .setting,
82
+ pre .params,
83
+ pre .variable,
84
+ pre .clojure .title {
85
+ color: #606;
86
+ }
87
+
88
+ pre .css .tag,
89
+ pre .rules .property,
90
+ pre .pseudo,
91
+ pre .subst {
92
+ color: #000;
93
+ }
94
+
95
+ pre .css .class, pre .css .id {
96
+ color: #9B703F;
97
+ }
98
+
99
+ pre .value .important {
100
+ color: #ff7700;
101
+ font-weight: bold;
102
+ }
103
+
104
+ pre .rules .keyword {
105
+ color: #C5AF75;
106
+ }
107
+
108
+ pre .annotation,
109
+ pre .apache .sqbracket,
110
+ pre .nginx .built_in {
111
+ color: #9B859D;
112
+ }
113
+
114
+ pre .preprocessor,
115
+ pre .preprocessor * {
116
+ color: #444;
117
+ }
118
+
119
+ pre .tex .formula {
120
+ background-color: #EEE;
121
+ font-style: italic;
122
+ }
123
+
124
+ pre .diff .header,
125
+ pre .chunk {
126
+ color: #808080;
127
+ font-weight: bold;
128
+ }
129
+
130
+ pre .diff .change {
131
+ background-color: #BCCFF9;
132
+ }
133
+
134
+ pre .addition {
135
+ background-color: #BAEEBA;
136
+ }
137
+
138
+ pre .deletion {
139
+ background-color: #FFC8BD;
140
+ }
141
+
142
+ pre .comment .yardoctag {
143
+ font-weight: bold;
144
+ }
skin/adminhtml/default/default/epiceditor/themes/base/epiceditor.css ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html, body, iframe, div {
2
+ margin : 0;
3
+ padding : 0;
4
+ }
5
+
6
+ #epiceditor-utilbar {
7
+ position : fixed;
8
+ bottom : 10px;
9
+ right : 10px;
10
+ }
11
+
12
+ #epiceditor-utilbar button {
13
+ display : block;
14
+ float : left;
15
+ width : 30px;
16
+ height : 30px;
17
+ border : none;
18
+ background : none;
19
+ }
20
+
21
+ #epiceditor-utilbar button.epiceditor-toggle-preview-btn {
22
+ background-image : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAf9JREFUeNrsVu1twjAQLZmAEcIGYYKGDWACkgkSkPiQ+CifAoEgyQTQDegEpROQDZoRMgH0HToj1yIglKr90Vg62T6f/e4d9gu50+n09BctlwFnwP8TOAgCA10VRr2ZELaHhbBXx3HCVMC+71voXmD6g4Qi2NB13e1DwAAkZhtmmKYRcxsJhHeBPc8bMMufbDU0PxF4vV4TSyth8477csI6lTV/a71er9tioonBarXaIAmLErliNWyqoM8nrJPpHFNLWLcI4xvj5XKpMo2ZgcvzIvs+75S0wKwPPB/CnpWXsG00Gra2WCwshekOVoC9Sb6IGN1ge2HNsWK+B0iJqxAL5oSpYeDJJW02mxVYLAWSGfDtebylA68Bc4wh+ahK5PcxLh6PR5GUpym/iTOfz89PqNVqhRI4iQf1/o174HNMVYDSGeTDmXQ3znogCGrtdpsYVBhER1aH2Wzm8iE7UR74DMTWGNxUQWmNYqTEzq8APoo9sJ8wKoR5eU7T6VQVjZAvx4YvDJWt1Ol0QsTqkppF8EW8/12OhTnSpT2LCe2/KiCTyUQVkJgPuwgb6XG32w05Xui4q0imLLNDxA/uSuZ4PNaZqZlSsejDYfd6veihj8RoNDK5XOUHAen3Dfr9/j7VZxEJ6AwuxCCvhMTM7oNAARhl/0Ay4Az419qXAAMAfBdK7281j6YAAAAASUVORK5CYII=);
23
+ }
24
+
25
+ #epiceditor-utilbar button.epiceditor-toggle-edit-btn {
26
+ background-image : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNrsV+lt4lAQNtEWAB2QCtZ0ABWsqYB1BU5AgDgkQOISl9cV4FQQtgLYCuKtIN4K4gogM9H3pMmLTTh24c+O9An7+XnOb8aP1G63M64hN8aV5GqGvxyyyfO8Y3S6hDtc2wTfcZyLRPwdvxEbPSviIwjIRtO49s9ONSRLMIGvwkBI+EPYEEqyQmcZdl2Xo3BgcJ90xPUGDh1veLFYWCBK9oQ6P4BgiXVO6fUjg5zCJcE6kVxsLEN4QTlWzO5yuRwlGp7P55zOxz1RRqK2SfKD8BvOG4IHxUqlEnxop9lsZpITa0KWndGwIeQIXswzHbynpK2xzjXbeBfxdDrlhbUWjYyuiJS9fBJxgL3PItKsprNQrVaD1GQyYUVP2oYelDziPoPnT5+k2QajleEC3usI/exM7oYiXor0hpxS8qhLv5FIVVrcBwkp5ueclbS27qNMvkj7kg3nZX1qtVqAaSUN5OGUwn2M4RUb3263plh700E6I7wTKn1suCAW3PF4zL1r1Ov1SBhXZLEJFqGTQCq5N1BZIp3s+DOi5fXcG7lGo1Ea5DIFSWzcq7a4R6uYqJmlkSqHoeGKeq+w905MtGKj0Yje9TE5ID9pqictQQwmXTfh82cIJ0NML0d0QY8MdhMn13A4zENB0hAJEYn6EnGL3MZ0hsyG3Ww2g70jU8lgMOhqHicJz+KfovVkz3J5/FardfhBgDZzS90SeoJ8cXjQJlUAEmZUCx30kYiTfr9vgFRc72+ChCHSzNH+Qgk+fA7b7fZZJ5AAiIRhT4zUv3/Y07LiaPW9yPE2L5jrI/p/d7wVEZe0U8bJkvr/F+ZS8irAAIorRozUvI0gAAAAAElFTkSuQmCC);
27
+ }
28
+
29
+ #epiceditor-utilbar button.epiceditor-fullscreen-btn {
30
+ background-image : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAY5JREFUeNrsV8tthDAQhYgCtoV04BKghK2En5AQBw4IITjwq2RLgBJcwpawHZAxMpGDYGwTFPaQkUbG1sybj58NmNM0GVfIh3GRXBbYEid932N9d1zXHWWAGAb4m+9VsUA0H5SubKkKIGA4qyUC2qKBxSCe541HKln7dV2nVbHRtq0Nw7CXGNtz/jzwqjZ5odtqTOagQRC82KRpGkcS/L2Ok7lXZV3Xp7Q6DMNR2mqNthMhKXLqzcUCc6Wgn3wU1wlX1PboHs8tjaLoyVtLT7JFK2ZZM6CZvWyE+X1Voaj3la3DMA63epGqqm4wfyCBH8xmz1+Z1WVZ2vyqU2HvHtv9OI4PsRpjL91YV2a7pcB8onmOifYFUhSFLQCTnQtkDpokyYv73JBtcAQsA3zGTXJBEgNXgrF3CcrBZGwnC+4uqxHnH+zN8/ybvexZwvZNhlsHbrt5CyCgDtu1XosUe58K4kuOF9EKnKYp20eVrxDUJssyreM0bDg4kIw0EfCbftvqQ6KKYf7/wvyVfAkwAMjqoFSaWbrBAAAAAElFTkSuQmCC);
31
+ }
32
+
33
+ @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and ( min--moz-device-pixel-ratio: 2), only screen and ( -o-min-device-pixel-ratio: 2/1), only screen and ( min-device-pixel-ratio: 2), only screen and ( min-resolution: 192dpi), only screen and ( min-resolution: 2dppx) {
34
+ #epiceditor-utilbar button.epiceditor-toggle-preview-btn {
35
+ background : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAABrVBMVEWIiIiHh4eIiIiJiYmEhISEhISEhISMjIyNjY2MjIyNjY2MjIyFhYWDg4OFhYWKioqKioqBgYGJiYmJiYmJiYmKioqKioqFhYWIiIiJiYmIiIiJiYmIiIiGhoaIiIiGhoaIiIiHh4eHh4eGhoaFhYWKioqFhYWEhISFhYWEhISDg4ONjY2NjY2MjIyHh4eMjIyLi4uCgoKCgoKCgoKBgYGBgYGOjo6Ojo6BgYGPj4+Ojo6Hh4eNjY2Pj4+BgYGNjY2BgYGHh4eQkJCNjY2FhYWHh4eDg4OQkJCQkJCAgICCgoKAgICAgICQkJCQkJCQkJCAgICQkJCAgICQkJCFhYWHh4eHh4eGhoaKioqGhoaMjIyDg4OEhISDg4OEhISDg4OEhISKioqBgYGJiYmKioqKioqJiYmMjIyEhISCgoKGhoaLi4uPj4+Pj4+BgYGKioqDg4OGhoaGhoaMjIyHh4eEhISEhISDg4OIiIiJiYmGhoaHh4eJiYkAAACAgICFhYWQkJCIiIiEhISLi4uDg4OHh4eGhoaCgoKBgYGJiYmMjIyOjo6KioqNjY2Pj4+o6Lr0AAAAfnRSTlPfvxDPEL8gv5/Pr3AQ3++vn8+/cCCA34/vj8/vMJ+vgJ+vYN/fMIBAv4/v38/v39+PgBBg74DPgGAwMHDvrzBQj1AggJ8wIHC/IM9gjxBQgO+vv89Qz0C/70Bgz89ggIDvYN/fII8wIHDvMJ+/QBBAMBDPnxBgML9AEI/vQACT8cYwAAACXElEQVR42u2WV1caURRGJ73H9J7YW+y99957ARREURAFQRFEOFGMkybxN+eec2cWUwBnrTwli/1yvvnm7PvEuoMAf0FGzsj/nDwxOTUzdyoxNzM1OWFU7h0aPtUxPNRrQPZ0XKagw3OF7Nm9TMOuJ43cQmpavSWV3HRhgKakcmvjhSEaW/VyV/tvg7R3aeU+9ULZ/fLEQ/ndMvXbPrV88EvBvQdOMCsLMzjNd5TFgSTr3SpWOCuUTYWTVVV6m+Sdr0qqqVGxw6pqXUPyZlziFaU9Vi3HVSyzag+D/YlcbXLZLm+85AsOgMK4hkIABz/YkSVVdpS3fnKevQCIYgCcGqKslGd0g3dbIIR5fP8cZCkMdOANWeSLEJZklt5StxEWcmLIuw+AHGE+YiEWE67jGxnlO8xv8OGTEBGRRSACmNtYyBUjgcCCKJPLqjYMAeD04ENEGIjQ7AGikuUFNhdF8Vogj0T5bDyqEjh55AwI4N7/hmRTe4zRxEM+ZuKYFSYeEP9HzPtuEFjm9pKf9W5KQLbKhSVMbkymfHL90i+s/wR5PM9iCaYiLOcLTogCrKEIYwkLD19T25/4bR+unSG3bkOQwiG1QZfV6gryBaqL5c01XCCZ9lbOCOvNUpouUOGishSK+dpKUHMZ2M6Jz7ZHNEO+hOoLUWVZZROx6a8hn+VcRWh1EOtBUuhcPiy+pBdg3fZ3DaOj2ma7LtXVW1uj0XVqTW2aS9/bUP8jJfUN3is+N97mp8nV9Wavka9kZ/e6zuzuNP59Hhkbn53+QkzPjo+NZP6TZOSM/L/LfwAJ69Ba3fXmtwAAAABJRU5ErkJggg==);
36
+ background-size : 30px 30px;
37
+ }
38
+
39
+ #epiceditor-utilbar button.epiceditor-toggle-edit-btn {
40
+ background : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAACNFBMVEWIiIiHh4eIiIiGhoaEhISDg4OCgoKDg4OMjIyNjY2MjIyMjIyLi4uMjIyKioqCgoKNjY2Ojo6Dg4ONjY2FhYWFhYWCgoKKioqKioqLi4uLi4uMjIyLi4uFhYWKioqKioqEhISJiYmJiYmEhISEhISFhYWEhISGhoaHh4eJiYmNjY2Hh4eKioqDg4ODg4OCgoKOjo6Ojo6Dg4OOjo6BgYGOjo6AgICCgoKNjY2Dg4OPj4+BgYGBgYGAgICOjo6BgYGOjo6AgICOjo6BgYGBgYGAgICPj4+QkJCQkJCQkJCQkJCPj4+NjY2IiIiIiIiFhYWFhYWEhISEhISGhoaDg4OGhoaDg4OHh4eDg4OCgoKMjIyLi4uLi4uKioqNjY2KioqNjY2NjY2NjY2Dg4OKioqGhoaJiYmIiIiGhoaFhYWEhISFhYWFhYWOjo6Pj4+FhYWGhoaPj4+Hh4eMjIyMjIyMjIyJiYmIiIiIiIiOjo6Ojo6CgoKBgYGPj4+QkJCHh4eHh4eEhISIiIiDg4OEhISDg4ODg4OLi4uHh4eEhISMjIyFhYWGhoaHh4eOjo6IiIiKioqKioqMjIyGhoaIiIiJiYmIiIiFhYWGhoaGhoaHh4eKioqJiYmLi4uIiIiGhoaJiYmHh4eGhoaGhoaJiYmFhYWKioqPj4+IiIiGhoYAAACAgICCgoKQkJCPj4+BgYGDg4OFhYWHh4eOjo6JiYmGhoaLi4uEhISIiIiNjY2MjIyKioqRGRgVAAAAq3RSTlNQz7+Pz4AQr4/vIK+Av99QICBQzyCv37/PcM+f378QgHAwgIBgnxBQMK+PgFCf34Bwn0BQv2Ag7zBwEFDvj4CAv1DvYK/f398gUICA39+fYHAwQHAQn78gz0DfYO8wgO+/YECPIK9gz89Av8/f31AwgK9A72AwIGCAMM+PICCvrxCfr+/fYCAgYO9QgGBQjyCPYM8QcM8wEEAgn58Qn+8w7+/fv0DvQEBA7wCxres+AAAC50lEQVR42u2XVUMbQRSFU3d3d3d3d8EpUKRoKbS40+LuDsEtQOFQKA2FEvhz3XsnS5IlJLB9a/s95J49M99sXpJsNPgD/st/oZwxJJGhTn4+RLxQJ78k9xnUyU+HJV6pk6OGiXR1ciy5sZizfPMrwzl9mIiak5x6/sLnUZmH9+9eqqAQCftyakXkqFUq7MpX6JbW2WBHDnxtmJUAIMiGfD3AYINArDdsCppNfmewxQqx4aRVObFm0ibLAW+aNYkz5ZL4SdusRIkI8SVKOcFyp/cu5ftYA6ySc4Kl3DZuxs4dhfAZV+CDQtNFm0lWuLsBFPoqXF9g9bjSZrlqypwqAC1TClqAtYprIVfLjT+f0gfAXyn7oY9GNZ/KiWVumX17OYYAIUp3O+AnDu7bZqxOk5zU+ZOpPwD0UABNBaEAaPYACBVCZ5Ik14vlg5ClVjFpC0NZ6lplGa0nxN2gSZkg2vtB9FOmKDUNCyemcTStMXXcpmh4yGUl5ToAORMOaGiflhtkoRKCfq41yTw+GFsHKeeIRUfwwbwKOo/eIATi3GQNivREVxyIZsqeADL1+swuvZEiAJ4UmsEUsVEODRAnNp2iulzekrVAbyJLPrYctJTJ7nGQjI7uMULXBIBjIyQWUWLeAGik0E39sQGKYU0QMmp1vGnADSjj0EFtk5uOjuKzOtgok8r34rxasMzEjDFhjdDJNsE7u2VXh9oYDgNllp/n8IgfzJbwXhq9zlRu5soZWtFFl/Zy8SkaljK0R6inJTEinLQo5aSFE889kkqUWvsCdM37ZcnHYnrNBhably5QyoJDtFuJK1xMF3mHgVlkHM2e4eYB02Xxfts/NwXBuSMWLIG7sTmbb/+Hzj3fy1wuQD6N3DMX5/g0VHDDQ3aXAR4jXsEb5/co9fbLN2KdlJbO/zmM5a0qH+KukXxOnfzoO5GmTr5M7mOoktP4xrfUyffIvQ118pNBiTvq5AeDxNV//W+CFX4Doxg1lHC8FNUAAAAASUVORK5CYII=);
41
+ background-size : 30px 30px;
42
+ }
43
+
44
+ #epiceditor-utilbar button.epiceditor-fullscreen-btn {
45
+ background : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAA5FBMVEWPj4+JiYmJiYmCgoKGhoaPj4+Li4uEhISAgICNjY2Li4uNjY2QkJCFhYWEhISLi4uHh4eHh4eIiIiHh4eGhoaHh4eEhISOjo6EhISJiYmFhYWJiYmFhYWNjY2NjY2Hh4eOjo6CgoKGhoaKioqKioqJiYmKioqDg4ODg4OBgYGGhoaKioqKioqLi4uMjIyMjIyIiIiIiIiMjIyOjo6GhoaDg4ONjY2EhISPj4+CgoIAAACJiYmHh4eIiIiFhYWCgoKPj4+Li4uAgICKioqEhISGhoaBgYGOjo6MjIyQkJCNjY2Dg4O2Jy/OAAAAO3RSTlMQz0DPEHDPz0DvMDBAQBBAQBAQz88wMM/v7zAw788Q7xAQQM9AEBDPEEAwMO/v7zDvMEBA70BAQEBAAAENmbYAAAG1SURBVHja7dftM0JREMdxD5GkEEqKpBJRSc8rQq7Q////2Dnm3t85jtkTmmGG75t7Z3c/t9fN0Tf6x1/Dz0ZrZGZf/BJ8a9QjM/vCwks9Px5aBcslC+P3nPVmi8dcczrcHHM2flug1CHRYcoYNd0YFlrAL1yHqPOC9g9IdbAfjHAjYFjoT+FI1MfRiAM/cZdEl0+oVidVvRaMcAOMgJWGBUYSVhrWjdfvjKqrq1Vzsu7Cy1Vo7XXZhYuj0ahwfHY+sjo/Oy7woyhitkTQsESsZawstG6VlvDCfIlUmfSVVjpDqtL8goBLbKFhsRcwalxcB100CDkxLLQbJxKwpvb3At7Y2iRuJzd4V26HuM2tDQkPWMOamu1Awkeetx2qtDy/lvZaCW173pGIWetA/xBbF+ZgiVgjGcdutLJ7xO1l9VnMiWGhJdzl4vx4CNpN+ifJXUy7RPEuZ2C1AIY1NG5kHI4Dx8MynnBtovYkqHzi25OyP8ONiKFh3djQsCIecn2i/lBvMU+UXxwi3HyE832jvD0RsLuP8CN3Oh0+feRmixE+g7CdLb43WiEz++Jb+M//x/gB/ArCl0G4nsFuEQAAAABJRU5ErkJggg==);
46
+ background-size : 30px 30px;
47
+ }
48
+ }
49
+
50
+ #epiceditor-utilbar button:last-child {
51
+ margin-left : 15px;
52
+ }
53
+
54
+ #epiceditor-utilbar button:hover {
55
+ cursor : pointer;
56
+ }
57
+
58
+ .epiceditor-edit-mode #epiceditor-utilbar button.epiceditor-toggle-edit-btn {
59
+ display : none;
60
+ }
61
+
62
+ .epiceditor-preview-mode #epiceditor-utilbar button.epiceditor-toggle-preview-btn {
63
+ display : none;
64
+ }
skin/adminhtml/default/default/epiceditor/themes/editor/epic-dark.css ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ padding : 10px;
3
+ }
4
+
5
+ body {
6
+ border : 0;
7
+ background : rgb(41, 41, 41);
8
+ font-family : monospace;
9
+ font-size : 14px;
10
+ padding : 10px;
11
+ color : #ddd;
12
+ line-height : 1.35em;
13
+ margin : 0;
14
+ padding : 0;
15
+ }
skin/adminhtml/default/default/epiceditor/themes/editor/epic-light.css ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ padding : 10px;
3
+ }
4
+
5
+ body {
6
+ border : 0;
7
+ background : #fff;
8
+ font-family : monospace;
9
+ font-size : 14px;
10
+ padding : 10px;
11
+ line-height : 1.35em;
12
+ margin : 0;
13
+ padding : 0;
14
+ }
skin/adminhtml/default/default/epiceditor/themes/preview/bartik.css ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family : Georgia, "Times New Roman", Times, serif;
3
+ line-height : 1.5;
4
+ font-size : 87.5%;
5
+ word-wrap : break-word;
6
+ margin : 2em;
7
+ padding : 0;
8
+ border : 0;
9
+ outline : 0;
10
+ background : #fff;
11
+ }
12
+
13
+ h1,
14
+ h2,
15
+ h3,
16
+ h4,
17
+ h5,
18
+ h6 {
19
+ margin : 1.0em 0 0.5em;
20
+ font-weight : inherit;
21
+ }
22
+
23
+ h1 {
24
+ font-size : 1.357em;
25
+ color : #000;
26
+ }
27
+
28
+ h2 {
29
+ font-size : 1.143em;
30
+ }
31
+
32
+ p {
33
+ margin : 0 0 1.2em;
34
+ }
35
+
36
+ del {
37
+ text-decoration : line-through;
38
+ }
39
+
40
+ tr:nth-child(odd) {
41
+ background-color : #dddddd;
42
+ }
43
+
44
+ img {
45
+ outline : 0;
46
+ }
47
+
48
+ code {
49
+ background-color : #f2f2f2;
50
+ background-color : rgba(40, 40, 0, 0.06);
51
+ }
52
+
53
+ pre {
54
+ background-color : #f2f2f2;
55
+ background-color : rgba(40, 40, 0, 0.06);
56
+ margin : 10px 0;
57
+ overflow : hidden;
58
+ padding : 15px;
59
+ white-space : pre-wrap;
60
+ }
61
+
62
+ pre code {
63
+ font-size : 100%;
64
+ background-color : transparent;
65
+ }
66
+
67
+ blockquote {
68
+ background : #f7f7f7;
69
+ border-left : 1px solid #bbb;
70
+ font-style : italic;
71
+ margin : 1.5em 10px;
72
+ padding : 0.5em 10px;
73
+ }
74
+
75
+ blockquote:before {
76
+ color : #bbb;
77
+ content : "\201C";
78
+ font-size : 3em;
79
+ line-height : 0.1em;
80
+ margin-right : 0.2em;
81
+ vertical-align : -.4em;
82
+ }
83
+
84
+ blockquote:after {
85
+ color : #bbb;
86
+ content : "\201D";
87
+ font-size : 3em;
88
+ line-height : 0.1em;
89
+ vertical-align : -.45em;
90
+ }
91
+
92
+ blockquote > p:first-child {
93
+ display : inline;
94
+ }
95
+
96
+ table {
97
+ font-family : "Helvetica Neue", Helvetica, Arial, sans-serif;
98
+ border : 0;
99
+ border-spacing : 0;
100
+ font-size : 0.857em;
101
+ margin : 10px 0;
102
+ width : 100%;
103
+ }
104
+
105
+ table table {
106
+ font-size : 1em;
107
+ }
108
+
109
+ table tr th {
110
+ background : #757575;
111
+ background : rgba(0, 0, 0, 0.51);
112
+ border-bottom-style : none;
113
+ }
114
+
115
+ table tr th,
116
+ table tr th a,
117
+ table tr th a:hover {
118
+ color : #FFF;
119
+ font-weight : bold;
120
+ }
121
+
122
+ table tbody tr th {
123
+ vertical-align : top;
124
+ }
125
+
126
+ tr td,
127
+ tr th {
128
+ padding : 4px 9px;
129
+ border : 1px solid #fff;
130
+ text-align : left; /* LTR */
131
+ }
132
+
133
+ tr:nth-child(odd) {
134
+ background : #e4e4e4;
135
+ background : rgba(0, 0, 0, 0.105);
136
+ }
137
+
138
+ tr,
139
+ tr:nth-child(even) {
140
+ background : #efefef;
141
+ background : rgba(0, 0, 0, 0.063);
142
+ }
143
+
144
+ a {
145
+ color : #0071B3;
146
+ }
147
+
148
+ a:hover,
149
+ a:focus {
150
+ color : #018fe2;
151
+ }
152
+
153
+ a:active {
154
+ color : #23aeff;
155
+ }
156
+
157
+ a:link,
158
+ a:visited {
159
+ text-decoration : none;
160
+ }
161
+
162
+ a:hover,
163
+ a:active,
164
+ a:focus {
165
+ text-decoration : underline;
166
+ }
167
+
skin/adminhtml/default/default/epiceditor/themes/preview/github.css ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html { padding:0 10px; }
2
+
3
+ body {
4
+ margin:0;
5
+ padding:0;
6
+ background:#fff;
7
+ }
8
+
9
+ #epiceditor-wrapper{
10
+ background:white;
11
+ }
12
+
13
+ #epiceditor-preview{
14
+ padding-top:10px;
15
+ padding-bottom:10px;
16
+ font-family: Helvetica,arial,freesans,clean,sans-serif;
17
+ font-size:13px;
18
+ line-height:1.6;
19
+ }
20
+
21
+ #epiceditor-preview>*:first-child{
22
+ margin-top:0!important;
23
+ }
24
+
25
+ #epiceditor-preview>*:last-child{
26
+ margin-bottom:0!important;
27
+ }
28
+
29
+ #epiceditor-preview a{
30
+ color:#4183C4;
31
+ text-decoration:none;
32
+ }
33
+
34
+ #epiceditor-preview a:hover{
35
+ text-decoration:underline;
36
+ }
37
+
38
+ #epiceditor-preview h1,
39
+ #epiceditor-preview h2,
40
+ #epiceditor-preview h3,
41
+ #epiceditor-preview h4,
42
+ #epiceditor-preview h5,
43
+ #epiceditor-preview h6{
44
+ margin:20px 0 10px;
45
+ padding:0;
46
+ font-weight:bold;
47
+ -webkit-font-smoothing:antialiased;
48
+ }
49
+
50
+ #epiceditor-preview h1 tt,
51
+ #epiceditor-preview h1 code,
52
+ #epiceditor-preview h2 tt,
53
+ #epiceditor-preview h2 code,
54
+ #epiceditor-preview h3 tt,
55
+ #epiceditor-preview h3 code,
56
+ #epiceditor-preview h4 tt,
57
+ #epiceditor-preview h4 code,
58
+ #epiceditor-preview h5 tt,
59
+ #epiceditor-preview h5 code,
60
+ #epiceditor-preview h6 tt,
61
+ #epiceditor-preview h6 code{
62
+ font-size:inherit;
63
+ }
64
+
65
+ #epiceditor-preview h1{
66
+ font-size:28px;
67
+ color:#000;
68
+ }
69
+
70
+ #epiceditor-preview h2{
71
+ font-size:24px;
72
+ border-bottom:1px solid #ccc;
73
+ color:#000;
74
+ }
75
+
76
+ #epiceditor-preview h3{
77
+ font-size:18px;
78
+ }
79
+
80
+ #epiceditor-preview h4{
81
+ font-size:16px;
82
+ }
83
+
84
+ #epiceditor-preview h5{
85
+ font-size:14px;
86
+ }
87
+
88
+ #epiceditor-preview h6{
89
+ color:#777;
90
+ font-size:14px;
91
+ }
92
+
93
+ #epiceditor-preview p,
94
+ #epiceditor-preview blockquote,
95
+ #epiceditor-preview ul,
96
+ #epiceditor-preview ol,
97
+ #epiceditor-preview dl,
98
+ #epiceditor-preview li,
99
+ #epiceditor-preview table,
100
+ #epiceditor-preview pre{
101
+ margin:15px 0;
102
+ }
103
+
104
+ #epiceditor-preview hr{
105
+ background:transparent url('../../images/modules/pulls/dirty-shade.png') repeat-x 0 0;
106
+ border:0 none;
107
+ color:#ccc;
108
+ height:4px;
109
+ padding:0;
110
+ }
111
+
112
+ #epiceditor-preview>h2:first-child,
113
+ #epiceditor-preview>h1:first-child,
114
+ #epiceditor-preview>h1:first-child+h2,
115
+ #epiceditor-preview>h3:first-child,
116
+ #epiceditor-preview>h4:first-child,
117
+ #epiceditor-preview>h5:first-child,
118
+ #epiceditor-preview>h6:first-child{
119
+ margin-top:0;
120
+ padding-top:0;
121
+ }
122
+
123
+ #epiceditor-preview h1+p,
124
+ #epiceditor-preview h2+p,
125
+ #epiceditor-preview h3+p,
126
+ #epiceditor-preview h4+p,
127
+ #epiceditor-preview h5+p,
128
+ #epiceditor-preview h6+p{
129
+ margin-top:0;
130
+ }
131
+
132
+ #epiceditor-preview li p.first{
133
+ display:inline-block;
134
+ }
135
+
136
+ #epiceditor-preview ul,
137
+ #epiceditor-preview ol{
138
+ padding-left:30px;
139
+ }
140
+
141
+ #epiceditor-preview ul li>:first-child,
142
+ #epiceditor-preview ol li>:first-child{
143
+ margin-top:0;
144
+ }
145
+
146
+ #epiceditor-preview ul li>:last-child,
147
+ #epiceditor-preview ol li>:last-child{
148
+ margin-bottom:0;
149
+ }
150
+
151
+ #epiceditor-preview dl{
152
+ padding:0;
153
+ }
154
+
155
+ #epiceditor-preview dl dt{
156
+ font-size:14px;
157
+ font-weight:bold;
158
+ font-style:italic;
159
+ padding:0;
160
+ margin:15px 0 5px;
161
+ }
162
+
163
+ #epiceditor-preview dl dt:first-child{
164
+ padding:0;
165
+ }
166
+
167
+ #epiceditor-preview dl dt>:first-child{
168
+ margin-top:0;
169
+ }
170
+
171
+ #epiceditor-preview dl dt>:last-child{
172
+ margin-bottom:0;
173
+ }
174
+
175
+ #epiceditor-preview dl dd{
176
+ margin:0 0 15px;
177
+ padding:0 15px;
178
+ }
179
+
180
+ #epiceditor-preview dl dd>:first-child{
181
+ margin-top:0;
182
+ }
183
+
184
+ #epiceditor-preview dl dd>:last-child{
185
+ margin-bottom:0;
186
+ }
187
+
188
+ #epiceditor-preview blockquote{
189
+ border-left:4px solid #DDD;
190
+ padding:0 15px;
191
+ color:#777;
192
+ }
193
+
194
+ #epiceditor-preview blockquote>:first-child{
195
+ margin-top:0;
196
+ }
197
+
198
+ #epiceditor-preview blockquote>:last-child{
199
+ margin-bottom:0;
200
+ }
201
+
202
+ #epiceditor-preview table{
203
+ padding:0;
204
+ border-collapse: collapse;
205
+ border-spacing: 0;
206
+ font-size: 100%;
207
+ font: inherit;
208
+ }
209
+
210
+ #epiceditor-preview table tr{
211
+ border-top:1px solid #ccc;
212
+ background-color:#fff;
213
+ margin:0;
214
+ padding:0;
215
+ }
216
+
217
+ #epiceditor-preview table tr:nth-child(2n){
218
+ background-color:#f8f8f8;
219
+ }
220
+
221
+ #epiceditor-preview table tr th{
222
+ font-weight:bold;
223
+ }
224
+
225
+ #epiceditor-preview table tr th,
226
+ #epiceditor-preview table tr td{
227
+ border:1px solid #ccc;
228
+ text-align:left;
229
+ margin:0;
230
+ padding:6px 13px;
231
+ }
232
+
233
+ #epiceditor-preview table tr th>:first-child,
234
+ #epiceditor-preview table tr td>:first-child{
235
+ margin-top:0;
236
+ }
237
+
238
+ #epiceditor-preview table tr th>:last-child,
239
+ #epiceditor-preview table tr td>:last-child{
240
+ margin-bottom:0;
241
+ }
242
+
243
+ #epiceditor-preview img{
244
+ max-width:100%;
245
+ }
246
+
247
+ #epiceditor-preview span.frame{
248
+ display:block;
249
+ overflow:hidden;
250
+ }
251
+
252
+ #epiceditor-preview span.frame>span{
253
+ border:1px solid #ddd;
254
+ display:block;
255
+ float:left;
256
+ overflow:hidden;
257
+ margin:13px 0 0;
258
+ padding:7px;
259
+ width:auto;
260
+ }
261
+
262
+ #epiceditor-preview span.frame span img{
263
+ display:block;
264
+ float:left;
265
+ }
266
+
267
+ #epiceditor-preview span.frame span span{
268
+ clear:both;
269
+ color:#333;
270
+ display:block;
271
+ padding:5px 0 0;
272
+ }
273
+
274
+ #epiceditor-preview span.align-center{
275
+ display:block;
276
+ overflow:hidden;
277
+ clear:both;
278
+ }
279
+
280
+ #epiceditor-preview span.align-center>span{
281
+ display:block;
282
+ overflow:hidden;
283
+ margin:13px auto 0;
284
+ text-align:center;
285
+ }
286
+
287
+ #epiceditor-preview span.align-center span img{
288
+ margin:0 auto;
289
+ text-align:center;
290
+ }
291
+
292
+ #epiceditor-preview span.align-right{
293
+ display:block;
294
+ overflow:hidden;
295
+ clear:both;
296
+ }
297
+
298
+ #epiceditor-preview span.align-right>span{
299
+ display:block;
300
+ overflow:hidden;
301
+ margin:13px 0 0;
302
+ text-align:right;
303
+ }
304
+
305
+ #epiceditor-preview span.align-right span img{
306
+ margin:0;
307
+ text-align:right;
308
+ }
309
+
310
+ #epiceditor-preview span.float-left{
311
+ display:block;
312
+ margin-right:13px;
313
+ overflow:hidden;
314
+ float:left;
315
+ }
316
+
317
+ #epiceditor-preview span.float-left span{
318
+ margin:13px 0 0;
319
+ }
320
+
321
+ #epiceditor-preview span.float-right{
322
+ display:block;
323
+ margin-left:13px;
324
+ overflow:hidden;
325
+ float:right;
326
+ }
327
+
328
+ #epiceditor-preview span.float-right>span{
329
+ display:block;
330
+ overflow:hidden;
331
+ margin:13px auto 0;
332
+ text-align:right;
333
+ }
334
+
335
+ #epiceditor-preview code,
336
+ #epiceditor-preview tt{
337
+ margin:0 2px;
338
+ padding:0 5px;
339
+ white-space:nowrap;
340
+ border:1px solid #eaeaea;
341
+ background-color:#f8f8f8;
342
+ border-radius:3px;
343
+ }
344
+
345
+ #epiceditor-preview pre>code{
346
+ margin:0;
347
+ padding:0;
348
+ white-space:pre;
349
+ border:none;
350
+ background:transparent;
351
+ }
352
+
353
+ #epiceditor-preview .highlight pre,
354
+ #epiceditor-preview pre{
355
+ background-color:#f8f8f8;
356
+ border:1px solid #ccc;
357
+ font-size:13px;
358
+ line-height:19px;
359
+ overflow:auto;
360
+ padding:6px 10px;
361
+ border-radius:3px;
362
+ }
363
+
364
+ #epiceditor-preview pre code,
365
+ #epiceditor-preview pre tt{
366
+ background-color:transparent;
367
+ border:none;
368
+ }
skin/adminhtml/default/default/epiceditor/themes/preview/githubNxcode.css ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ padding : 0 10px;
3
+ }
4
+
5
+ body {
6
+ margin : 0;
7
+ padding : 0;
8
+ background : #fff;
9
+ }
10
+
11
+ #epiceditor-wrapper {
12
+ background : white;
13
+ }
14
+
15
+ #epiceditor-preview {
16
+ padding-top : 10px;
17
+ padding-bottom : 10px;
18
+ font-family : Helvetica, arial, freesans, clean, sans-serif;
19
+ font-size : 13px;
20
+ line-height : 1.6;
21
+ }
22
+
23
+ #epiceditor-preview>*:first-child {
24
+ margin-top : 0 !important;
25
+ }
26
+
27
+ #epiceditor-preview>*:last-child {
28
+ margin-bottom : 0 !important;
29
+ }
30
+
31
+ #epiceditor-preview a {
32
+ color : #4183C4;
33
+ text-decoration : none;
34
+ }
35
+
36
+ #epiceditor-preview a:hover {
37
+ text-decoration : underline;
38
+ }
39
+
40
+ #epiceditor-preview h1,
41
+ #epiceditor-preview h2,
42
+ #epiceditor-preview h3,
43
+ #epiceditor-preview h4,
44
+ #epiceditor-preview h5,
45
+ #epiceditor-preview h6 {
46
+ margin : 20px 0 10px;
47
+ padding : 0;
48
+ font-weight : bold;
49
+ -webkit-font-smoothing : antialiased;
50
+ }
51
+
52
+ #epiceditor-preview h1 tt,
53
+ #epiceditor-preview h1 code,
54
+ #epiceditor-preview h2 tt,
55
+ #epiceditor-preview h2 code,
56
+ #epiceditor-preview h3 tt,
57
+ #epiceditor-preview h3 code,
58
+ #epiceditor-preview h4 tt,
59
+ #epiceditor-preview h4 code,
60
+ #epiceditor-preview h5 tt,
61
+ #epiceditor-preview h5 code,
62
+ #epiceditor-preview h6 tt,
63
+ #epiceditor-preview h6 code {
64
+ font-size : inherit;
65
+ }
66
+
67
+ #epiceditor-preview h1 {
68
+ font-size : 28px;
69
+ color : #000;
70
+ }
71
+
72
+ #epiceditor-preview h2 {
73
+ font-size : 24px;
74
+ border-bottom : 1px solid #ccc;
75
+ color : #000;
76
+ }
77
+
78
+ #epiceditor-preview h3 {
79
+ font-size : 18px;
80
+ }
81
+
82
+ #epiceditor-preview h4 {
83
+ font-size : 16px;
84
+ }
85
+
86
+ #epiceditor-preview h5 {
87
+ font-size : 14px;
88
+ }
89
+
90
+ #epiceditor-preview h6 {
91
+ color : #777;
92
+ font-size : 14px;
93
+ }
94
+
95
+ #epiceditor-preview p,
96
+ #epiceditor-preview blockquote,
97
+ #epiceditor-preview ul,
98
+ #epiceditor-preview ol,
99
+ #epiceditor-preview dl,
100
+ #epiceditor-preview li,
101
+ #epiceditor-preview table,
102
+ #epiceditor-preview pre {
103
+ margin : 15px 0;
104
+ }
105
+
106
+ #epiceditor-preview hr {
107
+ background : transparent url('../../images/modules/pulls/dirty-shade.png') repeat-x 0 0;
108
+ border : 0 none;
109
+ color : #ccc;
110
+ height : 4px;
111
+ padding : 0;
112
+ }
113
+
114
+ #epiceditor-preview>h2:first-child,
115
+ #epiceditor-preview>h1:first-child,
116
+ #epiceditor-preview>h1:first-child+h2,
117
+ #epiceditor-preview>h3:first-child,
118
+ #epiceditor-preview>h4:first-child,
119
+ #epiceditor-preview>h5:first-child,
120
+ #epiceditor-preview>h6:first-child {
121
+ margin-top : 0;
122
+ padding-top : 0;
123
+ }
124
+
125
+ #epiceditor-preview h1+p,
126
+ #epiceditor-preview h2+p,
127
+ #epiceditor-preview h3+p,
128
+ #epiceditor-preview h4+p,
129
+ #epiceditor-preview h5+p,
130
+ #epiceditor-preview h6+p {
131
+ margin-top : 0;
132
+ }
133
+
134
+ #epiceditor-preview li p.first {
135
+ display : inline-block;
136
+ }
137
+
138
+ #epiceditor-preview ul,
139
+ #epiceditor-preview ol {
140
+ padding-left : 30px;
141
+ }
142
+
143
+ #epiceditor-preview ul li>:first-child,
144
+ #epiceditor-preview ol li>:first-child {
145
+ margin-top : 0;
146
+ }
147
+
148
+ #epiceditor-preview ul li>:last-child,
149
+ #epiceditor-preview ol li>:last-child {
150
+ margin-bottom : 0;
151
+ }
152
+
153
+ #epiceditor-preview dl {
154
+ padding : 0;
155
+ }
156
+
157
+ #epiceditor-preview dl dt {
158
+ font-size : 14px;
159
+ font-weight : bold;
160
+ font-style : italic;
161
+ padding : 0;
162
+ margin : 15px 0 5px;
163
+ }
164
+
165
+ #epiceditor-preview dl dt:first-child {
166
+ padding : 0;
167
+ }
168
+
169
+ #epiceditor-preview dl dt>:first-child {
170
+ margin-top : 0;
171
+ }
172
+
173
+ #epiceditor-preview dl dt>:last-child {
174
+ margin-bottom : 0;
175
+ }
176
+
177
+ #epiceditor-preview dl dd {
178
+ margin : 0 0 15px;
179
+ padding : 0 15px;
180
+ }
181
+
182
+ #epiceditor-preview dl dd>:first-child {
183
+ margin-top : 0;
184
+ }
185
+
186
+ #epiceditor-preview dl dd>:last-child {
187
+ margin-bottom : 0;
188
+ }
189
+
190
+ #epiceditor-preview blockquote {
191
+ border-left : 4px solid #DDD;
192
+ padding : 0 15px;
193
+ color : #777;
194
+ }
195
+
196
+ #epiceditor-preview blockquote>:first-child {
197
+ margin-top : 0;
198
+ }
199
+
200
+ #epiceditor-preview blockquote>:last-child {
201
+ margin-bottom : 0;
202
+ }
203
+
204
+ #epiceditor-preview table {
205
+ padding : 0;
206
+ border-collapse : collapse;
207
+ border-spacing : 0;
208
+ font-size : 100%;
209
+ font : inherit;
210
+ }
211
+
212
+ #epiceditor-preview table tr {
213
+ border-top : 1px solid #ccc;
214
+ background-color : #fff;
215
+ margin : 0;
216
+ padding : 0;
217
+ }
218
+
219
+ #epiceditor-preview table tr:nth-child(2n) {
220
+ background-color : #f8f8f8;
221
+ }
222
+
223
+ #epiceditor-preview table tr th {
224
+ font-weight : bold;
225
+ }
226
+
227
+ #epiceditor-preview table tr th,
228
+ #epiceditor-preview table tr td {
229
+ border : 1px solid #ccc;
230
+ text-align : left;
231
+ margin : 0;
232
+ padding : 6px 13px;
233
+ }
234
+
235
+ #epiceditor-preview table tr th>:first-child,
236
+ #epiceditor-preview table tr td>:first-child {
237
+ margin-top : 0;
238
+ }
239
+
240
+ #epiceditor-preview table tr th>:last-child,
241
+ #epiceditor-preview table tr td>:last-child {
242
+ margin-bottom : 0;
243
+ }
244
+
245
+ #epiceditor-preview img {
246
+ max-width : 100%;
247
+ }
248
+
249
+ #epiceditor-preview span.frame {
250
+ display : block;
251
+ overflow : hidden;
252
+ }
253
+
254
+ #epiceditor-preview span.frame>span {
255
+ border : 1px solid #ddd;
256
+ display : block;
257
+ float : left;
258
+ overflow : hidden;
259
+ margin : 13px 0 0;
260
+ padding : 7px;
261
+ width : auto;
262
+ }
263
+
264
+ #epiceditor-preview span.frame span img {
265
+ display : block;
266
+ float : left;
267
+ }
268
+
269
+ #epiceditor-preview span.frame span span {
270
+ clear : both;
271
+ color : #333;
272
+ display : block;
273
+ padding : 5px 0 0;
274
+ }
275
+
276
+ #epiceditor-preview span.align-center {
277
+ display : block;
278
+ overflow : hidden;
279
+ clear : both;
280
+ }
281
+
282
+ #epiceditor-preview span.align-center>span {
283
+ display : block;
284
+ overflow : hidden;
285
+ margin : 13px auto 0;
286
+ text-align : center;
287
+ }
288
+
289
+ #epiceditor-preview span.align-center span img {
290
+ margin : 0 auto;
291
+ text-align : center;
292
+ }
293
+
294
+ #epiceditor-preview span.align-right {
295
+ display : block;
296
+ overflow : hidden;
297
+ clear : both;
298
+ }
299
+
300
+ #epiceditor-preview span.align-right>span {
301
+ display : block;
302
+ overflow : hidden;
303
+ margin : 13px 0 0;
304
+ text-align : right;
305
+ }
306
+
307
+ #epiceditor-preview span.align-right span img {
308
+ margin : 0;
309
+ text-align : right;
310
+ }
311
+
312
+ #epiceditor-preview span.float-left {
313
+ display : block;
314
+ margin-right : 13px;
315
+ overflow : hidden;
316
+ float : left;
317
+ }
318
+
319
+ #epiceditor-preview span.float-left span {
320
+ margin : 13px 0 0;
321
+ }
322
+
323
+ #epiceditor-preview span.float-right {
324
+ display : block;
325
+ margin-left : 13px;
326
+ overflow : hidden;
327
+ float : right;
328
+ }
329
+
330
+ #epiceditor-preview span.float-right>span {
331
+ display : block;
332
+ overflow : hidden;
333
+ margin : 13px auto 0;
334
+ text-align : right;
335
+ }
336
+
337
+ #epiceditor-preview code,
338
+ #epiceditor-preview tt {
339
+ margin : 0 2px;
340
+ padding : 0 5px;
341
+ white-space : nowrap;
342
+ border : 1px solid #eaeaea;
343
+ background-color : #f8f8f8;
344
+ border-radius : 3px;
345
+ }
346
+
347
+ #epiceditor-preview pre>code {
348
+ margin : 0;
349
+ padding : 0;
350
+ white-space : pre;
351
+ border : none;
352
+ background : transparent;
353
+ }
354
+
355
+ #epiceditor-preview .highlight pre,
356
+ #epiceditor-preview pre {
357
+ background-color : #f8f8f8;
358
+ border : 1px solid #ccc;
359
+ font-size : 13px;
360
+ line-height : 19px;
361
+ overflow : auto;
362
+ padding : 6px 10px;
363
+ border-radius : 3px;
364
+ }
365
+
366
+ #epiceditor-preview pre code,
367
+ #epiceditor-preview pre tt {
368
+ background-color : transparent;
369
+ border : none;
370
+ }
371
+
372
+ /*custom*/
373
+ pre.hljs {
374
+ background-color : #fff !important;
375
+ }
376
+
377
+ /*
378
+ highlight.js
379
+ XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>
380
+ */
381
+ pre.hljs code {
382
+ display : block;
383
+ padding : 0.5em;
384
+ background : #fff;
385
+ color : black;
386
+ }
387
+
388
+ pre.hljs .comment,
389
+ pre.hljs .template_comment,
390
+ pre.hljs .javadoc,
391
+ pre.hljs .comment * {
392
+ color : rgb(0, 106, 0);
393
+ }
394
+
395
+ pre.hljs .keyword,
396
+ pre.hljs .literal,
397
+ pre.hljs .nginx .title {
398
+ color : rgb(170, 13, 145);
399
+ }
400
+
401
+ pre.hljs .method,
402
+ pre.hljs .list .title,
403
+ pre.hljs .tag .title,
404
+ pre.hljs .setting .value,
405
+ pre.hljs .winutils,
406
+ pre.hljs .tex .command,
407
+ pre.hljs .http .title,
408
+ pre.hljs .request,
409
+ pre.hljs .status {
410
+ color : #008;
411
+ }
412
+
413
+ pre.hljs .envvar,
414
+ pre.hljs .tex .special {
415
+ color : #660;
416
+ }
417
+
418
+ pre.hljs .string {
419
+ color : rgb(196, 26, 22);
420
+ }
421
+
422
+ pre.hljs .tag .value,
423
+ pre.hljs .cdata,
424
+ pre.hljs .filter .argument,
425
+ pre.hljs .attr_selector,
426
+ pre.hljs .apache .cbracket,
427
+ pre.hljs .date,
428
+ pre.hljs .regexp {
429
+ color : #080;
430
+ }
431
+
432
+ pre.hljs .sub .identifier,
433
+ pre.hljs .pi,
434
+ pre.hljs .tag,
435
+ pre.hljs .tag .keyword,
436
+ pre.hljs .decorator,
437
+ pre.hljs .ini .title,
438
+ pre.hljs .shebang,
439
+ pre.hljs .prompt,
440
+ pre.hljs .hexcolor,
441
+ pre.hljs .rules .value,
442
+ pre.hljs .css .value .number,
443
+ pre.hljs .symbol,
444
+ pre.hljs .symbol .string,
445
+ pre.hljs .number,
446
+ pre.hljs .css .function,
447
+ pre.hljs .clojure .title,
448
+ pre.hljs .clojure .built_in {
449
+ color : rgb(28, 0, 207);
450
+ }
451
+
452
+ pre.hljs .class .title,
453
+ pre.hljs .haskell .type,
454
+ pre.hljs .smalltalk .class,
455
+ pre.hljs .javadoctag,
456
+ pre.hljs .yardoctag,
457
+ pre.hljs .phpdoc,
458
+ pre.hljs .typename,
459
+ pre.hljs .tag .attribute,
460
+ pre.hljs .doctype,
461
+ pre.hljs .class .id,
462
+ pre.hljs .built_in,
463
+ pre.hljs .setting,
464
+ pre.hljs .params,
465
+ pre.hljs .clojure .attribute {
466
+ color : rgb(92, 38, 153);
467
+ }
468
+
469
+ pre.hljs .variable {
470
+ color : rgb(63, 110, 116);
471
+ }
472
+
473
+ pre.hljs .css .tag,
474
+ pre.hljs .rules .property,
475
+ pre.hljs .pseudo,
476
+ pre.hljs .subst {
477
+ color : #000;
478
+ }
479
+
480
+ pre.hljs .css .class, pre.hljs .css .id {
481
+ color : #9B703F;
482
+ }
483
+
484
+ pre.hljs .value .important {
485
+ color : #ff7700;
486
+ font-weight : bold;
487
+ }
488
+
489
+ pre.hljs .rules .keyword {
490
+ color : #C5AF75;
491
+ }
492
+
493
+ pre.hljs .annotation,
494
+ pre.hljs .apache .sqbracket,
495
+ pre.hljs .nginx .built_in {
496
+ color : #9B859D;
497
+ }
498
+
499
+ pre.hljs .preprocessor,
500
+ pre.hljs .preprocessor * {
501
+ color : rgb(100, 56, 32);
502
+ }
503
+
504
+ pre.hljs .tex .formula {
505
+ background-color : #EEE;
506
+ font-style : italic;
507
+ }
508
+
509
+ pre.hljs .diff .header,
510
+ pre.hljs .chunk {
511
+ color : #808080;
512
+ font-weight : bold;
513
+ }
514
+
515
+ pre.hljs .diff .change {
516
+ background-color : #BCCFF9;
517
+ }
518
+
519
+ pre.hljs .addition {
520
+ background-color : #BAEEBA;
521
+ }
522
+
523
+ pre.hljs .deletion {
524
+ background-color : #FFC8BD;
525
+ }
526
+
527
+ pre.hljs .comment .yardoctag {
528
+ font-weight : bold;
529
+ }
530
+
531
+ pre.hljs .method .id {
532
+ color : #000;
533
+ }
skin/adminhtml/default/default/epiceditor/themes/preview/preview-dark.css ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ padding : 0 10px;
3
+ }
4
+
5
+ body {
6
+ margin : 0;
7
+ padding : 10px 0;
8
+ background : #000;
9
+ }
10
+
11
+ #epiceditor-preview h1,
12
+ #epiceditor-preview h2,
13
+ #epiceditor-preview h3,
14
+ #epiceditor-preview h4,
15
+ #epiceditor-preview h5,
16
+ #epiceditor-preview h6,
17
+ #epiceditor-preview p,
18
+ #epiceditor-preview blockquote {
19
+ margin : 0;
20
+ padding : 0;
21
+ }
22
+
23
+ #epiceditor-preview {
24
+ background : #000;
25
+ font-family : "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
26
+ font-size : 13px;
27
+ line-height : 18px;
28
+ color : #ccc;
29
+ }
30
+
31
+ #epiceditor-preview a {
32
+ color : #fff;
33
+ }
34
+
35
+ #epiceditor-preview a:hover {
36
+ color : #00ff00;
37
+ text-decoration : none;
38
+ }
39
+
40
+ #epiceditor-preview a img {
41
+ border : none;
42
+ }
43
+
44
+ #epiceditor-preview p {
45
+ margin-bottom : 9px;
46
+ }
47
+
48
+ #epiceditor-preview h1,
49
+ #epiceditor-preview h2,
50
+ #epiceditor-preview h3,
51
+ #epiceditor-preview h4,
52
+ #epiceditor-preview h5,
53
+ #epiceditor-preview h6 {
54
+ color : #cdcdcd;
55
+ line-height : 36px;
56
+ }
57
+
58
+ #epiceditor-preview h1 {
59
+ margin-bottom : 18px;
60
+ font-size : 30px;
61
+ }
62
+
63
+ #epiceditor-preview h2 {
64
+ font-size : 24px;
65
+ }
66
+
67
+ #epiceditor-preview h3 {
68
+ font-size : 18px;
69
+ }
70
+
71
+ #epiceditor-preview h4 {
72
+ font-size : 16px;
73
+ }
74
+
75
+ #epiceditor-preview h5 {
76
+ font-size : 14px;
77
+ }
78
+
79
+ #epiceditor-preview h6 {
80
+ font-size : 13px;
81
+ }
82
+
83
+ #epiceditor-preview hr {
84
+ margin : 0 0 19px;
85
+ border : 0;
86
+ border-bottom : 1px solid #ccc;
87
+ }
88
+
89
+ #epiceditor-preview blockquote {
90
+ padding : 13px 13px 21px 15px;
91
+ margin-bottom : 18px;
92
+ font-family : georgia, serif;
93
+ font-style : italic;
94
+ }
95
+
96
+ #epiceditor-preview blockquote:before {
97
+ content : "\201C";
98
+ font-size : 40px;
99
+ margin-left : -10px;
100
+ font-family : georgia, serif;
101
+ color : #eee;
102
+ }
103
+
104
+ #epiceditor-preview blockquote p {
105
+ font-size : 14px;
106
+ font-weight : 300;
107
+ line-height : 18px;
108
+ margin-bottom : 0;
109
+ font-style : italic;
110
+ }
111
+
112
+ #epiceditor-preview code, #epiceditor-preview pre {
113
+ font-family : Monaco, Andale Mono, Courier New, monospace;
114
+ }
115
+
116
+ #epiceditor-preview code {
117
+ background-color : #000;
118
+ color : #f92672;
119
+ padding : 1px 3px;
120
+ font-size : 12px;
121
+ -webkit-border-radius : 3px;
122
+ -moz-border-radius : 3px;
123
+ border-radius : 3px;
124
+ }
125
+
126
+ #epiceditor-preview pre {
127
+ display : block;
128
+ padding : 14px;
129
+ color : #66d9ef;
130
+ margin : 0 0 18px;
131
+ line-height : 16px;
132
+ font-size : 11px;
133
+ border : 1px solid #d9d9d9;
134
+ white-space : pre-wrap;
135
+ word-wrap : break-word;
136
+ }
137
+
138
+ #epiceditor-preview pre code {
139
+ background-color : #000;
140
+ color : #ccc;
141
+ font-size : 11px;
142
+ padding : 0;
143
+ }
skin/adminhtml/default/default/markdown.css DELETED
@@ -1,310 +0,0 @@
1
- /*
2
- Copyright http://kevinburke.bitbucket.org/markdowncss/
3
- Adapted for Magento Markdown
4
- */
5
- .markdown {
6
- margin : 0 auto;
7
- font-family : Georgia, Palatino, serif;
8
- color : #444444;
9
- line-height : 1;
10
- max-width : 960px;
11
- padding : 30px;
12
- background-color : white;
13
- }
14
-
15
- .markdown h1, .markdown h2, .markdown h3, .markdown h4 {
16
- color : #111111;
17
- font-weight : 400;
18
- }
19
-
20
- .markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown p {
21
- margin-bottom : 24px;
22
- padding : 0;
23
- }
24
-
25
- .markdown h1 {
26
- font-size : 48px;
27
- }
28
-
29
- .markdown h2 {
30
- font-size : 36px;
31
- /* The bottom margin is small. It's designed to be used with gray meta text
32
- * below a post title. */
33
- margin : 24px 0 6px;
34
- }
35
-
36
- .markdown h3 {
37
- font-size : 24px;
38
- }
39
-
40
- .markdown h4 {
41
- font-size : 21px;
42
- }
43
-
44
- .markdown h5 {
45
- font-size : 18px;
46
- }
47
-
48
- .markdown a {
49
- color : #0099ff;
50
- margin : 0;
51
- padding : 0;
52
- vertical-align : baseline;
53
- }
54
-
55
- .markdown a:hover {
56
- text-decoration : none;
57
- color : #ff6600;
58
- }
59
-
60
- .markdown a:visited {
61
- color : purple;
62
- }
63
-
64
- .markdown ul, ol {
65
- list-style: disc inside;
66
- padding : 0;
67
- margin : 0;
68
- }
69
-
70
- .markdown li {
71
- line-height : 24px;
72
- }
73
-
74
- .markdown li ul, .markdown li ul {
75
- margin-left : 24px;
76
- }
77
-
78
- .markdown p, .markdown ul, .markdown ol {
79
- font-size : 16px;
80
- line-height : 24px;
81
- /*max-width : 540px;*/
82
- }
83
-
84
- .markdown pre {
85
- padding : 0px 24px;
86
- max-width : 700px;
87
- white-space : pre-wrap;
88
- }
89
-
90
- .markdown code, .markdown pre code {
91
- font-family : Consolas, Monaco, Andale Mono, monospace;
92
- line-height : 1.5;
93
- font-size : 13px;
94
-
95
- margin : 0 2px;
96
- padding : 0 5px;
97
- /*white-space: nowrap;*/
98
- border : 1px solid #eaeaea;
99
- background-color : #f8f8f8;
100
- border-radius : 3px;
101
- }
102
-
103
- .markdown aside {
104
- display : block;
105
- float : right;
106
- width : 390px;
107
- }
108
-
109
- .markdown blockquote {
110
- border-left : .5em solid #eee;
111
- padding : 0 2em;
112
- margin-left : 0;
113
- max-width : 476px;
114
- }
115
-
116
- .markdown blockquote cite {
117
- font-size : 14px;
118
- line-height : 20px;
119
- color : #bfbfbf;
120
- }
121
-
122
- .markdown blockquote cite:before {
123
- content : '\2014 \00A0';
124
- }
125
-
126
- .markdown blockquote p {
127
- color : #666;
128
- max-width : 460px;
129
- }
130
-
131
- .markdown hr {
132
- width : 540px;
133
- text-align : left;
134
- margin : 0 auto 0 0;
135
- color : #999;
136
- }
137
-
138
- /* Code below this line is copyright Twitter Inc. */
139
-
140
- .markdown button,
141
- .markdown input,
142
- .markdown select,
143
- .markdown textarea {
144
- font-size : 100%;
145
- margin : 0;
146
- vertical-align : baseline;
147
- *vertical-align : middle;
148
- }
149
-
150
- .markdown button, .markdown input {
151
- line-height : normal;
152
- *overflow : visible;
153
- }
154
-
155
- .markdown button::-moz-focus-inner, .markdown input::-moz-focus-inner {
156
- border : 0;
157
- padding : 0;
158
- }
159
-
160
- .markdown button,
161
- .markdown input[type="button"],
162
- .markdown input[type="reset"],
163
- .markdown input[type="submit"] {
164
- cursor : pointer;
165
- -webkit-appearance : button;
166
- }
167
-
168
- .markdown input[type=checkbox], .markdown input[type=radio] {
169
- cursor : pointer;
170
- }
171
-
172
- /* override default chrome & firefox settings */
173
- .markdown input:not([type="image"]), .markdown textarea {
174
- -webkit-box-sizing : content-box;
175
- -moz-box-sizing : content-box;
176
- box-sizing : content-box;
177
- }
178
-
179
- .markdown input[type="search"] {
180
- -webkit-appearance : textfield;
181
- -webkit-box-sizing : content-box;
182
- -moz-box-sizing : content-box;
183
- box-sizing : content-box;
184
- }
185
-
186
- .markdown input[type="search"]::-webkit-search-decoration {
187
- -webkit-appearance : none;
188
- }
189
-
190
- .markdown label,
191
- .markdown input,
192
- .markdown select,
193
- .markdown textarea {
194
- font-family : "Helvetica Neue", Helvetica, Arial, sans-serif;
195
- font-size : 13px;
196
- font-weight : normal;
197
- line-height : normal;
198
- margin-bottom : 18px;
199
- }
200
-
201
- .markdown input[type=checkbox], .markdown input[type=radio] {
202
- cursor : pointer;
203
- margin-bottom : 0;
204
- }
205
-
206
- .markdown input[type=text],
207
- .markdown input[type=password],
208
- .markdown textarea,
209
- .markdown select {
210
- display : inline-block;
211
- width : 210px;
212
- padding : 4px;
213
- font-size : 13px;
214
- font-weight : normal;
215
- line-height : 18px;
216
- height : 18px;
217
- color : #808080;
218
- border : 1px solid #ccc;
219
- -webkit-border-radius : 3px;
220
- -moz-border-radius : 3px;
221
- border-radius : 3px;
222
- }
223
-
224
- .markdown select, .markdown input[type=file] {
225
- height : 27px;
226
- line-height : 27px;
227
- }
228
-
229
- .markdown textarea {
230
- height : auto;
231
- }
232
-
233
- /* grey out placeholders */
234
- .markdown :-moz-placeholder {
235
- color : #bfbfbf;
236
- }
237
-
238
- .markdown ::-webkit-input-placeholder {
239
- color : #bfbfbf;
240
- }
241
-
242
- .markdown input[type=text],
243
- .markdown input[type=password],
244
- .markdown select,
245
- .markdown textarea {
246
- -webkit-transition : border linear 0.2s, box-shadow linear 0.2s;
247
- -moz-transition : border linear 0.2s, box-shadow linear 0.2s;
248
- transition : border linear 0.2s, box-shadow linear 0.2s;
249
- -webkit-box-shadow : inset 0 1px 3px rgba(0, 0, 0, 0.1);
250
- -moz-box-shadow : inset 0 1px 3px rgba(0, 0, 0, 0.1);
251
- box-shadow : inset 0 1px 3px rgba(0, 0, 0, 0.1);
252
- }
253
-
254
- .markdown input[type=text]:focus, .markdown input[type=password]:focus, .markdown textarea:focus {
255
- outline : none;
256
- border-color : rgba(82, 168, 236, 0.8);
257
- -webkit-box-shadow : inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
258
- -moz-box-shadow : inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
259
- box-shadow : inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
260
- }
261
-
262
- /* buttons */
263
- .markdown button {
264
- display : inline-block;
265
- padding : 4px 14px;
266
- font-family : "Helvetica Neue", Helvetica, Arial, sans-serif;
267
- font-size : 13px;
268
- line-height : 18px;
269
- -webkit-border-radius : 4px;
270
- -moz-border-radius : 4px;
271
- border-radius : 4px;
272
- -webkit-box-shadow : inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
273
- -moz-box-shadow : inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
274
- box-shadow : inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
275
- background-color : #0064cd;
276
- background-repeat : repeat-x;
277
- background-image : -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));
278
- background-image : -moz-linear-gradient(top, #049cdb, #0064cd);
279
- background-image : -ms-linear-gradient(top, #049cdb, #0064cd);
280
- background-image : -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));
281
- background-image : -webkit-linear-gradient(top, #049cdb, #0064cd);
282
- background-image : -o-linear-gradient(top, #049cdb, #0064cd);
283
- background-image : linear-gradient(top, #049cdb, #0064cd);
284
- color : #fff;
285
- text-shadow : 0 -1px 0 rgba(0, 0, 0, 0.25);
286
- border : 1px solid #004b9a;
287
- border-bottom-color : #003f81;
288
- -webkit-transition : 0.1s linear all;
289
- -moz-transition : 0.1s linear all;
290
- transition : 0.1s linear all;
291
- border-color : #0064cd #0064cd #003f81;
292
- border-color : rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
293
- }
294
-
295
- .markdown button:hover {
296
- color : #fff;
297
- background-position : 0 -15px;
298
- text-decoration : none;
299
- }
300
-
301
- .markdown button:active {
302
- -webkit-box-shadow : inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
303
- -moz-box-shadow : inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
304
- box-shadow : inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
305
- }
306
-
307
- .markdown button::-moz-focus-inner {
308
- padding : 0;
309
- border : 0;
310
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
skin/adminhtml/default/default/markdown/mdm.css ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ /**
2
+ Magento Markdown Module CSS
3
+ */
4
+ .fReaderDrag {
5
+ border : 5px solid darkseagreen;
6
+ }