diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-08 01:03:29 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-07 13:03:29 -0400 |
commit | 93eb914438fcec234842ed626278fecab3fefba6 (patch) | |
tree | 7d8800032322a64f3d07e151dcf32a2e732ec970 /web_src/js/utils | |
parent | 97d5ec2aeb4a3718a5f27aa4ad608e1a0e3a94b4 (diff) | |
download | gitea-93eb914438fcec234842ed626278fecab3fefba6.tar.gz gitea-93eb914438fcec234842ed626278fecab3fefba6.zip |
Improve markdown editor: width, height, preferred (#23895)
Follow #23876
1. Fine tune the heights of the editors (like before)
* Auto expand the editor (increase/decrease the height) when editing
2. Remember user's last used editor (textarea/easymde) in LocalStorage,
then next time the editor will be switched automatically
* No need to introduce extra config option, it satisfies all users,
including who prefer EasyMDE
3. Also fix the width problem of Review Panel
Screenshot:
<details>
![image](https://user-images.githubusercontent.com/2114189/229518585-2e05827e-8355-48f3-a20c-2c8b9e60ce74.png)
![image](https://user-images.githubusercontent.com/2114189/229518173-4caa6da7-6ad9-40e9-bf1a-ceddfcd4b37f.png)
![image](https://user-images.githubusercontent.com/2114189/229507886-148e9b84-9b58-46d1-ba3f-727e1396f476.png)
![image](https://user-images.githubusercontent.com/2114189/229518258-9f522294-1e64-4b06-91ab-ab43b0353aaa.png)
![image](https://user-images.githubusercontent.com/2114189/229507752-6d540ac7-7748-4bb6-bc09-28acab32d31b.png)
![image](https://user-images.githubusercontent.com/2114189/229510899-de322af5-57e8-4dc5-9a61-771a3b1bee79.png)
</details>
---------
Co-authored-by: silverwind <me@silverwind.io>
Diffstat (limited to 'web_src/js/utils')
-rw-r--r-- | web_src/js/utils/dom.js | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index c160d37f6c..6a9ee56eeb 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -49,3 +49,124 @@ export function onDomReady(cb) { cb(); } } + +// autosize a textarea to fit content. Based on +// https://github.com/github/textarea-autosize +// --------------------------------------------------------------------- +// Copyright (c) 2018 GitHub, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// --------------------------------------------------------------------- +export function autosize(textarea, {viewportMarginBottom = 0} = {}) { + let isUserResized = false; + // lastStyleHeight and initialStyleHeight are CSS values like '100px' + let lastMouseX, lastMouseY, lastStyleHeight, initialStyleHeight; + + function onUserResize(event) { + if (isUserResized) return; + if (lastMouseX !== event.clientX || lastMouseY !== event.clientY) { + const newStyleHeight = textarea.style.height; + if (lastStyleHeight && lastStyleHeight !== newStyleHeight) { + isUserResized = true; + } + lastStyleHeight = newStyleHeight; + } + + lastMouseX = event.clientX; + lastMouseY = event.clientY; + } + + function overflowOffset() { + let offsetTop = 0; + let el = textarea; + + while (el !== document.body && el !== null) { + offsetTop += el.offsetTop || 0; + el = el.offsetParent; + } + + const top = offsetTop - document.defaultView.scrollY; + const bottom = document.documentElement.clientHeight - (top + textarea.offsetHeight); + return {top, bottom}; + } + + function resizeToFit() { + if (isUserResized) return; + if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return; + + try { + const {top, bottom} = overflowOffset(); + const isOutOfViewport = top < 0 || bottom < 0; + + const computedStyle = getComputedStyle(textarea); + const topBorderWidth = parseFloat(computedStyle.borderTopWidth); + const bottomBorderWidth = parseFloat(computedStyle.borderBottomWidth); + const isBorderBox = computedStyle.boxSizing === 'border-box'; + const borderAddOn = isBorderBox ? topBorderWidth + bottomBorderWidth : 0; + + const adjustedViewportMarginBottom = bottom < viewportMarginBottom ? bottom : viewportMarginBottom; + const curHeight = parseFloat(computedStyle.height); + const maxHeight = curHeight + bottom - adjustedViewportMarginBottom; + + textarea.style.height = 'auto'; + let newHeight = textarea.scrollHeight + borderAddOn; + + if (isOutOfViewport) { + // it is already out of the viewport: + // * if the textarea is expanding: do not resize it + if (newHeight > curHeight) { + newHeight = curHeight; + } + // * if the textarea is shrinking, shrink line by line (just use the + // scrollHeight). do not apply max-height limit, otherwise the page + // flickers and the textarea jumps + } else { + // * if it is in the viewport, apply the max-height limit + newHeight = Math.min(maxHeight, newHeight); + } + + textarea.style.height = `${newHeight}px`; + lastStyleHeight = textarea.style.height; + } finally { + // ensure that the textarea is fully scrolled to the end, when the cursor + // is at the end during an input event + if (textarea.selectionStart === textarea.selectionEnd && + textarea.selectionStart === textarea.value.length) { + textarea.scrollTop = textarea.scrollHeight; + } + } + } + + function onFormReset() { + isUserResized = false; + if (initialStyleHeight !== undefined) { + textarea.style.height = initialStyleHeight; + } else { + textarea.style.removeProperty('height'); + } + } + + textarea.addEventListener('mousemove', onUserResize); + textarea.addEventListener('input', resizeToFit); + textarea.form?.addEventListener('reset', onFormReset); + initialStyleHeight = textarea.style.height ?? undefined; + if (textarea.value) resizeToFit(); + + return { + resizeToFit, + destroy() { + textarea.removeEventListener('mousemove', onUserResize); + textarea.removeEventListener('input', resizeToFit); + textarea.form?.removeEventListener('reset', onFormReset); + } + }; +} |