ep_markdown_toc 1.0.6 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,8 @@ It does this in multiple ways:
7
7
  - It enables automatic generation of a Table of Contents based on Markdown-style hashsign headings (#, ##, etc.) in Etherpad documents (can be disabled through settings). === style headings are not supported at the moment, and likely will never be.
8
8
  - It styles the headings to make it easier to scan long documents for chapters (can be disabled through settings)
9
9
  - It adds a button to the toolbar that shows a quick cheatsheet for Markdown syntax
10
- - It removes the default text styling buttons from the toolbar (can be reenabled through settings)
10
+ - It removes the default text styling buttons from the toolbar and disables the associated shortcuts.
11
+ - Tries to remove rich text when pasting
11
12
 
12
13
  It was developed as a compagnion to [Octomode](https://cc.vvvvvvaria.org/wiki/Octomode), a collective editing space for PDF making, using Etherpad, Paged.js and Flask.
13
14
  It borrows heavily from the excellent [ep_headings2](https://github.com/ether/ep_headings2) plugin.
package/index.js CHANGED
@@ -12,7 +12,13 @@ exports.eejsBlock_styles = (hook, context) => {
12
12
 
13
13
  exports.eejsBlock_editbarMenuRight = (hookName, args, cb) => {
14
14
  // console.log("MENU RIGHT");
15
- args.content = eejs.require('ep_markdown_toc/templates/markdownButton.ejs') + args.content;
15
+ args.content = eejs.require('ep_markdown_toc/templates/tocToggleButton.ejs') +
16
+ eejs.require('ep_markdown_toc/templates/markdownButton.ejs') + args.content;
17
+ return cb();
18
+ };
19
+
20
+ exports.eejsBlock_dd_view = (hookName, args, cb) => {
21
+ args.content += '<li><a href="#" id="markdown-toc-toggle-menu">Markdown TOC</a></li>';
16
22
  return cb();
17
23
  };
18
24
 
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "ep_markdown_toc",
3
3
  "description": "Provides markdown-based heading detection and TOC for Etherpad, used in Octomode.",
4
- "version": "1.0.6",
4
+ "version": "1.0.10",
5
5
  "author": {
6
6
  "name": "hrk",
7
7
  "email": "heerko@hackersanddesigners.nl"
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/heerko/ep_markdown_toc"
11
+ "url": "git+https://github.com/heerko/ep_markdown_toc.git"
12
12
  },
13
13
  "dependencies": {
14
14
  "showdown": "*"
@@ -27,4 +27,36 @@
27
27
 
28
28
  #markdown-cheat .popup-content pre {
29
29
  white-space: break-spaces;
30
- }
30
+ }
31
+
32
+ .cheat-grid {
33
+ display: grid;
34
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
35
+ gap: 10px;
36
+ }
37
+
38
+ .cheat-block {
39
+ background: #f7f7f7;
40
+ border: 1px solid #ddd;
41
+ border-radius: 4px;
42
+ padding: 10px;
43
+ font-family: monospace;
44
+ font-size: 12px;
45
+ }
46
+
47
+ .cheat-block h3 {
48
+ margin: 0 0 6px 0;
49
+ font-size: 13px;
50
+ }
51
+
52
+ .cheat-lines {
53
+ list-style: none;
54
+ padding: 0;
55
+ margin: 0;
56
+ line-height: 1.5;
57
+ }
58
+
59
+ .cheat-lines li {
60
+ margin: 0 0 4px 0;
61
+ white-space: pre-line;
62
+ }
@@ -53,15 +53,15 @@ Add a margin for the TOC when enabled
53
53
  For hiding the regular Ace/Etherpad editor buttons.
54
54
  TODO: We might be able to this through manipulating the settings instead
55
55
  */
56
- .hide-buttons #editbar .menu_left [data-key="bold"],
57
- .hide-buttons #editbar .menu_left [data-key="italic"],
58
- .hide-buttons #editbar .menu_left [data-key="underline"],
59
- .hide-buttons #editbar .menu_left [data-key="strikethrough"],
60
- .hide-buttons #editbar .menu_left [data-key="bold"],
61
- .hide-buttons #editbar .menu_left [data-key="insertorderedlist"],
62
- .hide-buttons #editbar .menu_left [data-key="insertunorderedlist"],
63
- .hide-buttons #editbar .menu_left [data-key="indent"],
64
- .hide-buttons #editbar .menu_left [data-key="outdent"],
65
- .hide-buttons #editbar .menu_left [data-key="bold"] + li {
56
+ #editbar .menu_left [data-key="bold"],
57
+ #editbar .menu_left [data-key="italic"],
58
+ #editbar .menu_left [data-key="underline"],
59
+ #editbar .menu_left [data-key="strikethrough"],
60
+ #editbar .menu_left [data-key="bold"],
61
+ #editbar .menu_left [data-key="insertorderedlist"],
62
+ #editbar .menu_left [data-key="insertunorderedlist"],
63
+ #editbar .menu_left [data-key="indent"],
64
+ #editbar .menu_left [data-key="outdent"],
65
+ #editbar .menu_left [data-key="bold"] + li {
66
66
  display: none !important;
67
67
  }
@@ -3,6 +3,7 @@
3
3
  let _editorInfo = null; // Both no longer used atm, but leaving them for now.
4
4
  let _ace;
5
5
  const tags = ['h1', 'h2', 'h3', 'h4'];
6
+ let plainPasteEnabled = true;
6
7
 
7
8
  /* Load the css in the editor iframe */
8
9
  exports.aceEditorCSS = function() {
@@ -15,6 +16,9 @@ exports.aceEditorCSS = function() {
15
16
  exports.postAceInit = (hook, context) => {
16
17
  // store a reference to the editor for later
17
18
  _ace = context.ace;
19
+ // Initialize plain paste handler (if enabled).
20
+ maybeInitPlainPaste(context);
21
+ initShortcutBlocker(context);
18
22
  updateTOC();
19
23
  };
20
24
 
@@ -71,6 +75,14 @@ exports.postToolbarInit = () => {
71
75
  $('#markdown-cheat').toggleClass('popup-show');
72
76
  });
73
77
 
78
+ const toggleToc = (event) => {
79
+ if (event) event.preventDefault();
80
+ $('#options-hideToc').click();
81
+ };
82
+
83
+ $('#markdown-toc-toggle').on('click', toggleToc);
84
+ $('#markdown-toc-toggle-menu').on('click', toggleToc);
85
+
74
86
  initSettingsUI();
75
87
  };
76
88
 
@@ -153,6 +165,7 @@ but it maybe possible to manipulate the list from settings.json?
153
165
  */
154
166
  function initSettingsUI() {
155
167
  const padcookie = require('ep_etherpad-lite/static/js/pad_cookie').padcookie;
168
+ const plainPaste = require('./plain_paste');
156
169
 
157
170
  let prefs = padcookie.getPref('userPrefs') || {};
158
171
 
@@ -161,14 +174,14 @@ function initSettingsUI() {
161
174
  prefs.hideToc = false; // defaults to false
162
175
  padcookie.setPref('userPrefs', prefs);
163
176
  }
164
- if (typeof prefs.hideButtons === 'undefined') {
165
- prefs.hideButtons = true; // defaults to true
166
- padcookie.setPref('userPrefs', prefs);
167
- }
168
177
  if (typeof prefs.styleHeadings === 'undefined') {
169
178
  prefs.styleHeadings = true; // also true
170
179
  padcookie.setPref('userPrefs', prefs);
171
180
  }
181
+ if (typeof prefs.plainPaste === 'undefined') {
182
+ prefs.plainPaste = true; // default to on
183
+ padcookie.setPref('userPrefs', prefs);
184
+ }
172
185
 
173
186
  // apply the headings class to the inner iframe if set
174
187
  const padOuter = document.querySelector('iframe[name="ace_outer"]');
@@ -201,8 +214,23 @@ function initSettingsUI() {
201
214
  }
202
215
 
203
216
  bindToggleSetting('#options-hideToc', 'hide-toc', 'hideToc');
204
- bindToggleSetting('#options-hideButtons', 'hide-buttons', 'hideButtons');
205
217
  bindToggleSetting('#options-styleHeadings', 'style-md-headings', 'styleHeadings');
218
+
219
+ // Plain paste toggle
220
+ const plainPasteCheckbox = $('#options-plainPaste');
221
+ plainPasteCheckbox.prop('checked', prefs.plainPaste === true);
222
+ plainPasteEnabled = prefs.plainPaste === true;
223
+ plainPasteCheckbox.on('change', function () {
224
+ const isChecked = $(this).is(':checked');
225
+ prefs.plainPaste = isChecked;
226
+ plainPasteEnabled = isChecked;
227
+ padcookie.setPref('userPrefs', prefs);
228
+ if (isChecked) {
229
+ plainPaste.ensure(context);
230
+ } else {
231
+ plainPaste.disable();
232
+ }
233
+ });
206
234
  }
207
235
 
208
236
  /* functions stolen from ep_headings2 */
@@ -217,4 +245,43 @@ exports.aceAttribsToClasses = (hookName, context) => {
217
245
  if (context.key === 'heading') {
218
246
  return [`heading:${context.value}`];
219
247
  }
220
- };
248
+ };
249
+
250
+ /*
251
+ Plain paste integration: init if enabled and available.
252
+ */
253
+ function maybeInitPlainPaste(context) {
254
+ if (!plainPasteEnabled) return;
255
+ //const plainPaste = require('./plain_paste');
256
+ //plainPaste.ensure(context);
257
+ }
258
+
259
+ // Disable common rich-text keyboard shortcuts (Ctrl/Cmd+B/I/U, etc.)
260
+ function initShortcutBlocker(context) {
261
+ const padOuter = document.querySelector('iframe[name="ace_outer"]');
262
+ const padInner = padOuter?.contentDocument?.querySelector('iframe[name="ace_inner"]');
263
+ const innerDoc = padInner?.contentDocument;
264
+ if (!innerDoc) return;
265
+
266
+ const handler = (e) => {
267
+ const key = (e.key || '').toLowerCase();
268
+ const meta = e.metaKey || e.ctrlKey;
269
+ if (!meta) return;
270
+ // Block common formatting combos:
271
+ // b (bold), i (italic), u (underline), 5 (strike);
272
+ // Shift+L (bullet), Shift+N (numbered list), Shift+1 (heading), Shift+C (color).
273
+ const block =
274
+ ['b', 'i', 'u', '5'].includes(key) ||
275
+ (e.shiftKey && ['l', 'n', '1', 'c'].includes(key));
276
+ if (block) {
277
+ e.preventDefault();
278
+ e.stopPropagation();
279
+ return false;
280
+ }
281
+ };
282
+
283
+ if (!innerDoc._tocShortcutBlockerBound) {
284
+ innerDoc.addEventListener('keydown', handler, true);
285
+ innerDoc._tocShortcutBlockerBound = true;
286
+ }
287
+ }
@@ -0,0 +1,60 @@
1
+ // Force plain-text paste: intercept paste and insert text/plain only.
2
+ let bound = false;
3
+ let currentBody = null;
4
+ let currentHandler = null;
5
+
6
+ // Locate the inner editor window either from context or via DOM.
7
+ const getInnerWindow = (context) => {
8
+ if (context?.editorInfo?.ace_innerWin) return context.editorInfo.ace_innerWin;
9
+ const outer = document.querySelector('iframe[name="ace_outer"]');
10
+ const inner = outer?.contentDocument?.querySelector('iframe[name="ace_inner"]');
11
+ return inner?.contentWindow || null;
12
+ };
13
+
14
+ const install = (context) => {
15
+ if (bound) return;
16
+
17
+ const editor = context?.editorInfo?.editor;
18
+ const callWithAce =
19
+ editor && typeof editor.callWithAce === 'function'
20
+ ? editor.callWithAce.bind(editor)
21
+ : null;
22
+ const innerWin = getInnerWindow(context);
23
+ const body = innerWin?.document?.body;
24
+
25
+ if (!callWithAce || !body) return;
26
+
27
+ const handlePaste = (e) => {
28
+ const clipboard = e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
29
+ const text = clipboard
30
+ ? clipboard.getData('text/plain') || clipboard.getData('text')
31
+ : '';
32
+ if (!text) return;
33
+
34
+ e.preventDefault();
35
+ e.stopPropagation();
36
+ e.stopImmediatePropagation?.();
37
+ callWithAce((ace) => {
38
+ if (!ace || typeof ace.ace_getRep !== 'function' || typeof ace.ace_replaceRange !== 'function') return;
39
+ const rep = ace.ace_getRep();
40
+ if (!rep || !rep.selStart || !rep.selEnd) return;
41
+ ace.ace_replaceRange(rep.selStart, rep.selEnd, text);
42
+ }, 'plain-paste', true);
43
+ };
44
+
45
+ body.addEventListener('paste', handlePaste, true);
46
+ bound = true;
47
+ currentBody = body;
48
+ currentHandler = handlePaste;
49
+ };
50
+
51
+ exports.ensure = (context) => install(context);
52
+
53
+ exports.disable = () => {
54
+ if (bound && currentBody && currentHandler) {
55
+ currentBody.removeEventListener('paste', currentHandler, true);
56
+ }
57
+ bound = false;
58
+ currentBody = null;
59
+ currentHandler = null;
60
+ };
@@ -5,51 +5,75 @@
5
5
  href="https://garrettgman.github.io/rmarkdown/authoring_pandoc_markdown.html" target="_blank">Pandoc Markdown
6
6
  Authoring Guide</a></p>
7
7
  </p>
8
- <h3>Headings</h3>
9
- <pre><code># Heading 1
10
- ## Heading 2
11
- ### Heading 3
12
- ### Heading 3
13
- </code></pre>
14
- <h3>Styling text</h3>
15
- <pre><code>
16
- For a paragraph break, follow a line by two or more spaces.
17
8
 
18
- **bold**
19
- *italic*
20
- ~~strikethrough~~
9
+ <div class="cheat-grid">
10
+ <div class="cheat-block">
11
+ <h3>Headings</h3>
12
+ <ul class="cheat-lines">
13
+ <li># Heading 1</li>
14
+ <li>## Heading 2</li>
15
+ <li>### Heading 3</li>
16
+ </ul>
17
+ </div>
21
18
 
22
- `inline code`
19
+ <div class="cheat-block">
20
+ <h3>Styling</h3>
21
+ <ul class="cheat-lines">
22
+ <li>**bold**</li>
23
+ <li>*italic*</li>
24
+ <li>~~strikethrough~~</li>
25
+ <li>`inline code`</li>
26
+ </ul>
27
+ </div>
23
28
 
24
- ```
25
- code block
26
- ```
29
+ <div class="cheat-block">
30
+ <h3>Spacing</h3>
31
+ <ul class="cheat-lines">
32
+ <li>A line containing a row of three or more *, -, or _ characters (optionally separated by spaces) produces a horizontal rule: <br>
33
+ * * * *<br>
34
+ ---------------<br>
35
+ It is strongly recommended that horizontal rules be separated from surrounding text by blank lines. </li>
36
+ <li>Line break: end a line with two trailing spaces␠␠</li>
37
+ <li>Line break: end a line with backslash \</li>
38
+ </ul>
39
+ </div>
27
40
 
28
- > blockquote
29
- </code></pre>
30
- <h3>Lists</h3>
31
- <pre><code>
32
- - Unordered list
33
- 1. Ordered list
34
- </code></pre>
35
- <h3>Links & Images</h3>
36
- <pre><code>
37
- [link text](https://example.com)
38
- ![alt](img.jpg)
39
- ![alt](img.jpg){.class}
40
- </code></pre>
41
+ <div class="cheat-block">
42
+ <h3>Lists</h3>
43
+ <ul class="cheat-lines">
44
+ <li>- Unordered item</li>
45
+ <li>1. Ordered item</li>
46
+ </ul>
47
+ </div>
41
48
 
42
- <pre><code>
43
- horizontal rule
44
- ---
49
+ <div class="cheat-block">
50
+ <h3>Links & Images</h3>
51
+ <ul class="cheat-lines">
52
+ <li>[link text](https://example.com)</li>
53
+ <li>![alt](img.jpg)</li>
54
+ <li>![alt](img.jpg){.class}</li>
55
+ </ul>
56
+ </div>
45
57
 
46
- ::: intro
47
- This is the intro block.
48
- :::
58
+ <div class="cheat-block">
59
+ <h3>Footnotes</h3>
60
+ <ul class="cheat-lines">
61
+ <li>You can use inline footnotes: ^[All content here will be a footnote.]</li>
62
+ <li>Note: support for two-part Pandoc-style footnotes is sketchy.</li>
63
+ </ul>
64
+ </div>
49
65
 
50
- ::: main
51
- This is the main block.
52
- :::
53
- </code></pre>
66
+ <div class="cheat-block">
67
+ <h3>Blocks (Pandoc)</h3>
68
+ <ul class="cheat-lines">
69
+ <li>::: intro</li>
70
+ <li>This is the intro block.</li>
71
+ <li>:::</li>
72
+ <li>::: main</li>
73
+ <li>This is the main block.</li>
74
+ <li>:::</li>
75
+ </ul>
76
+ </div>
77
+ </div>
54
78
  </div>
55
- </div>
79
+ </div>
@@ -4,14 +4,14 @@
4
4
  <input type="checkbox" id="options-hideToc" />
5
5
  <label for="options-hideToc" aria-label="Hide TOC">Hide TOC</label>
6
6
  </p>
7
-
7
+
8
8
  <p>
9
- <input type="checkbox" id="options-hideButtons" />
10
- <label for="options-hideButtons" aria-label="Hide text styling buttons">Hide text styling buttons</label>
9
+ <input type="checkbox" id="options-styleHeadings" />
10
+ <label for="options-styleHeadings" aria-label="Enable/Disable styling of the markdown headings">Style Markdown headings</label>
11
11
  </p>
12
12
 
13
13
  <p>
14
- <input type="checkbox" id="options-styleHeadings" />
15
- <label for="options-styleHeadings" aria-label="Enable/Disable styling of the markdown headings">Style Markdown headings</label>
14
+ <input type="checkbox" id="options-plainPaste" />
15
+ <label for="options-plainPaste" aria-label="Force plain-text paste">Force plain-text paste</label>
16
16
  </p>
17
- </fieldset>
17
+ </fieldset>
@@ -0,0 +1,6 @@
1
+ <li data-key="ep_markdown_toc-toggle" data-type="button">
2
+ <a id="ep_markdown_toc-a" title="Toggle TOC">
3
+ <button id="markdown-toc-toggle" class="buttonicon buttonicon-insertunorderedlist" title="Toggle TOC"></button>
4
+ </a>
5
+ </li>
6
+ <li class="separator"></li>