From fae8e1aeb75968f51c4bbdd84783fa56b746217b Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Wed, 14 Feb 2024 16:56:45 -0600 Subject: [PATCH 1/6] Adds truncation to work item description --- .../components/work_item_description.vue | 3 + .../work_item_description_rendered.vue | 56 +++++++++++++++++-- .../work_item_description_show_all.vue | 25 +++++++++ .../components/work_item_detail.vue | 5 ++ .../stylesheets/page_bundles/work_items.scss | 44 ++++++++++++++- locale/gitlab.pot | 3 + .../work_item_description_rendered_spec.js | 44 +++++++++++++++ 7 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/work_items/components/work_item_description_show_all.vue diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue index d5ba2195b0ed2b..3470b31545e152 100644 --- a/app/assets/javascripts/work_items/components/work_item_description.vue +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -61,6 +61,7 @@ export default { data() { return { workItem: {}, + disableTruncation: false, isEditing: this.editMode, isSubmitting: false, isSubmittingWithKeydown: false, @@ -181,6 +182,7 @@ export default { }, async startEditing() { this.isEditing = true; + this.disableTruncation = true; this.descriptionText = getDraft(this.autosaveKey) || this.workItemDescription?.description; @@ -359,6 +361,7 @@ export default { :disable-inline-editing="disableInlineEditing" :work-item-description="workItemDescription" :can-edit="canEdit" + :disable-truncation="disableTruncation" @startEditing="startEditing" @descriptionUpdated="handleDescriptionTextUpdated" /> diff --git a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue index 1699f6c419e3ad..12ed5bc5c9cc02 100644 --- a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue +++ b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue @@ -13,7 +13,13 @@ export default { components: { GlButton, }, + inject: ['workItemsMvc2'], props: { + disableTruncation: { + type: Boolean, + required: false, + default: false, + }, workItemDescription: { type: Object, required: true, @@ -30,6 +36,7 @@ export default { }, data() { return { + truncated: false, checkboxes: [], }; }, @@ -49,6 +56,9 @@ export default { showEditButton() { return this.canEdit && !this.disableInlineEditing; }, + isTruncated() { + return this.truncated && !this.disableTruncation && this.workItemsMvc2; + }, }, watch: { descriptionHtml: { @@ -58,6 +68,20 @@ export default { immediate: true, }, }, + mounted() { + this.$nextTick(() => { + const defaultMaxHeight = document.documentElement.clientHeight * 0.4; + let maxHeight; + if (defaultMaxHeight > 512) { + maxHeight = 512; + } else if (defaultMaxHeight < 256) { + maxHeight = 256; + } else { + maxHeight = defaultMaxHeight; + } + this.truncated = this.$refs['gfm-content'].clientHeight > maxHeight; + }); + }, methods: { async renderGFM() { await this.$nextTick(); @@ -105,6 +129,9 @@ export default { this.$emit('descriptionUpdated', newDescriptionText); } }, + showAll() { + this.truncated = false; + }, }, }; @@ -130,11 +157,28 @@ export default {
{{ __('None') }}
+ ref="description" + class="work-item-description md gl-mb-5 gl-min-h-8 gl-clearfix gl-relative" + > +
+
+
+ {{ __('Read more') }} +
+
+ diff --git a/app/assets/javascripts/work_items/components/work_item_description_show_all.vue b/app/assets/javascripts/work_items/components/work_item_description_show_all.vue new file mode 100644 index 00000000000000..388fb697991735 --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_description_show_all.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 495907bdd38b23..2aef1ea43553f8 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -77,6 +77,11 @@ export default { WorkItemLoading, }, mixins: [glFeatureFlagMixin()], + provide() { + return { + workItemsMvc2: this.workItemsMvc2Enabled, + }; + }, inject: ['fullPath', 'isGroup', 'reportAbusePath'], props: { isModal: { diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss index c140a13f8f2e9b..358925de81970c 100644 --- a/app/assets/stylesheets/page_bundles/work_items.scss +++ b/app/assets/stylesheets/page_bundles/work_items.scss @@ -387,4 +387,46 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; .work-item-sticky-header-text { max-width: $limited-layout-width; } -} \ No newline at end of file +} +.work-item-description .truncated{ + max-height: clamp(16rem, 40vh, 32rem); + overflow: hidden; +} + +.description-more{ + position: absolute; + bottom: 0; + pointer-events: none; + + &::before { + content: ''; + display: block; + height: 3rem; + width: 100%; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, $white 100%); + + .gl-dark & { + background: linear-gradient(180deg, rgba(31, 30, 36, 0.00) 0%, $gray-950 100%); + } + } + + .show-all-btn { + pointer-events: auto; + background-color: $white; + + .gl-dark & { + background-color: $gray-950; + } + + &::before, &::after { + content: ''; + height: 1px; + flex: 1; + border-top: 1px solid $gray-50; + + .gl-dark & { + border-top: 1px solid $gray-900; + } + } + } +} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f88e6f89ead38d..48ff0d13dbc7e1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -47792,6 +47792,9 @@ msgstr "" msgid "Show Pipeline IID" msgstr "" +msgid "Show all" +msgstr "" + msgid "Show all %{issuable_type}." msgstr "" diff --git a/spec/frontend/work_items/components/work_item_description_rendered_spec.js b/spec/frontend/work_items/components/work_item_description_rendered_spec.js index c4c88c7643f45a..a2a4cc9504c289 100644 --- a/spec/frontend/work_items/components/work_item_description_rendered_spec.js +++ b/spec/frontend/work_items/components/work_item_description_rendered_spec.js @@ -21,6 +21,8 @@ describe('WorkItemDescription', () => { workItemDescription = defaultWorkItemDescription, canEdit = false, disableInlineEditing = false, + mockComputed = {}, + hasWorkItemsMvc2 = false, } = {}) => { wrapper = shallowMount(WorkItemDescriptionRendered, { propsData: { @@ -28,6 +30,10 @@ describe('WorkItemDescription', () => { canEdit, disableInlineEditing, }, + computed: mockComputed, + provide: { + workItemsMvc2: hasWorkItemsMvc2, + }, }); }; @@ -39,6 +45,44 @@ describe('WorkItemDescription', () => { expect(renderGFM).toHaveBeenCalled(); }); + describe('with truncation', () => { + it('shows the untruncate action', () => { + createComponent({ + workItemDescription: { + description: 'This is a long description', + descriptionHtml: '

This is a long description

', + }, + mockComputed: { + isTruncated() { + return true; + }, + }, + hasWorkItemsMvc2: true, + }); + + expect(wrapper.find('.description-more').exists()).toBe(true); + }); + }); + + describe('without truncation', () => { + it('does not show the untruncate action', () => { + createComponent({ + workItemDescription: { + description: 'This is a long description', + descriptionHtml: '

This is a long description

', + }, + mockComputed: { + isTruncated() { + return false; + }, + }, + hasWorkItemsMvc2: true, + }); + + expect(wrapper.find('.description-more').exists()).toBe(false); + }); + }); + describe('with checkboxes', () => { beforeEach(() => { createComponent({ -- GitLab From cc925e3a3ef50e80b39d539ae249f07124a60693 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Wed, 27 Mar 2024 09:41:24 -0500 Subject: [PATCH 2/6] Code clean up --- .../work_item_description_rendered.vue | 25 +++++++++++-------- .../work_item_description_show_all.vue | 25 ------------------- locale/gitlab.pot | 3 --- 3 files changed, 14 insertions(+), 39 deletions(-) delete mode 100644 app/assets/javascripts/work_items/components/work_item_description_show_all.vue diff --git a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue index 12ed5bc5c9cc02..615208d5ed230a 100644 --- a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue +++ b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue @@ -57,7 +57,7 @@ export default { return this.canEdit && !this.disableInlineEditing; }, isTruncated() { - return this.truncated && !this.disableTruncation && this.workItemsMvc2; + return this.truncated; }, }, watch: { @@ -70,16 +70,7 @@ export default { }, mounted() { this.$nextTick(() => { - const defaultMaxHeight = document.documentElement.clientHeight * 0.4; - let maxHeight; - if (defaultMaxHeight > 512) { - maxHeight = 512; - } else if (defaultMaxHeight < 256) { - maxHeight = 256; - } else { - maxHeight = defaultMaxHeight; - } - this.truncated = this.$refs['gfm-content'].clientHeight > maxHeight; + this.truncateLongDescription(); }); }, methods: { @@ -129,6 +120,18 @@ export default { this.$emit('descriptionUpdated', newDescriptionText); } }, + truncateLongDescription() { + /* Truncate when description is > 40% viewport height or 512px. + Update `.work-item-description .truncated` max height if value changes. */ + const defaultMaxHeight = document.documentElement.clientHeight * 0.4; + let maxHeight = defaultMaxHeight; + if (defaultMaxHeight > 512) { + maxHeight = 512; + } else if (defaultMaxHeight < 256) { + maxHeight = 256; + } + this.truncated = this.$refs['gfm-content'].clientHeight > maxHeight; + }, showAll() { this.truncated = false; }, diff --git a/app/assets/javascripts/work_items/components/work_item_description_show_all.vue b/app/assets/javascripts/work_items/components/work_item_description_show_all.vue deleted file mode 100644 index 388fb697991735..00000000000000 --- a/app/assets/javascripts/work_items/components/work_item_description_show_all.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 48ff0d13dbc7e1..f88e6f89ead38d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -47792,9 +47792,6 @@ msgstr "" msgid "Show Pipeline IID" msgstr "" -msgid "Show all" -msgstr "" - msgid "Show all %{issuable_type}." msgstr "" -- GitLab From 5ccb1e87d85c678bc0dfd2b4210ff29fafdd23d2 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Mon, 1 Apr 2024 09:41:33 -0500 Subject: [PATCH 3/6] Apply review suggestions --- .../components/work_item_description_rendered.vue | 11 ++++------- .../work_items/components/work_item_detail.vue | 5 ----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue index 615208d5ed230a..7f731dbe9861f7 100644 --- a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue +++ b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue @@ -2,6 +2,7 @@ import { GlButton, GlTooltipDirective } from '@gitlab/ui'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; const isCheckbox = (target) => target?.classList.contains('task-list-item-checkbox'); @@ -13,7 +14,7 @@ export default { components: { GlButton, }, - inject: ['workItemsMvc2'], + mixins: [glFeatureFlagMixin()], props: { disableTruncation: { type: Boolean, @@ -57,7 +58,7 @@ export default { return this.canEdit && !this.disableInlineEditing; }, isTruncated() { - return this.truncated; + return this.truncated && !this.disableTruncation && this.glFeatures.workItemsMvc2; }, }, watch: { @@ -68,11 +69,6 @@ export default { immediate: true, }, }, - mounted() { - this.$nextTick(() => { - this.truncateLongDescription(); - }); - }, methods: { async renderGFM() { await this.$nextTick(); @@ -89,6 +85,7 @@ export default { checkbox.disabled = false; }); } + this.truncateLongDescription(); }, toggleCheckboxes(event) { const { target } = event; diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 2aef1ea43553f8..495907bdd38b23 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -77,11 +77,6 @@ export default { WorkItemLoading, }, mixins: [glFeatureFlagMixin()], - provide() { - return { - workItemsMvc2: this.workItemsMvc2Enabled, - }; - }, inject: ['fullPath', 'isGroup', 'reportAbusePath'], props: { isModal: { -- GitLab From bd9da15e55a288521f6bd692eefc64e0d5ff2f2a Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Tue, 2 Apr 2024 10:36:40 -0500 Subject: [PATCH 4/6] Fix merge conflict --- app/assets/stylesheets/page_bundles/work_items.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss index 358925de81970c..f33dc61c32e72f 100644 --- a/app/assets/stylesheets/page_bundles/work_items.scss +++ b/app/assets/stylesheets/page_bundles/work_items.scss @@ -388,6 +388,7 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; max-width: $limited-layout-width; } } + .work-item-description .truncated{ max-height: clamp(16rem, 40vh, 32rem); overflow: hidden; -- GitLab From 18dcc82366b7ca3df42166023921c42020c3c051 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Wed, 10 Apr 2024 19:42:39 +0000 Subject: [PATCH 5/6] Update test reference --- .../components/work_item_description_rendered.vue | 7 ++++++- .../components/work_item_description_rendered_spec.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue index 7f731dbe9861f7..7a1a272b2e419e 100644 --- a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue +++ b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue @@ -167,7 +167,12 @@ export default { :class="{ truncated: isTruncated }" @change="toggleCheckboxes" > -
+
{ hasWorkItemsMvc2: true, }); - expect(wrapper.find('.description-more').exists()).toBe(true); + expect(wrapper.find('[data-test-id="description-read-more"]').exists()).toBe(true); }); }); @@ -79,7 +79,7 @@ describe('WorkItemDescription', () => { hasWorkItemsMvc2: true, }); - expect(wrapper.find('.description-more').exists()).toBe(false); + expect(wrapper.find('[data-test-id="description-read-more"]').exists()).toBe(false); }); }); -- GitLab From f48e0af27832fa6e2ee6f61695a1ebc5ba261e93 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Wed, 10 Apr 2024 20:34:29 +0000 Subject: [PATCH 6/6] Remove extra line --- .../work_items/components/work_item_description_rendered.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue index 7a1a272b2e419e..c79905005cb8e8 100644 --- a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue +++ b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue @@ -167,7 +167,6 @@ export default { :class="{ truncated: isTruncated }" @change="toggleCheckboxes" >
-