├── LICENSE
├── README.md
├── favicon.png
├── index.html
├── writty.css
├── writty.js
├── writtyautosave.js
└── writtybottom.svg
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Carlos Yllobre
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Writty Open-source
2 |
3 | An open-source text editor that helps you focus on what matters.
4 | For more information or to install the Chrome extension, please visit [writtyapp.com](https://writtyapp.com/)
5 |
6 | **Version 1.4.2**
7 |
8 | Writty is a simple text editor built with:
9 |
10 | * Javascript
11 | * HTML
12 | * CSS
13 |
14 | ## Features
15 |
16 | * 5 Font Styles (Headline, Subheadline, Body, Caption and Quote)
17 | * Main Editor Functions (Bold, Italic, Underline, Lists}
18 | * Image Uploader or paste from clipboard
19 | * Autosave Session (LocalStorage)
20 | * Add URL
21 | * Export as PDF, HTML, Markdown and TXT
22 | * RTL Support
23 | * Autosave RTL preferences
24 | * Word and Character Counter
25 | * Light/Dark Mode
26 |
27 | ## Wishlist
28 |
29 | * Markdown view
30 | * HTML view
31 | * Image resizing
32 |
33 | ## Contributors
34 |
35 | Big Thanks to:
36 | [@GraemeFulton](https://github.com/GraemeFulton), [@raulriera](https://github.com/raulriera), [@twanmulder](https://github.com/twanmulder), [@filiptronicek](https://github.com/filiptronicek), [@kenanchristian](https://github.com/kenanchristian), [@phosph](https://github.com/phosph), [@morpheus7CS](https://github.com/morpheus7CS) and [@Lewis-Marshall](https://github.com/Lewis-Marshall) for helping me bringing Writty to the next level.
37 |
38 | ## License
39 | [MIT](https://opensource.org/licenses/MIT) © [Carlos Yllobre](https://iamcharlie.design/)
40 |
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Writty/Open/ce2a13fb0b4497569059caa8030016c460159849/favicon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Writty
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
81 |
91 | 0
92 |
93 |
94 |
95 |
96 |
Start writing...✏️
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |

105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/writty.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | color: var(--text);
4 | font-size: 21px;
5 | }
6 |
7 | h1 {
8 | font-size: 28px;
9 | font-weight: bold;
10 | display: block;
11 | margin-block-start: 16px;
12 | margin-block-end: 16px;
13 | margin-inline-start: 0px;
14 | margin-inline-end: 0px;
15 | }
16 |
17 | h2 {
18 | font-size: 24px;
19 | font-weight: bold;
20 | display: block;
21 | margin-block-start: 14px;
22 | margin-block-end: 14px;
23 | margin-inline-start: 0px;
24 | margin-inline-end: 0px;
25 | }
26 |
27 | p {
28 | font-size: 21px;
29 | display: block;
30 | margin-block-start: 12px;
31 | margin-block-end: 12px;
32 | margin-inline-start: 0px;
33 | margin-inline-end: 0px;
34 | }
35 |
36 | h5 {
37 | font-size: 18px;
38 | font-weight: 100;
39 | display: block;
40 | margin-block-start: -3px;
41 | margin-block-end: 10px;
42 | margin-inline-start: 0px;
43 | margin-inline-end: 0px;
44 | }
45 |
46 | blockquote {
47 | font-size: 21px;
48 | color: var(--text);
49 | font-style: italic;
50 | display: block;
51 | padding: 17px;
52 | margin-block-start: 12px;
53 | margin-block-end: 12px;
54 | margin-inline-start: 0px;
55 | margin-inline-end: 0px;
56 | }
57 |
58 |
59 | pre {
60 | white-space: pre-wrap;
61 | background-color: var(--darker-background);
62 | color: var(--text);
63 | padding: 12px;
64 | border-radius: 5px;
65 | font-size: 16px;
66 | }
67 |
68 | .imgView {
69 | width: fit-content;
70 | padding: 20px 0px 20px 0px;
71 | }
72 |
73 | :focus {
74 | outline: 1px solid var(--hightlight);
75 | }
76 |
77 | .topbar {
78 | overflow: hidden;
79 | background: var(--background);
80 | position: fixed;
81 | top: 0;
82 | width: 100%;
83 | font-size: 18px;
84 | z-index: 10;
85 | }
86 |
87 | .topbar-row {
88 | max-width: 950px;
89 | margin: auto;
90 | display: block;
91 | text-align: right;
92 | }
93 |
94 | .topbar-button {
95 | font-family: 'ubuntu', sans-serif;
96 | color: var(--text);
97 | font-size: 15px;
98 | border: 0;
99 | padding: 20px 24px 30px 21px;
100 | cursor: pointer;
101 | background: var(--background);
102 | float: right;
103 | height: 40px;
104 | margin-top: 5px;
105 | }
106 |
107 | .topbar-button:hover {
108 | outline: none;
109 | color: #fcaf12;
110 | }
111 |
112 | .topbar-button.active {
113 | color: #fcb312;
114 | }
115 |
116 | /* Switch */
117 |
118 | .switch {
119 | position: relative;
120 | display: inline-block;
121 | width: 45px;
122 | height: 24px;
123 | margin: 16px;
124 | float: right;
125 | margin: 20px 10px 20px 15px;
126 | }
127 |
128 | .switch input {
129 | display: none;
130 | }
131 |
132 | .switch-slider {
133 | position: absolute;
134 | cursor: pointer;
135 | top: 0;
136 | left: 0;
137 | right: 0;
138 | bottom: 0;
139 | background-color: #ccc;
140 | -webkit-transition: .4s;
141 | transition: .4s;
142 | border-radius: 34px;
143 | }
144 |
145 | .switch-slider:before {
146 | position: absolute;
147 | content: "";
148 | height: 24px;
149 | width: 24px;
150 | left: -4px;
151 | bottom: 0px;
152 | background-color: #f3f3f3;
153 | -webkit-transition: .4s;
154 | transition: .4s;
155 | border-radius: 50%;
156 | -webkit-box-shadow: 2px 0px 3px 1px rgba(0, 0, 0, 0.2);
157 | box-shadow: 2px 0px 3px 1px rgba(0, 0, 0, 0.2);
158 | }
159 |
160 | input:checked + .switch-slider {
161 | background-color: #fcaf12;
162 | }
163 |
164 | input:focus + .witch-slider {
165 | box-shadow: 0 0 1px #fcaf12;
166 | }
167 |
168 | input:checked + .switch-slider:before {
169 | -webkit-transform: translateX(26px);
170 | -ms-transform: translateX(26px);
171 | transform: translateX(26px);
172 | }
173 |
174 | .sun {
175 | margin: 12px 4px 2px 2px;
176 | color: #d27b18;
177 | font-size: 15px;
178 | vertical-align: -.1em;
179 | }
180 | .moon{
181 | margin: 12px 6px 2px 0px;
182 | color: #ababab;
183 | font-size: 15px;
184 | vertical-align: -.1em;
185 | }
186 |
187 | /* Popup */
188 |
189 | .popup-button {
190 | font-family: 'ubuntu', sans-serif;
191 | color: var(--text);
192 | font-size: 15px;
193 | width: 135px;
194 | border: 0;
195 | padding: 12px 0px 3px 0px;
196 | cursor: pointer;
197 | text-align: left;
198 | display: flex;
199 | background: var(--background);
200 | }
201 |
202 | .popup-button:hover {
203 | outline: none;
204 | color: #fcaf12;
205 | }
206 |
207 | .popup-button.active {
208 | background: #fcb312;
209 | }
210 |
211 | /* Toolbar */
212 |
213 | .toolbar-button {
214 | font-size: 16px;
215 | border: 0px;
216 | width: 48px;
217 | padding: 24px 15px 0px 15px;
218 | margin: 0;
219 | cursor: pointer;
220 | color: var(--text);
221 | background: var(--background);
222 | }
223 |
224 | .toolbar-button:hover {
225 | outline: none;
226 | color: #fcaf12;
227 | }
228 |
229 | .toolbar-button.active {
230 | color: #fcaf12
231 | }
232 |
233 | .last {
234 | font-size: 16px;
235 | border: 0px;
236 | width: 48px;
237 | padding: 24px 15px 24px 15px;
238 | margin: 0;
239 | cursor: pointer;
240 | background: var(--background);
241 | }
242 |
243 | .toolbar {
244 | min-height: 20px;
245 | background: var(--background);
246 | width: 50px;
247 | left: 60px;
248 | position: fixed;
249 | -webkit-box-shadow: 2px 2px 5px 2px rgba(0, 0, 0, 0.2);
250 | box-shadow: 2px 2px 5px 2px rgba(0, 0, 0, 0.2);
251 | border-radius: 4px;
252 | margin-top: 63px;
253 | z-index: 10;
254 | }
255 |
256 | .tools {
257 | display: -webkit-box;
258 | display: -ms-flexbox;
259 | display: flex;
260 | border-right: 1px solid lightgray
261 | }
262 |
263 | /* Popup */
264 |
265 | .popup {
266 | display: inline-block;
267 | position: relative
268 | }
269 |
270 | .popup:hover .popup-window {
271 | color: #fcaf12
272 | }
273 |
274 | .container .popup:hover .popup-window {
275 | display: block
276 | }
277 |
278 | .popup-window {
279 | z-index: 1;
280 | font-family: 'Buenard', sans-serif;
281 | display: none;
282 | position: absolute;
283 | background-color: var(--background);
284 | border-radius: 4px;
285 | -webkit-box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
286 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
287 | margin: -30px 45px;
288 | padding: 3px 15px 10px 15px;
289 | outline: 0;
290 | }
291 |
292 | .container .popup .popup-window.hover-popup {
293 | display: block
294 | }
295 |
296 | .popup-item {
297 | cursor: pointer;
298 | color: var(--text);
299 | cursor: pointer;
300 | font-family: 'Buenard', sans-serif;
301 | background: var(--background);
302 | border: 0;
303 | width: 100%;
304 | padding: 6px 3px 6px 3px;
305 | }
306 |
307 | .Heading {
308 | font-size: 28px;
309 | font-weight: bold;
310 | }
311 |
312 | .Subheading {
313 | font-size: 24px;
314 | font-weight: bold;
315 | }
316 |
317 | .Body {
318 | font-size: 21px;
319 | }
320 |
321 | .Caption {
322 | font-size: 18px;
323 | }
324 |
325 | .popup-item i {
326 | min-width: 100px;
327 | padding: auto 40px
328 | }
329 |
330 | .popup-item:hover {
331 | color: #fcaf12;
332 | }
333 |
334 | .popup-item.active {
335 | color: #fcaf12;
336 | }
337 |
338 | .popup-window .url-input {
339 | padding: 5px display: -webkit-box;
340 | display: -ms-flexbox;
341 | display: flex;
342 | -webkit-box-orient: vertical;
343 | -webkit-box-direction: normal;
344 | -ms-flex-direction: column;
345 | flex-direction: column
346 | }
347 |
348 | .popup-window .url-input input {
349 | display: block;
350 | outline: none;
351 | border: 0;
352 | border-bottom: 2px solid #fcaf12;
353 | padding: 24px 30px 5px 0px;
354 | font-size: 14px;
355 | color: var(--text);
356 | background: var(--background);
357 | }
358 |
359 | /* Container, Editor and Content */
360 |
361 | .container {
362 | display: inline-block;
363 | position: relative;
364 | width: 100%;
365 | }
366 |
367 | .content {
368 | font-family: 'Buenard', sans-serif;
369 | font-size: 21px;
370 | position: relative;
371 | display: inline-block;
372 | width: 100%;
373 | min-height: 900px;
374 | padding: 10px;
375 | padding-top: 0;
376 | margin-top: -4px;
377 | overflow-wrap: break-word;
378 | background: var(--background);
379 | color: var(--text);
380 | border: 0;
381 | border-radius: 4px;
382 | outline: 0px;
383 | }
384 |
385 | .editor {
386 | font-family: 'Buenard', sans-serif;
387 | display: block;
388 | max-width: 900px;
389 | height: 500px;
390 | overflow-y: auto;
391 | overflow-x: hidden;
392 | margin: auto;
393 | padding: 0px 50px 50px 150px;
394 | margin-top: 64px;
395 | }
396 |
397 | [contenteditable] {
398 | background: var(--background);
399 | color: var(--text);
400 | }
401 |
402 | #counter {
403 | font-family: 'ubuntu', sans-serif;
404 | position: absolute;
405 | padding: 2px 5px 0 5px;
406 | font-size: 15px;
407 | color: #999;
408 | font-weight: 100;
409 | right: 0;
410 | bottom: -40px;
411 | width: 42px;
412 | text-align: center;
413 | cursor: pointer;
414 | }
415 |
416 | .bottom-bar {
417 | position: fixed;
418 | float: left;
419 | background: var(--background);
420 | width: 100%;
421 | padding: 15px 0 5px 0;
422 | font-size: 15px;
423 | text-align: right;
424 | bottom: 0;
425 | }
426 |
427 | .bottom-row {
428 | max-width: 950px;
429 | margin: auto;
430 | display: block;
431 | }
432 |
433 | .logo{
434 | opacity: var(--alpha);
435 | padding-right: 10px;
436 | }
437 |
438 | /* Variables */
439 |
440 | :root {
441 | --background: #fff;
442 | --darker-background: #eee;
443 | --text: #33363C;
444 | --hightlight: #24242b;
445 | --alpha: 0.4;
446 | }
447 |
448 | .dark-theme {
449 | --background: #33363C;
450 | --darker-background: #1111136F;
451 | --text: #d3d3d3;
452 | --hightlight: #fcaf12;
453 | --alpha: 1;
454 | background: var(--background);
455 |
456 | }
457 |
458 | /* Break Points */
459 |
460 | @media only screen and (max-width: 680px) {
461 |
462 | .toolbar {
463 | left: 0px;
464 | }
465 | .editor{
466 | padding: 50px 20px 50px 70px;
467 | }
468 |
469 | }
470 |
471 | /* SimpleBar custom styles */
472 |
473 | .simplebar-scrollbar::before {
474 | background-color: var(--text);
475 | }
476 |
477 |
478 | /* Hide file input */
479 | input[type="file"]{
480 | display: none;
481 | }
482 |
--------------------------------------------------------------------------------
/writty.js:
--------------------------------------------------------------------------------
1 | window.onload = function () {
2 | trigger();
3 | setupEventListenerForThemeSwitch();
4 | setupEventListenerForFileImport();
5 | setupEventListenerForCounter();
6 | initialCheckForTheme();
7 | initialCheckForCounter();
8 | };
9 | // Styling: Headings, Bold, Italic, Underline, Quotes, Lists //
10 |
11 | document.querySelectorAll('[data-edit]').forEach(btn =>
12 | btn.addEventListener('click', edit)
13 | );
14 |
15 | function edit(ev) {
16 | ev.preventDefault();
17 | const cmd_val = this.getAttribute('data-edit').split(':');
18 | document.execCommand(cmd_val[0], false, cmd_val[1]);
19 | }
20 |
21 | // Functions: Links and Images //
22 |
23 | const btns = document.querySelectorAll('[data-edt]');
24 |
25 | function Space(aID) {
26 |
27 | return document.getElementById(aID);
28 | }
29 |
30 | function trigger() {
31 | let space = document.getElementById('content');
32 | space.designMode = 'on';
33 | space.addEventListener('mouseup', agent);
34 | space.addEventListener('keyup', agent);
35 |
36 |
37 | //Buttons Commands //
38 |
39 | for (let b of btns) {
40 | b.addEventListener('click', () => {
41 | run(b.dataset.edt, b, b.dataset.param);
42 | document.getElementById('content').focus();
43 | document.getElementById('content').focus();
44 | });
45 | }
46 |
47 | }
48 |
49 | // Insert Link //
50 |
51 | function run(cmd, ele, value = null) {
52 | let status = document.execCommand(cmd, false, value);
53 | if (!status) {
54 | switch (cmd) {
55 | case 'insertLink':
56 | value = prompt('Enter url');
57 | if (value.slice(0, 4) != 'http') {
58 | value = 'http://' + value;
59 | }
60 | document.execCommand('createLink', false, value);
61 |
62 | // Overrides inherited attribute "contenteditable" from parent
63 | // which would otherwise prevent anchor tag from being interacted with.
64 | atag = document.getSelection().focusNode.parentNode;
65 | atag.setAttribute("contenteditable", "false");
66 |
67 | break;
68 | }
69 | }
70 | }
71 |
72 |
73 | // Insert Image //
74 |
75 | if (window.File && window.FileList && window.FileReader) {
76 | const filesInput = document.getElementById("imageUpload");
77 |
78 | filesInput.addEventListener("change", function (event) {
79 |
80 | const files = event.target.files; //FileList object
81 | const output = document.getElementById("content");
82 |
83 | for (let i = 0; i < files.length; i++) {
84 | const file = files[i];
85 |
86 | //Only pics
87 | if (!file.type.match('image'))
88 | continue;
89 |
90 | const picReader = new FileReader();
91 |
92 | picReader.addEventListener("load", (event) => {
93 |
94 | const picSrc = event.target.result;
95 |
96 | const imgThumbnailElem = "
Caption
";
98 |
99 | output.innerHTML = output.innerHTML + imgThumbnailElem;
100 |
101 | });
102 |
103 | //Read the image
104 | picReader.readAsDataURL(file);
105 | }
106 |
107 | });
108 | } else {
109 | alert("Your browser does not support File API");
110 | }
111 |
112 |
113 | // Word Counter //
114 |
115 | function agent() {
116 | let currentCounterPreference = localStorage.getItem("counter-preference");
117 |
118 | var counterTotal;
119 |
120 | switch(currentCounterPreference) {
121 | case "character-count":
122 | counterTotal = characterCount(document.getElementById('content').innerText);
123 | break;
124 | case "word-count":
125 | counterTotal = wordCount(document.getElementById('content').innerText);
126 | break;
127 | }
128 |
129 | document.getElementById('counter').innerText = counterTotal;
130 | }
131 |
132 | // Count All Characters //
133 | function characterCount(str) {
134 | return str.length;
135 | }
136 |
137 | // Count Words //
138 | function wordCount(str) {
139 | return str.match(/\b[-?(\w+)?]+\b/gi).length;
140 | }
141 |
142 | // Check For Counter //
143 | function initialCheckForCounter() {
144 | let counterPreference = "character-count";
145 |
146 | // Local storage is used to override OS theme settings
147 | if(localStorage.getItem("counter-preference")){
148 | if(localStorage.getItem("counter-preference") === "word-count"){
149 | counterPreference = "word-count";
150 | }
151 | }
152 |
153 | localStorage.setItem("counter-preference", counterPreference);
154 | }
155 |
156 | // Toggle Current Counter //
157 | function toggleCounterPreference() {
158 | let currentCounterPreference = localStorage.getItem("counter-preference");
159 |
160 | switch(currentCounterPreference) {
161 | case "character-count":
162 | localStorage.setItem("counter-preference", "word-count");
163 | break;
164 | case "word-count":
165 | localStorage.setItem("counter-preference", "character-count");
166 | break;
167 | }
168 |
169 | agent();
170 | }
171 |
172 |
173 | // Counter Switch //
174 | function setupEventListenerForCounter() {
175 | const counter = document.getElementById("counter");
176 | counter.addEventListener("click", function() {
177 | toggleCounterPreference();
178 | });
179 | }
180 |
181 |
182 | // Theme Switch //
183 |
184 | function setupEventListenerForThemeSwitch() {
185 | const themeSwitch = document.getElementById("theme-switch");
186 | themeSwitch.addEventListener("click", function() {
187 | toggleThemePreference();
188 | });
189 | }
190 |
191 | // File Import //
192 | function triggerImportFile() {
193 | const fileInput = document.getElementById("import-file")
194 | fileInput.click()
195 | }
196 |
197 | function setupEventListenerForFileImport() {
198 | const fileInput = document.getElementById("import-file")
199 | fileInput.addEventListener("change", (event) => {
200 | const file = event.currentTarget.files[0]
201 | if(!file){ return }
202 | const extension = file.name.split(".").pop()
203 |
204 | if(extension === "html" || "md"){
205 | const reader = new FileReader()
206 | reader.onload = function(){
207 | importContent(extension, reader.result)
208 | }
209 |
210 | reader.readAsText(file)
211 | } else {
212 | alert("File type is not supported for import")
213 | }
214 | })
215 | }
216 |
217 | function downloadContent(type) {
218 | let editorContent = ''
219 | if (type === 'txt') {
220 | editorContent = document.getElementById('content').textContent;
221 | } else if(type === 'md') {
222 | const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', emDelimiter: '*' });
223 | editorContent = turndownService.turndown(document.getElementById('content').innerHTML);
224 | } else {
225 | editorContent =`
226 |
227 |
228 |
229 |
230 |
231 | Writty
232 |
233 |
234 | ${document.getElementById('content').innerHTML}
235 |
236 |
237 | `
238 | }
239 |
240 | const linkElement = document.createElement("a")
241 | linkElement.setAttribute("download", `writty.${type}`)
242 | linkElement.setAttribute("href", 'data:text/plain;charset=utf-8,' + encodeURIComponent(editorContent))
243 | linkElement.click()
244 |
245 | document.body.removeChild(linkElement);
246 | }
247 |
248 | function importContent(fileExtension, content) {
249 | const editorElement = document.getElementById('content')
250 | if(fileExtension === 'html'){
251 | const sanitizedContent = HtmlSanitizer.SanitizeHtml(content)
252 | const tempElement = document.createElement("html")
253 | tempElement.innerHTML = sanitizedContent
254 | editorElement.innerHTML = tempElement.querySelector("body").innerHTML
255 | } else if(fileExtension === "md") {
256 | const converter = new showdown.Converter()
257 | const html = converter.makeHtml(content)
258 | editorElement.innerHTML = html
259 | } else {
260 | alert("Import only supports Markdown & HTML File")
261 | }
262 |
263 | agent()
264 | }
265 |
266 | // Toggle RTL //
267 |
268 | function toggleRTL() {
269 | const editorElement = document.querySelector("#editor")
270 | const currentDir = editorElement.getAttribute("dir")
271 | if (!currentDir || currentDir === "ltr") {
272 | editorElement.setAttribute("dir", "rtl")
273 | } else {
274 | editorElement.setAttribute("dir", "ltr")
275 | } {
276 | var nav = document.querySelector('.topbar-button');
277 | nav.classList.toggle('active');
278 | e.preventDefault();
279 | }
280 | }
281 |
282 | // Check for theme //
283 |
284 | function initialCheckForTheme() {
285 | // Default to light-theme
286 | let themePreference = "light-theme";
287 |
288 |
289 | // Local storage is used to override OS theme settings
290 | if(localStorage.getItem("theme-preference")){
291 | if(localStorage.getItem("theme-preference") === "dark-theme"){
292 | themePreference = "dark-theme";
293 | }
294 | } else if(!window.matchMedia) {
295 | // matchMedia method not supported
296 | return false;
297 | } else if(window.matchMedia("(prefers-color-scheme: dark)").matches) {
298 | // OS theme setting detected as dark
299 | themePreference = "dark-theme";
300 | }
301 |
302 | if (themePreference === "dark-theme") {
303 | const themeSwitch = document.getElementById("theme-switch");
304 | themeSwitch.checked = true;
305 | }
306 |
307 | localStorage.setItem("theme-preference", themePreference);
308 | document.body.classList.add(themePreference);
309 | }
310 |
311 | // Toggle current theme //
312 |
313 | function toggleThemePreference() {
314 | let currentThemePreference = localStorage.getItem("theme-preference");
315 |
316 | switch(currentThemePreference) {
317 | case "light-theme":
318 | localStorage.setItem("theme-preference", "dark-theme");
319 |
320 | document.body.classList.remove("light-theme");
321 | document.body.classList.add("dark-theme");
322 | break;
323 | case "dark-theme":
324 | localStorage.setItem("theme-preference", "light-theme");
325 |
326 | document.body.classList.remove("dark-theme");
327 | document.body.classList.add("light-theme");
328 | break;
329 | }
330 | }
331 |
332 | // Paste plain text //
333 |
334 | const ce = document.querySelector('[contenteditable]');
335 | ce.addEventListener('paste', function (e) {
336 | e.preventDefault();
337 | const text = e.clipboardData.getData('text/plain');
338 | document.execCommand('insertText', false, text);
339 | });
340 |
341 | // Paste image //
342 |
343 | document.getElementById('content').addEventListener("paste", (event) => {
344 | var clipboardData = event.clipboardData;
345 | clipboardData.types.forEach((type, i) => {
346 | const fileType = clipboardData.items[i].type;
347 | if (fileType.match(/image.*/)) {
348 | const file = clipboardData.items[i].getAsFile();
349 | const reader = new FileReader();
350 | reader.onload = function (evt) {
351 | const dataURL = evt.target.result;
352 | const img = document.createElement("img");
353 | img.src = dataURL;
354 | document.execCommand('insertHTML', true, img.outerHTML);
355 | };
356 | reader.readAsDataURL(file);
357 | }
358 | })
359 | });
360 |
--------------------------------------------------------------------------------
/writtyautosave.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", function() {
2 | AutoSave.start();
3 | });
4 |
5 |
6 | const AutoSave = (function () {
7 |
8 | const getEditorElement = () => document.querySelector("#editor")
9 |
10 | let timer = null;
11 |
12 | //Save to local storage //
13 |
14 | function save() {
15 |
16 | const editorContent = document.getElementById('content').innerHTML;
17 |
18 | if (editorContent) {
19 | localStorage.setItem('AutoSave' + document.location, editorContent);
20 | }
21 |
22 |
23 | const dir = getEditorElement().getAttribute("dir")
24 | localStorage.setItem('dirIsRtl', dir === "rtl" );
25 | }
26 |
27 | //Load from local storage //
28 |
29 | function restore() {
30 |
31 | //get the content from local storage
32 | const savedContent = localStorage.getItem('AutoSave' + document.location);
33 |
34 | //if it found some
35 | if (savedContent) {
36 | //grab the editor
37 | document.getElementById('content').innerHTML =savedContent;
38 |
39 | }
40 |
41 | const dirIsRtl = localStorage.getItem('dirIsRtl');
42 | getEditorElement().setAttribute("dir", JSON.parse(dirIsRtl) ? "rtl" : "ltr")
43 | }
44 |
45 | return {
46 |
47 | // Start Autosave function triggered in line 2 //
48 |
49 | start: function () {
50 |
51 | const editor = document.getElementById('content');
52 |
53 | if (editor)
54 | restore();
55 |
56 | if (timer != null) {
57 | clearInterval(timer);
58 | timer = null;
59 | }
60 |
61 | timer = setInterval(save, 2000);
62 | },
63 |
64 | stop: function () {
65 |
66 | if (timer) {
67 | clearInterval(timer);
68 | timer = null;
69 | }
70 | }
71 | };
72 |
73 |
74 |
75 | }());
76 |
77 | // Clear All //
78 |
79 | function clearStorage() {
80 | if (confirm("Are you sure you want to create a new text? This will erase all the content.")) {
81 | window.localStorage.clear();
82 | document.getElementById("content").innerHTML= "Once upon a time...✏️
";
83 | location.reload();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/writtybottom.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------