]>
Commit | Line | Data |
---|---|---|
feeace6e | 1 | class Editor { |
7b5802bb MT |
2 | constructor(parent) { |
3 | this.parent = $(parent); | |
4 | this.data = this.parent.data(); | |
7b1f068c | 5 | |
7b5802bb | 6 | // Hide the preview here |
7b1f068c MT |
7 | this.preview = $("#preview"); |
8 | this.preview.hide(); | |
feeace6e | 9 | |
7b5802bb MT |
10 | // Get the textarea |
11 | this.textarea = this.parent.find("textarea"); | |
feeace6e | 12 | |
7b5802bb MT |
13 | // Initialise selection |
14 | this.selection = { | |
15 | start : 0, | |
16 | end : 0, | |
17 | text : "", | |
18 | length : 0, | |
19 | }; | |
8eff1fbe | 20 | |
7b5802bb MT |
21 | // Make the textarea magic |
22 | this.activateTextArea(); | |
feeace6e | 23 | |
7b5802bb MT |
24 | // Bind all keys |
25 | this.bindKeys(); | |
feeace6e | 26 | |
7b5802bb MT |
27 | // Change timer for preview |
28 | var update = null; | |
7b1f068c | 29 | |
7b5802bb | 30 | console.log("Editor initialised for " + this.parent); |
717811a7 | 31 | |
7b5802bb MT |
32 | // Set focus on the textarea |
33 | this.textarea.focus(); | |
34 | } | |
feeace6e | 35 | |
7b5802bb MT |
36 | activateTextArea() { |
37 | var editor = this; | |
feeace6e | 38 | |
7b5802bb MT |
39 | // Render preview when content has changed |
40 | this.textarea.on("keyup change", function(e) { | |
41 | if (editor.update) | |
42 | clearTimeout(editor.update); | |
7b1f068c | 43 | |
7b5802bb | 44 | var content = editor.textarea.val(); |
7b1f068c MT |
45 | |
46 | // If the field is all empty, we will hide it | |
47 | if (content) | |
48 | editor.preview.show(); | |
49 | else | |
50 | editor.preview.hide(); | |
51 | ||
52 | // Go into update mode | |
53 | editor.preview.addClass("updating"); | |
54 | ||
7b5802bb | 55 | // Render content and show it |
7b1f068c MT |
56 | editor.update = setTimeout(function() { |
57 | var c = $("#preview-content"); | |
58 | ||
59 | $.post(editor.data.render_url, { content : content }, | |
60 | function(data) { | |
61 | c.html(data); | |
62 | ||
63 | // Update finished | |
64 | editor.preview.removeClass("updating"); | |
65 | } | |
66 | ); | |
67 | }, 750); | |
7b5802bb MT |
68 | }); |
69 | ||
70 | // Remember any selected text | |
71 | this.textarea.on("select keyup click", function(e) { | |
72 | // Ignore any keyboard shortcuts | |
73 | if (e.ctrlKey) | |
74 | return; | |
75 | ||
76 | // Save selected text | |
77 | editor.selection = { | |
78 | start : this.selectionStart, | |
79 | end : this.selectionEnd, | |
80 | text : this.value.slice(this.selectionStart, this.selectionEnd), | |
81 | length: this.selectionEnd - this.selectionStart, | |
82 | }; | |
83 | ||
84 | console.debug("Something got selected:"); | |
85 | console.debug(editor.selection); | |
86 | }) | |
87 | ||
88 | // Bind keyboard shortcuts | |
89 | this.textarea.on("keyup", function(e) { | |
90 | // If Ctrl wasn't pressed this isn't for us | |
91 | if (!e.ctrlKey) | |
92 | return; | |
93 | ||
94 | switch (e.which) { | |
95 | // B - Bold | |
96 | case 66: | |
97 | editor.bold(); | |
98 | break; | |
99 | ||
100 | // I - Italic | |
101 | case 73: | |
102 | editor.italic(); | |
103 | break; | |
104 | ||
105 | // C - Code | |
106 | case 67: | |
107 | editor.code(); | |
108 | break; | |
109 | ||
110 | // H - Headline | |
111 | case 72: | |
112 | editor.headline(); | |
113 | break; | |
114 | ||
115 | // L - Link | |
116 | case 76: | |
117 | editor.link(); | |
118 | break; | |
119 | } | |
120 | }); | |
121 | } | |
122 | ||
123 | bindKeys() { | |
124 | // Typography | |
125 | this.parent.find("#bold").click(this.bold.bind(this)); | |
126 | this.parent.find("#italic").click(this.italic.bind(this)); | |
127 | this.parent.find("#code").click(this.code.bind(this)); | |
128 | ||
129 | // Headlines | |
130 | this.parent.find("#headline").click(this.headline.bind(this)); | |
131 | this.parent.find("#headline-down").click(this.headline_down.bind(this)); | |
132 | this.parent.find("#headline-up").click(this.headline_up.bind(this)); | |
133 | ||
134 | // Links | |
135 | this.parent.find("#link").click(this.link.bind(this)); | |
136 | } | |
137 | ||
138 | // Functions to modify the text | |
139 | ||
140 | replaceSelection(replacement) { | |
141 | // Get the DOM element | |
142 | var textarea = this.textarea.get(0); | |
143 | ||
144 | // Write text to textarea and move the cursor to the end | |
145 | textarea.setRangeText(replacement, | |
146 | this.selection.start, this.selection.end, "end"); | |
147 | } | |
148 | ||
149 | insertAtCursor(insertion) { | |
150 | this.replaceSelection(insertion); | |
151 | } | |
152 | ||
153 | bold() { | |
154 | console.debug("Converting into bold: " + this.selection.text); | |
155 | this.replaceSelection("**" + this.selection.text + "**"); | |
156 | } | |
157 | ||
158 | italic() { | |
159 | console.debug("Converting into italic: " + this.selection.text); | |
160 | this.replaceSelection("*" + this.selection.text + "*"); | |
161 | } | |
162 | ||
163 | code() { | |
164 | var multiline = this.selection.text.indexOf("\n"); | |
165 | ||
166 | if (multiline >= 0) { | |
167 | this.replaceSelection("```\n" + this.selection.text + "\n```\n\n"); | |
168 | } else { | |
169 | this.replaceSelection("`" + this.selection.text + "`"); | |
170 | } | |
171 | } | |
172 | ||
173 | link() { | |
174 | // Handle URLs | |
175 | if (this.selection.text.startsWith("https://") || this.selection.text.startsWith("http://")) { | |
176 | this.replaceSelection("[" + this.selection.text + "](" + this.selection.text + ")"); | |
177 | // Handle selected text | |
178 | } else { | |
179 | this.replaceSelection("[" + this.selection.text + "]()") | |
180 | } | |
181 | } | |
182 | ||
183 | // Headlines | |
184 | ||
185 | findLevel() { | |
186 | // Get all text between start and cursor position | |
187 | var text = this.textarea.val().slice(0, this.selection.start); | |
188 | ||
189 | // Split it in lines and reverse | |
190 | var lines = text.split("\n"); | |
191 | lines.reverse(); | |
192 | ||
193 | for (var line of lines) { | |
194 | console.debug(line); | |
195 | ||
196 | // Return the number of # found in the nearest headline | |
197 | var match = line.match("^(#+)"); | |
198 | if (match) { | |
199 | return match[1].length; | |
200 | } | |
201 | } | |
202 | ||
203 | // If nothing was found, we are on level one | |
204 | return 1; | |
205 | } | |
206 | ||
207 | insertHeadline(offset) { | |
208 | // Find level of headlines | |
209 | var level = Math.max(this.findLevel() + offset, 1); | |
210 | ||
211 | console.debug("Adding headline (" + level + ")"); | |
212 | var headline = "#".repeat(level); | |
213 | ||
214 | if (this.selection.length == 0) { | |
215 | headline += " ..."; | |
216 | } else { | |
217 | // Add some space if we don't have any, yet | |
218 | if (!this.selection.text.startsWith(" ")) | |
219 | headline += " "; | |
220 | ||
221 | headline += this.selection.text + "\n\n"; | |
222 | } | |
223 | ||
224 | this.replaceSelection(headline); | |
225 | } | |
226 | ||
227 | headline() { | |
228 | return this.insertHeadline(0); | |
229 | } | |
230 | ||
231 | headline_down() { | |
232 | return this.insertHeadline(1); | |
233 | } | |
234 | ||
235 | headline_up() { | |
236 | return this.insertHeadline(-1); | |
237 | } | |
feeace6e MT |
238 | } |
239 | ||
240 | $(document).ready(function() { | |
7b5802bb MT |
241 | // Initialise all editors |
242 | $(".editor").each(function(i, e) { | |
243 | new Editor(e); | |
244 | }); | |
feeace6e | 245 | }); |