From 7352ee5877982e3913c44dc86a80513d26b674ba Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 3 Apr 2023 20:00:16 +0800 Subject: [PATCH 01/17] improve --- templates/repo/issue/comment_tab.tmpl | 18 +++-- web_src/css/editor-markdown.css | 11 ++- web_src/css/repository.css | 21 ----- web_src/css/review.css | 7 +- .../js/features/comp/ComboMarkdownEditor.js | 80 ++++++++++++++++--- web_src/js/features/repo-wiki.js | 2 + 6 files changed, 94 insertions(+), 45 deletions(-) diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl index 2212d99a10438..e11105d901d1d 100644 --- a/templates/repo/issue/comment_tab.tmpl +++ b/templates/repo/issue/comment_tab.tmpl @@ -3,14 +3,16 @@ {{if not $textareaContent}}{{$textareaContent = .PullRequestTemplate}}{{end}} {{if not $textareaContent}}{{$textareaContent = .content}}{{end}} -{{template "shared/combomarkdowneditor" (dict - "locale" $.locale - "MarkdownPreviewUrl" (print .Repository.Link "/markup") - "MarkdownPreviewContext" .RepoLink - "TextareaName" "content" - "TextareaContent" $textareaContent - "DropzoneParentContainer" "form, .ui.form" -)}} +
+ {{template "shared/combomarkdowneditor" (dict + "locale" $.locale + "MarkdownPreviewUrl" (print .Repository.Link "/markup") + "MarkdownPreviewContext" .RepoLink + "TextareaName" "content" + "TextareaContent" $textareaContent + "DropzoneParentContainer" "form, .ui.form" + )}} +
{{if .IsAttachmentEnabled}}
diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css index 31ffeb06d044a..4f4727eadd190 100644 --- a/web_src/css/editor-markdown.css +++ b/web_src/css/editor-markdown.css @@ -18,8 +18,15 @@ cursor: pointer; } -.combo-markdown-editor .markdown-text-editor { +.ui.form .combo-markdown-editor textarea.markdown-text-editor, +.combo-markdown-editor textarea.markdown-text-editor { display: block; width: 100%; - height: 200px; + min-height: 200px; + max-height: calc(100vh - 200px); + resize: none; +} + +.combo-markdown-editor .CodeMirror-scroll { + max-height: calc(100vh - 200px); } diff --git a/web_src/css/repository.css b/web_src/css/repository.css index fb5c75b73c97a..bfa771457a860 100644 --- a/web_src/css/repository.css +++ b/web_src/css/repository.css @@ -544,10 +544,6 @@ margin: 0; } -.repository .comment textarea { - max-height: none !important; -} - .repository.new.issue .comment.form .comment .avatar { width: 3em; } @@ -1068,11 +1064,6 @@ min-height: 5rem; } -.repository.view.issue .comment-list .comment .ui.form textarea { - height: 200px; - font-family: var(--fonts-monospace); -} - .repository.view.issue .comment-list .comment .edit.buttons { margin-top: 10px; } @@ -1191,15 +1182,6 @@ margin-top: -8px; } -.repository .comment.form .content textarea { - height: 200px; - font-family: var(--fonts-monospace); -} - -.repository .comment.form .content .CodeMirror-scroll { - max-height: 85vh; -} - .repository .milestone.list { list-style: none; padding-top: 15px; @@ -2129,9 +2111,6 @@ margin-top: 0; } -.repository.wiki .form .CodeMirror-scroll { - max-height: 85vh; -} @media (max-width: 767px) { .repository.wiki .dividing.header .stackable.grid .button { diff --git a/web_src/css/review.css b/web_src/css/review.css index 913a7e9df223a..f17b50984054d 100644 --- a/web_src/css/review.css +++ b/web_src/css/review.css @@ -160,8 +160,11 @@ margin: 0.5em; } +.comment-code-cloud .editor-statusbar { + display: none; +} + .comment-code-cloud .footer { - border-top: 1px solid var(--color-secondary); padding: 10px 0; } @@ -248,7 +251,7 @@ a.blob-excerpt:hover { } } -.review-box-panel .combo-markdown-editor textarea { +.review-box-panel .combo-markdown-editor { width: 730px; max-width: calc(100vw - 70px); } diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 4905ec23415e7..dba2f525bb53f 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -1,8 +1,8 @@ import '@github/markdown-toolbar-element'; +import $ from 'jquery'; import {attachTribute} from '../tribute.js'; import {hideElem, showElem} from '../../utils/dom.js'; import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js'; -import $ from 'jquery'; import {initMarkupContent} from '../../markup/content.js'; import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js'; import {attachRefIssueContextPopup} from '../contextpopup.js'; @@ -39,31 +39,57 @@ class ComboMarkdownEditor { } async init() { + this.prepareEasyMDEToolbarActions(); + + this.setupTab(); + this.setupDropzone(); + + this.setupTextarea(); + + await attachTribute(this.textarea, {mentions: true, emoji: true}); + + if (this.userPreferredEditor === 'easymde') { + await this.switchToEasyMDE(); + } + } + + applyEditorHeights(el, heights) { + if (!heights) return; + if (heights.minHeight) el.style.minHeight = heights.minHeight; + if (heights.height) el.style.height = heights.height; + if (heights.maxHeight) el.style.maxHeight = heights.maxHeight; + } + + setupTextarea() { this.textarea = this.container.querySelector('.markdown-text-editor'); this.textarea._giteaComboMarkdownEditor = this; - this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter)}`; - this.textarea.addEventListener('input', (e) => {this.options?.onContentChanged?.(this, e)}); + this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`; + this.textarea.addEventListener('input', (e) => { + this.textareaAutoResize(); + this.options?.onContentChanged?.(this, e); + }); + this.applyEditorHeights(this.textarea, this.options.editorHeights); + this.textareaAutoResize(); this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar'); this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id); - elementIdCounter++; - this.switchToEasyMDEButton = this.container.querySelector('.markdown-switch-easymde'); this.switchToEasyMDEButton?.addEventListener('click', async (e) => { e.preventDefault(); + this.userPreferredEditor = 'easymde'; await this.switchToEasyMDE(); }); - await attachTribute(this.textarea, {mentions: true, emoji: true}); + if (this.dropzone) { + initTextareaImagePaste(this.textarea, this.dropzone); + } + } + setupDropzone() { const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container'); if (dropzoneParentContainer) { this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone'); - initTextareaImagePaste(this.textarea, this.dropzone); } - - this.setupTab(); - this.prepareEasyMDEToolbarActions(); } setupTab() { @@ -134,7 +160,10 @@ class ComboMarkdownEditor { title: 'Add Checkbox (checked)', }, 'gitea-switch-to-textarea': { - action: this.switchToTextarea.bind(this), + action: () => { + this.userPreferredEditor = 'textarea'; + this.switchToTextarea(); + }, className: 'fa fa-file', title: 'Revert to simple textarea', }, @@ -169,7 +198,7 @@ class ComboMarkdownEditor { return processed; } - async switchToTextarea() { + switchToTextarea() { showElem(this.textareaMarkdownToolbar); if (this.easyMDE) { this.easyMDE.toTextArea(); @@ -218,6 +247,7 @@ class ComboMarkdownEditor { } }, }); + this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll'), this.options.editorHeights); await attachTribute(this.easyMDE.codemirror.getInputField(), {mentions: true, emoji: true}); initEasyMDEImagePaste(this.easyMDE, this.dropzone); hideElem(this.textareaMarkdownToolbar); @@ -235,6 +265,7 @@ class ComboMarkdownEditor { this.easyMDE.value(v); } else { this.textarea.value = v; + this.textareaAutoResize(); } } @@ -246,6 +277,24 @@ class ComboMarkdownEditor { } } + textareaAutoResize() { + if (this.textareaInitalHeight === undefined) { + this.textareaInitalHeight = this.textarea.offsetHeight; + } + const offset = this.textarea.offsetHeight - this.textarea.clientHeight; + if (!this.lastValue || Math.abs(this.lastValue.length - this.textarea.value.length) > 2) { + // the value has changed a lot, so reset the height to calculate the real scroll height, it might cause UI flickering + this.textarea.style.height = 'auto'; + } else { + // try to shrink a little to see if a line is deleted (since the value doesn't change much), it won't cause UI flickering + // the magic number is a general number which fits most line-height styles. + this.textarea.style.height = `${this.textarea.scrollHeight + offset - 40}px`; + } + // make sure the height is not smaller than the initial height + this.textarea.style.height = `${Math.max(this.textareaInitalHeight, this.textarea.scrollHeight + offset)}px`; + this.lastValue = this.textarea.value; + } + moveCursorToEnd() { this.textarea.focus(); this.textarea.setSelectionRange(this.textarea.value.length, this.textarea.value.length); @@ -254,6 +303,13 @@ class ComboMarkdownEditor { this.easyMDE.codemirror.setCursor(this.easyMDE.codemirror.lineCount(), 0); } } + + get userPreferredEditor() { + return window.localStorage.getItem(`markdown-editor-${this.options.useScene ?? 'default'}`); + } + set userPreferredEditor(s) { + window.localStorage.setItem(`markdown-editor-${this.options.useScene ?? 'default'}`, s); + } } export function getComboMarkdownEditor(el) { diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js index a48f63dcb18ca..9a23df69972fc 100644 --- a/web_src/js/features/repo-wiki.js +++ b/web_src/js/features/repo-wiki.js @@ -44,6 +44,8 @@ async function initRepoWikiFormEditor() { renderEasyMDEPreview(); editor = await initComboMarkdownEditor($editorContainer, { + useScene: 'wiki', + editorHeights: {minHeight: '300px', height: 'calc(100vh - 600px)'}, previewMode: 'gfm', previewWiki: true, easyMDEOptions: { From ab1b645a62a0036533bb1b6f483c21a648d4938d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 3 Apr 2023 22:59:26 +0800 Subject: [PATCH 02/17] add comment --- web_src/js/features/repo-wiki.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js index 9a23df69972fc..14cfe6816cbc2 100644 --- a/web_src/js/features/repo-wiki.js +++ b/web_src/js/features/repo-wiki.js @@ -45,6 +45,8 @@ async function initRepoWikiFormEditor() { editor = await initComboMarkdownEditor($editorContainer, { useScene: 'wiki', + // EasyMDE has some problems of height definition, it has inline style height 300px by default, so we also use inline styles to override it. + // And another benefit is that we only need to write the style once for both editors. editorHeights: {minHeight: '300px', height: 'calc(100vh - 600px)'}, previewMode: 'gfm', previewWiki: true, From 1f73dda83eebbc9e7ccc98f339549eed37b2bc9b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 3 Apr 2023 23:05:08 +0800 Subject: [PATCH 03/17] Update web_src/js/features/repo-wiki.js Co-authored-by: silverwind --- web_src/js/features/repo-wiki.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js index 14cfe6816cbc2..09202a303ce86 100644 --- a/web_src/js/features/repo-wiki.js +++ b/web_src/js/features/repo-wiki.js @@ -47,6 +47,7 @@ async function initRepoWikiFormEditor() { useScene: 'wiki', // EasyMDE has some problems of height definition, it has inline style height 300px by default, so we also use inline styles to override it. // And another benefit is that we only need to write the style once for both editors. + // TODO: Move height style to CSS after EasyMDE removal. editorHeights: {minHeight: '300px', height: 'calc(100vh - 600px)'}, previewMode: 'gfm', previewWiki: true, From d2b65b64a9c88cea106aa75f6558d8574bb85444 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 3 Apr 2023 23:21:27 +0800 Subject: [PATCH 04/17] merge review panel width --- web_src/css/review.css | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web_src/css/review.css b/web_src/css/review.css index 2e181f9ca248f..d6ac842c0f84e 100644 --- a/web_src/css/review.css +++ b/web_src/css/review.css @@ -227,15 +227,9 @@ a.blob-excerpt:hover { max-height: calc(100vh - 360px); } -.review-box-panel .editor-toolbar, -.review-box-panel .CodeMirror-scroll { - width: min(calc(100vw - 2em), 800px); - max-width: none; -} - .review-box-panel .combo-markdown-editor { - width: 730px; - max-width: calc(100vw - 70px); + width: 730px; /* this width matches current EasyMDE's toolbar's width */ + max-width: calc(100vw - 70px); /* leave enough space on left, and align the page content */ } #review-box { From 28701bb8d5479e45e5fc0f59e45874a542c1b7c4 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 3 Apr 2023 23:37:52 +0800 Subject: [PATCH 05/17] use the same value change detection as autosize package --- web_src/js/features/comp/ComboMarkdownEditor.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index dba2f525bb53f..57b7088eb97ed 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -282,13 +282,9 @@ class ComboMarkdownEditor { this.textareaInitalHeight = this.textarea.offsetHeight; } const offset = this.textarea.offsetHeight - this.textarea.clientHeight; - if (!this.lastValue || Math.abs(this.lastValue.length - this.textarea.value.length) > 2) { - // the value has changed a lot, so reset the height to calculate the real scroll height, it might cause UI flickering + if (!this.lastValue || !this.textarea.value.startsWith(this.lastValue)) { + // the value has changed a lot, so reset the height to calculate the real scroll height, it's slow and might cause slight flickering. this.textarea.style.height = 'auto'; - } else { - // try to shrink a little to see if a line is deleted (since the value doesn't change much), it won't cause UI flickering - // the magic number is a general number which fits most line-height styles. - this.textarea.style.height = `${this.textarea.scrollHeight + offset - 40}px`; } // make sure the height is not smaller than the initial height this.textarea.style.height = `${Math.max(this.textareaInitalHeight, this.textarea.scrollHeight + offset)}px`; From 9af6a4489bb68c5b220282f9af44cf7621209c2a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 4 Apr 2023 08:36:00 +0800 Subject: [PATCH 06/17] allow resize --- web_src/css/editor-markdown.css | 1 - web_src/js/features/comp/ComboMarkdownEditor.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css index 4f4727eadd190..0fbdad72a8c49 100644 --- a/web_src/css/editor-markdown.css +++ b/web_src/css/editor-markdown.css @@ -24,7 +24,6 @@ width: 100%; min-height: 200px; max-height: calc(100vh - 200px); - resize: none; } .combo-markdown-editor .CodeMirror-scroll { diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 57b7088eb97ed..3169e65830034 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -280,6 +280,7 @@ class ComboMarkdownEditor { textareaAutoResize() { if (this.textareaInitalHeight === undefined) { this.textareaInitalHeight = this.textarea.offsetHeight; + new ResizeObserver(() => this.textareaInitalHeight = this.textarea.offsetHeight).observe(this.textarea); } const offset = this.textarea.offsetHeight - this.textarea.clientHeight; if (!this.lastValue || !this.textarea.value.startsWith(this.lastValue)) { From d99c0c7f1a9b6d60dad0c7cdc65f1e4d4a4f9cc1 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 4 Apr 2023 17:45:43 +0800 Subject: [PATCH 07/17] use autosize package --- package-lock.json | 6 +++++ package.json | 1 + web_src/css/editor-markdown.css | 1 + .../js/features/comp/ComboMarkdownEditor.js | 25 +++---------------- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9d998a69d2a8..82f741bb4943e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "add-asset-webpack-plugin": "2.0.1", "ansi-to-html": "0.7.2", "asciinema-player": "3.2.0", + "autosize": "6.0.1", "clippie": "3.1.4", "css-loader": "6.7.3", "dropzone": "6.0.0-beta.2", @@ -2408,6 +2409,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/autosize": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/autosize/-/autosize-6.0.1.tgz", + "integrity": "sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", diff --git a/package.json b/package.json index 3ccf0c0840baa..7e3554416c7e7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "add-asset-webpack-plugin": "2.0.1", "ansi-to-html": "0.7.2", "asciinema-player": "3.2.0", + "autosize": "6.0.1", "clippie": "3.1.4", "css-loader": "6.7.3", "dropzone": "6.0.0-beta.2", diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css index 0fbdad72a8c49..7f8f618f7e17a 100644 --- a/web_src/css/editor-markdown.css +++ b/web_src/css/editor-markdown.css @@ -24,6 +24,7 @@ width: 100%; min-height: 200px; max-height: calc(100vh - 200px); + resize: vertical; /* TODO: even if we set this, the `autosize` package doesn't support manually resize vertically */ } .combo-markdown-editor .CodeMirror-scroll { diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 3169e65830034..4e999519aade1 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -6,6 +6,7 @@ import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js'; import {initMarkupContent} from '../../markup/content.js'; import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js'; import {attachRefIssueContextPopup} from '../contextpopup.js'; +import autosize from 'autosize'; let elementIdCounter = 0; @@ -64,12 +65,10 @@ class ComboMarkdownEditor { this.textarea = this.container.querySelector('.markdown-text-editor'); this.textarea._giteaComboMarkdownEditor = this; this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`; - this.textarea.addEventListener('input', (e) => { - this.textareaAutoResize(); - this.options?.onContentChanged?.(this, e); - }); + this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e)); this.applyEditorHeights(this.textarea, this.options.editorHeights); - this.textareaAutoResize(); + autosize(this.textarea); + this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar'); this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id); @@ -265,7 +264,6 @@ class ComboMarkdownEditor { this.easyMDE.value(v); } else { this.textarea.value = v; - this.textareaAutoResize(); } } @@ -277,21 +275,6 @@ class ComboMarkdownEditor { } } - textareaAutoResize() { - if (this.textareaInitalHeight === undefined) { - this.textareaInitalHeight = this.textarea.offsetHeight; - new ResizeObserver(() => this.textareaInitalHeight = this.textarea.offsetHeight).observe(this.textarea); - } - const offset = this.textarea.offsetHeight - this.textarea.clientHeight; - if (!this.lastValue || !this.textarea.value.startsWith(this.lastValue)) { - // the value has changed a lot, so reset the height to calculate the real scroll height, it's slow and might cause slight flickering. - this.textarea.style.height = 'auto'; - } - // make sure the height is not smaller than the initial height - this.textarea.style.height = `${Math.max(this.textareaInitalHeight, this.textarea.scrollHeight + offset)}px`; - this.lastValue = this.textarea.value; - } - moveCursorToEnd() { this.textarea.focus(); this.textarea.setSelectionRange(this.textarea.value.length, this.textarea.value.length); From c2dfbb399b523f6db8a4b1f7764dc21ffa5d03c6 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 5 Apr 2023 22:58:44 +0200 Subject: [PATCH 08/17] add custom autosize function --- web_src/css/editor-markdown.css | 2 +- .../js/features/comp/ComboMarkdownEditor.js | 5 +- web_src/js/utils/dom.js | 89 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css index 7f8f618f7e17a..da64164aec3d8 100644 --- a/web_src/css/editor-markdown.css +++ b/web_src/css/editor-markdown.css @@ -24,7 +24,7 @@ width: 100%; min-height: 200px; max-height: calc(100vh - 200px); - resize: vertical; /* TODO: even if we set this, the `autosize` package doesn't support manually resize vertically */ + resize: vertical; } .combo-markdown-editor .CodeMirror-scroll { diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 4e999519aade1..3274a5a388357 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -1,12 +1,11 @@ import '@github/markdown-toolbar-element'; import $ from 'jquery'; import {attachTribute} from '../tribute.js'; -import {hideElem, showElem} from '../../utils/dom.js'; +import {hideElem, showElem, autosize} from '../../utils/dom.js'; import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js'; import {initMarkupContent} from '../../markup/content.js'; import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js'; import {attachRefIssueContextPopup} from '../contextpopup.js'; -import autosize from 'autosize'; let elementIdCounter = 0; @@ -67,7 +66,7 @@ class ComboMarkdownEditor { this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`; this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e)); this.applyEditorHeights(this.textarea, this.options.editorHeights); - autosize(this.textarea); + autosize(this.textarea, {viewportMarginBottom: 130}); this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar'); this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id); diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index c160d37f6ccb7..f89f87935f60e 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -49,3 +49,92 @@ export function onDomReady(cb) { cb(); } } + +/* autosize a textarea to fit content */ +/* Based on https://github.com/github/textarea-autosize */ +export function autosize(textarea, {viewportMarginBottom = 100} = {}) { + let isUserResized = false; + let x, y, height; + + function onUserResize(event) { + if (x !== event.clientX || y !== event.clientY) { + const newHeight = textarea.style.height; + if (height && height !== newHeight) { + isUserResized = true; + textarea.style.removeProperty('max-height'); + textarea.removeEventListener('mousemove', onUserResize); + } + height = newHeight; + } + + x = event.clientX; + y = 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.pageYOffset; + const bottom = document.documentElement.clientHeight - (top + textarea.offsetHeight); + return {top, bottom}; + } + + function sizeToFit() { + try { + if (isUserResized) return; + if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return; + + const {top, bottom} = overflowOffset(); + if (top < 0 || bottom < 0) return; + + const textareaStyle = getComputedStyle(textarea); + const topBorderWidth = Number(textareaStyle.borderTopWidth.replace(/px/, '')); + const bottomBorderWidth = Number(textareaStyle.borderBottomWidth.replace(/px/, '')); + const isBorderBox = textareaStyle.boxSizing === 'border-box'; + const borderAddOn = isBorderBox ? topBorderWidth + bottomBorderWidth : 0; + const maxHeight = Number(textareaStyle.height.replace(/px/, '')) + bottom; + const adjustedViewportMarginBottom = bottom < viewportMarginBottom ? bottom : viewportMarginBottom; + + textarea.style.maxHeight = `${maxHeight - adjustedViewportMarginBottom}px`; + textarea.style.height = 'auto'; + textarea.style.height = `${textarea.scrollHeight + borderAddOn}px`; + height = 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; + textarea.style.removeProperty('height'); + textarea.style.removeProperty('max-height'); + } + + textarea.addEventListener('mousemove', onUserResize); + textarea.addEventListener('keyup', sizeToFit); + textarea.addEventListener('paste', sizeToFit); + textarea.addEventListener('input', sizeToFit); + const form = textarea.form; + if (form) form.addEventListener('reset', onFormReset); + if (textarea.value) sizeToFit(); + + return { + unsubscribe() { + textarea.removeEventListener('mousemove', onUserResize); + textarea.removeEventListener('keyup', sizeToFit); + textarea.removeEventListener('input', sizeToFit); + if (form) form.removeEventListener('reset', onFormReset); + } + }; +} From e0d115d56bb2f5a68df5f3c4f13609acace33533 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 5 Apr 2023 22:59:53 +0200 Subject: [PATCH 09/17] remove autosize --- package-lock.json | 6 ------ package.json | 1 - 2 files changed, 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82f741bb4943e..b9d998a69d2a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "add-asset-webpack-plugin": "2.0.1", "ansi-to-html": "0.7.2", "asciinema-player": "3.2.0", - "autosize": "6.0.1", "clippie": "3.1.4", "css-loader": "6.7.3", "dropzone": "6.0.0-beta.2", @@ -2409,11 +2408,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/autosize": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/autosize/-/autosize-6.0.1.tgz", - "integrity": "sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==" - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", diff --git a/package.json b/package.json index 7e3554416c7e7..3ccf0c0840baa 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "add-asset-webpack-plugin": "2.0.1", "ansi-to-html": "0.7.2", "asciinema-player": "3.2.0", - "autosize": "6.0.1", "clippie": "3.1.4", "css-loader": "6.7.3", "dropzone": "6.0.0-beta.2", From c16480f6e80421e83167348d1eadb2504a225453 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 5 Apr 2023 23:13:58 +0200 Subject: [PATCH 10/17] add MIT license copy --- web_src/js/utils/dom.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index f89f87935f60e..1c6dbb97a12aa 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -50,8 +50,22 @@ export function onDomReady(cb) { } } -/* autosize a textarea to fit content */ -/* Based on https://github.com/github/textarea-autosize */ +// 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 = 100} = {}) { let isUserResized = false; let x, y, height; From c831856f4eb8543c7955e059beba99dccdc9c950 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 5 Apr 2023 23:20:27 +0200 Subject: [PATCH 11/17] use parseFloat --- web_src/js/utils/dom.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 1c6dbb97a12aa..44f606e6af2b0 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -108,11 +108,11 @@ export function autosize(textarea, {viewportMarginBottom = 100} = {}) { if (top < 0 || bottom < 0) return; const textareaStyle = getComputedStyle(textarea); - const topBorderWidth = Number(textareaStyle.borderTopWidth.replace(/px/, '')); - const bottomBorderWidth = Number(textareaStyle.borderBottomWidth.replace(/px/, '')); + const topBorderWidth = parseFloat(textareaStyle.borderTopWidth); + const bottomBorderWidth = parseFloat(textareaStyle.borderBottomWidth); const isBorderBox = textareaStyle.boxSizing === 'border-box'; const borderAddOn = isBorderBox ? topBorderWidth + bottomBorderWidth : 0; - const maxHeight = Number(textareaStyle.height.replace(/px/, '')) + bottom; + const maxHeight = parseFloat(textareaStyle.height) + bottom; const adjustedViewportMarginBottom = bottom < viewportMarginBottom ? bottom : viewportMarginBottom; textarea.style.maxHeight = `${maxHeight - adjustedViewportMarginBottom}px`; From 2ec61edbfbcc529d030424367b9c1a07534a37b8 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 5 Apr 2023 23:23:14 +0200 Subject: [PATCH 12/17] add placeholder to issue/pr comment --- templates/repo/issue/comment_tab.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl index e11105d901d1d..c40e6ddf32e87 100644 --- a/templates/repo/issue/comment_tab.tmpl +++ b/templates/repo/issue/comment_tab.tmpl @@ -10,6 +10,7 @@ "MarkdownPreviewContext" .RepoLink "TextareaName" "content" "TextareaContent" $textareaContent + "TextareaPlaceholder" ($.locale.Tr "repo.diff.comment.placeholder") "DropzoneParentContainer" "form, .ui.form" )}}
From a1a61b9b36f6f10ec8353465d40bdda7a0caa695 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 6 Apr 2023 20:15:09 +0800 Subject: [PATCH 13/17] fine tune --- .../js/features/comp/ComboMarkdownEditor.js | 3 +- web_src/js/utils/dom.js | 55 ++++++++++++------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 3274a5a388357..c1607a1da8716 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -66,7 +66,7 @@ class ComboMarkdownEditor { this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`; this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e)); this.applyEditorHeights(this.textarea, this.options.editorHeights); - autosize(this.textarea, {viewportMarginBottom: 130}); + this.textareaAutosize = autosize(this.textarea, {viewportMarginBottom: 130}); this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar'); this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id); @@ -264,6 +264,7 @@ class ComboMarkdownEditor { } else { this.textarea.value = v; } + this.textareaAutosize.resizeToFit(); } focus() { diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 44f606e6af2b0..18f371336f01d 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -66,19 +66,33 @@ export function onDomReady(cb) { // 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 = 100} = {}) { +export function autosize(textarea, {viewportMarginBottom = 0} = {}) { let isUserResized = false; - let x, y, height; + let x, y, styleHeight; // the height in style like '100px', not a number + + const originalStyles = {}; + function backupStyle(name) { + originalStyles[name] = textarea.style[name]; + } + function restoreStyle(name) { + if (name in originalStyles) { + if (originalStyles[name] === undefined) { + textarea.style.removeProperty(name); + } else { + textarea.style[name] = originalStyles[name]; + } + } + } function onUserResize(event) { + if (isUserResized) return; if (x !== event.clientX || y !== event.clientY) { - const newHeight = textarea.style.height; - if (height && height !== newHeight) { + const newStyleHeight = textarea.style.height; + if (styleHeight && styleHeight !== newStyleHeight) { isUserResized = true; textarea.style.removeProperty('max-height'); - textarea.removeEventListener('mousemove', onUserResize); } - height = newHeight; + styleHeight = newStyleHeight; } x = event.clientX; @@ -94,12 +108,12 @@ export function autosize(textarea, {viewportMarginBottom = 100} = {}) { el = el.offsetParent; } - const top = offsetTop - document.defaultView.pageYOffset; + const top = offsetTop - document.defaultView.scrollY; const bottom = document.documentElement.clientHeight - (top + textarea.offsetHeight); return {top, bottom}; } - function sizeToFit() { + function resizeToFit() { try { if (isUserResized) return; if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return; @@ -118,7 +132,7 @@ export function autosize(textarea, {viewportMarginBottom = 100} = {}) { textarea.style.maxHeight = `${maxHeight - adjustedViewportMarginBottom}px`; textarea.style.height = 'auto'; textarea.style.height = `${textarea.scrollHeight + borderAddOn}px`; - height = textarea.style.height; + styleHeight = 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 @@ -131,24 +145,23 @@ export function autosize(textarea, {viewportMarginBottom = 100} = {}) { function onFormReset() { isUserResized = false; - textarea.style.removeProperty('height'); - textarea.style.removeProperty('max-height'); + restoreStyle('height'); + restoreStyle('max-height'); } textarea.addEventListener('mousemove', onUserResize); - textarea.addEventListener('keyup', sizeToFit); - textarea.addEventListener('paste', sizeToFit); - textarea.addEventListener('input', sizeToFit); - const form = textarea.form; - if (form) form.addEventListener('reset', onFormReset); - if (textarea.value) sizeToFit(); + textarea.addEventListener('input', resizeToFit); + textarea.form?.addEventListener('reset', onFormReset); + backupStyle('height'); + backupStyle('max-height'); + if (textarea.value) resizeToFit(); return { - unsubscribe() { + resizeToFit, + destroy() { textarea.removeEventListener('mousemove', onUserResize); - textarea.removeEventListener('keyup', sizeToFit); - textarea.removeEventListener('input', sizeToFit); - if (form) form.removeEventListener('reset', onFormReset); + textarea.removeEventListener('input', resizeToFit); + textarea.form?.removeEventListener('reset', onFormReset); } }; } From 23ac0bdbfd8d11322db1fd9726a15cf45e85e0d8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 6 Apr 2023 21:24:48 +0800 Subject: [PATCH 14/17] fix --- web_src/js/utils/dom.js | 81 +++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 18f371336f01d..375ed18ccdff6 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -68,35 +68,21 @@ export function onDomReady(cb) { // --------------------------------------------------------------------- export function autosize(textarea, {viewportMarginBottom = 0} = {}) { let isUserResized = false; - let x, y, styleHeight; // the height in style like '100px', not a number - - const originalStyles = {}; - function backupStyle(name) { - originalStyles[name] = textarea.style[name]; - } - function restoreStyle(name) { - if (name in originalStyles) { - if (originalStyles[name] === undefined) { - textarea.style.removeProperty(name); - } else { - textarea.style[name] = originalStyles[name]; - } - } - } + let lastMouseX, lastMouseY, lastStyleHeight; // the height is the property in style like '100px', not a number + let initialStyleHeight = undefined; function onUserResize(event) { if (isUserResized) return; - if (x !== event.clientX || y !== event.clientY) { + if (lastMouseX !== event.clientX || lastMouseY !== event.clientY) { const newStyleHeight = textarea.style.height; - if (styleHeight && styleHeight !== newStyleHeight) { + if (lastStyleHeight && lastStyleHeight !== newStyleHeight) { isUserResized = true; - textarea.style.removeProperty('max-height'); } - styleHeight = newStyleHeight; + lastStyleHeight = newStyleHeight; } - x = event.clientX; - y = event.clientY; + lastMouseX = event.clientX; + lastMouseY = event.clientY; } function overflowOffset() { @@ -114,28 +100,43 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { } function resizeToFit() { - try { - if (isUserResized) return; - if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return; + if (isUserResized) return; + if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return; + try { const {top, bottom} = overflowOffset(); - if (top < 0 || bottom < 0) return; + const isOutOfViewport = top < 0 || bottom < 0; - const textareaStyle = getComputedStyle(textarea); - const topBorderWidth = parseFloat(textareaStyle.borderTopWidth); - const bottomBorderWidth = parseFloat(textareaStyle.borderBottomWidth); - const isBorderBox = textareaStyle.boxSizing === 'border-box'; + 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 maxHeight = parseFloat(textareaStyle.height) + bottom; + const adjustedViewportMarginBottom = bottom < viewportMarginBottom ? bottom : viewportMarginBottom; + const curHeight = parseFloat(computedStyle.height); + const maxHeight = curHeight + bottom - adjustedViewportMarginBottom; - textarea.style.maxHeight = `${maxHeight - adjustedViewportMarginBottom}px`; textarea.style.height = 'auto'; - textarea.style.height = `${textarea.scrollHeight + borderAddOn}px`; - styleHeight = textarea.style.height; + 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 + // 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; @@ -145,15 +146,17 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { function onFormReset() { isUserResized = false; - restoreStyle('height'); - restoreStyle('max-height'); + 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); - backupStyle('height'); - backupStyle('max-height'); + initialStyleHeight = textarea.style.height ?? undefined; if (textarea.value) resizeToFit(); return { From 4ca7a9a6c3511c46743d926849593b91f887374c Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 6 Apr 2023 21:35:13 +0200 Subject: [PATCH 15/17] fix lint --- web_src/js/utils/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 375ed18ccdff6..4ac8f029adede 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -69,7 +69,7 @@ export function onDomReady(cb) { export function autosize(textarea, {viewportMarginBottom = 0} = {}) { let isUserResized = false; let lastMouseX, lastMouseY, lastStyleHeight; // the height is the property in style like '100px', not a number - let initialStyleHeight = undefined; + let initialStyleHeight; function onUserResize(event) { if (isUserResized) return; From ff65bda4f203bf33632e518fb12ab9072d50cac0 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 6 Apr 2023 21:38:40 +0200 Subject: [PATCH 16/17] wrap comments --- web_src/js/utils/dom.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 4ac8f029adede..cc29a1aec5541 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -68,8 +68,8 @@ export function onDomReady(cb) { // --------------------------------------------------------------------- export function autosize(textarea, {viewportMarginBottom = 0} = {}) { let isUserResized = false; - let lastMouseX, lastMouseY, lastStyleHeight; // the height is the property in style like '100px', not a number - let initialStyleHeight; + // lastStyleHeight is a CSS value like '100px' + let lastMouseX, lastMouseY, lastStyleHeight, initialStyleHeight; function onUserResize(event) { if (isUserResized) return; @@ -126,8 +126,9 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { 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 + // * 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); @@ -136,7 +137,8 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { 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 + // 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; From 1bc900b97069e449ba7380e845882178c9ddee11 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 6 Apr 2023 21:41:36 +0200 Subject: [PATCH 17/17] update comment --- web_src/js/utils/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index cc29a1aec5541..6a9ee56eebecf 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -68,7 +68,7 @@ export function onDomReady(cb) { // --------------------------------------------------------------------- export function autosize(textarea, {viewportMarginBottom = 0} = {}) { let isUserResized = false; - // lastStyleHeight is a CSS value like '100px' + // lastStyleHeight and initialStyleHeight are CSS values like '100px' let lastMouseX, lastMouseY, lastStyleHeight, initialStyleHeight; function onUserResize(event) {