1
0
Fork
You've already forked whatyougetsy
0
Tiny WYSIWYG editor without dependencies and with minimal UI
  • JavaScript 90.2%
  • Python 5.3%
  • PHP 4.5%
Find a file
2023年09月24日 02:17:04 +00:00
demo-strict.php initial ~working version 2023年09月19日 16:15:54 +02:00
demo-strict.py initial ~working version 2023年09月19日 16:15:54 +02:00
README.md add link to another implementation that might be helpful for future work 2023年09月24日 02:17:04 +00:00
whatyougetsy-replacemorenewlines.js initial ~working version 2023年09月19日 16:15:54 +02:00
whatyougetsy.js initial ~working version 2023年09月19日 16:15:54 +02:00
whatyougetsy.png readme: add screenshot, add further limitations, proofreading 2023年09月19日 22:02:37 +02:00

Whatyougetsy

A tiny WYSIWYG editor without dependencies and with minimal UI

Editable area with demo text showing different markup options, as well as the toolbar offering the same markup options for currently selected text

Quick start

Add class=whatyougetsy to an element such as a div, load the whatyougetsy.js file, and it will turn the element into a text editor.

<div class=whatyougetsy>You can <b>edit</b> this text.</div>
<script src='whatyougetsy.js'></script>

To obtain the user's input, simply use the innerHTML property of the element.

Form usage

If you would rather have this as part of a <form>, no problem:

<form>
	<div class=whatyougetsy data-form-field-name=helloworld></div>
	<input type=submit>
</form>
<script src='whatyougetsy.js'></script>

This will:

  1. Add a hidden <textarea> element to your form, right after the editable content element
  2. Update the textarea contents when the submit event of the form fires (it is not constantly serializing the HTML tree and updating the textarea unnecessarily)

You can also manually call $('.whatyougetsy').updateFormField() whenever you want, for example to then validate the entered text.

Styling

By default, it adds some basic rules such as giving the toolbar a nontransparent background and a border, adding a bit of padding to the editable content wrapper, and setting a reasonable width and height similar to a typical textarea.

You can override this with !important, here is an example for all classes used by Whatyougetsy:

.whatyougetsy {
	width: 100% !important;
}
.whatyougetsyToolbar {
	background-color: red !important;
}
.whatyougetsyToolbarButton {
	margin-left: 10px !important;
}

But using !important is kind of ugly, even if there aren't that many rules to override. To remove the default styles, set the data property default-styles=no on the script where Whatyougetsy is included:

<script src='whatyougetsy.js' data-default-styles=no></script>

Only one toolbar is added to the page (avoiding redundant elements for multiple editable content fields), so its styling needs to be disabled globally, hence setting this on the <script> element rather than on the editable content element.

Server handling

Design goals:

  1. If you let users upload HTML, you cannot simply reflect them elsewhere without inviting cross-site scripting attacks.
  2. XML/HTML parsing on the server can be dangerous. Tools are fairly mature now, but if not necessary, let's avoid it.

Whatyougetsy cleans the HTML before submission to such an extent that you can validate it with simple string operations, ensuring that it does not contain executable code or disallowed markup. Validation code for Python and PHP can be found in demo-strict.py and demo-strict.php.

I'd like to add non-strict variants which would do HTML parsing in order to allow things like whitespace in tags (<br />). By allowing more content to pass the filtering, we can afford to do less strict content cleaning on the client side, which improves undo capabilities.

Notes for future me: https://www.php.net/strip_tags does not strip attributes, so need to use (new DomDocument())->loadHTML($str) and figure it out from there. For Python, file:///usr/share/doc/python3/html/library/html.parser.html looks very promising because it gives us all tags and attributes with a few lines of code.

Known problems

Newlines
Newline handling is a pain. By default, pressing enter inserts a <br> as you expect. When pasting from LibreOffice Writer, the content gets a <p> tag instead, and now when you press enter, it changes the behavior and inserts a paragraph tag instead. To have consistent behavior, paragraph tags are replaced with a line break. It was subsequently discovered that sometimes, pressing enter inserts a <div> instead. This is not yet handled gracefully, so the content cleaner will remove the <div> (so the server can validate the submitted content) but not insert any <br> to replace it. The user can easily fix this by adding a <br> in the location where it is missing, but it is annoying to manually do this.

An attempt at doing more replacements (fixing the <div> issue) was made and can be found in whatyougetsy-replacemorenewlines.js. This only made things worse, but maybe it helps in the future to know what didn't work, or maybe it was a good start and only needs tweaking, so hence that version is preserved there. Maybe we can take inspiration from this implementation: https://github.com/nomocas/mini-wysiwyg/blob/579774f2e36c9d2b8d51f5b9b10ac0ac0dae2efe/index.js#L483

Undo
Any time you touch the contents in a contenteditable element, you lose all undo history. Except if you use execCommand() but you are explicitly told not to do that by browser makers. Because execCommand() is widely used and there is no reasonable replacement, Whatyougetsy uses it whenever possible, but content cleaning still causes undo history to break. We limit cleaning to:

  1. On page load, to make sure it starts with a good state and prevent surprises later
  2. On paste, because a lot of applications include disallowed tags or properties and it's better to clean this straight away than to surprise the user later when the content looks different after submitting to the server
  3. On submission of the form, so the server needs not reject the submission altogether when it includes disallowed (potentially dangerous) content

This means you get undo history for the entire edit time, except when pasting disallowed content. Perhaps the paste could be caught, stuffed into a different div, apply the edit there, and then insert that at the cursor with execCommand? I don't know that you can insert marked-up contents but that's worth looking into. Alternatively, we could copy it to the clipboard again after cleaning and then instruct the user to do another paste operation. Then we can detect that the contents are fine and let that one pass, preserving both safety and undo history, but the user has to press Ctrl+V twice on occasion (not always because some content will already be fine like plain text content, which is annoying, but requiring it every time also seems silly).

For what it's worth, I now understand why editors with syntax highlighting support have such terrible undo handling. I always wondered why not just leave the browser default which works perfectly fine? You can't style a normal text field, that's why.

Browser restarts
The default behavior is to preserve user input (in, e.g., <input>, <select>, and <textarea>) when you reload the page or when your browser restarts for any reason. This cannot be enabled or disabled, so we cannot enable it for our editable content field. A workaround may be to use localStorage, which can then be cleaned on form submission so you're not clogging it indefinitely, but then you need cross-tab communication to coordinate that it doesn't restore your content in another tab. Perhaps always having a hidden textarea and periodically (e.g., every second while the user is typing?) serializing it is a better solution.

Such a workaround is not yet implemented in Whatyougetsy.

Accessibility
If anyone with a sight impairment comes across this and wants to help, please get in touch. I'd like to improve it but am not currently sure how to do actions such as telling the browser to start offering buttons from the toolbar when selecting things (also because the 'selection changed' event doesn't fire when it should).


If your users are fine with typing bbcode, you can also avoid all these problems. No magic markup tags hidden from the user, no UI needed, just a textarea and versionable input. Rendering is a matter of a few string replacements. Example Python bbcode-to-HTML rendering. Markdown is also an option of course: nicer to type and, while more complex to parse, plenty of libraries can do that for you.