Commit b6e70c8a authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'winh-timeline-entry-component' into 'master'

Extract shared timeline entry component

See merge request gitlab-org/gitlab-ce!23447
parents df027849 e3bddb62
...@@ -4,6 +4,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; ...@@ -4,6 +4,7 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import Autosize from 'autosize'; import Autosize from 'autosize';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import Autosave from '../../autosave'; import Autosave from '../../autosave';
import { import {
...@@ -30,6 +31,7 @@ export default { ...@@ -30,6 +31,7 @@ export default {
markdownField, markdownField,
userAvatarLink, userAvatarLink,
loadingButton, loadingButton,
TimelineEntryItem,
}, },
mixins: [issuableStateMixin], mixins: [issuableStateMixin],
props: { props: {
...@@ -309,137 +311,135 @@ Please check your network connection and try again.`; ...@@ -309,137 +311,135 @@ Please check your network connection and try again.`;
<div> <div>
<note-signed-out-widget v-if="!isLoggedIn" /> <note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget v-else-if="!canCreateNote" :issuable-type="issuableTypeTitle" /> <discussion-locked-widget v-else-if="!canCreateNote" :issuable-type="issuableTypeTitle" />
<div v-else-if="canCreateNote" class="notes notes-form timeline"> <ul v-else-if="canCreateNote" class="notes notes-form timeline">
<div class="timeline-entry note-form"> <timeline-entry-item class="note-form">
<div class="timeline-entry-inner"> <div class="flash-container error-alert timeline-content"></div>
<div class="flash-container error-alert timeline-content"></div> <div class="timeline-icon d-none d-sm-none d-md-block">
<div class="timeline-icon d-none d-sm-none d-md-block"> <user-avatar-link
<user-avatar-link v-if="author"
v-if="author" :link-href="author.path"
:link-href="author.path" :img-src="author.avatar_url"
:img-src="author.avatar_url" :img-alt="author.name"
:img-alt="author.name" :img-size="40"
:img-size="40" />
/> </div>
</div> <div class="timeline-content timeline-content-form">
<div class="timeline-content timeline-content-form"> <form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form">
<form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form"> <div class="error-alert"></div>
<div class="error-alert"></div>
<issue-warning <issue-warning
v-if="hasWarning(getNoteableData)" v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)" :is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)" :is-confidential="isConfidential(getNoteableData)"
/> />
<markdown-field <markdown-field
ref="markdownField" ref="markdownField"
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath" :quick-actions-docs-path="quickActionsDocsPath"
:markdown-version="markdownVersion" :markdown-version="markdownVersion"
:add-spacing-classes="false" :add-spacing-classes="false"
> >
<textarea <textarea
id="note-body" id="note-body"
ref="textarea" ref="textarea"
slot="textarea" slot="textarea"
v-model="note" v-model="note"
:disabled="isSubmitting" :disabled="isSubmitting"
name="note[note]" name="note[note]"
class="note-textarea js-vue-comment-form js-note-text class="note-textarea js-vue-comment-form js-note-text
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input" js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true" data-supports-quick-actions="true"
aria-label="Description" aria-label="Description"
placeholder="Write a comment or drag your files here…" placeholder="Write a comment or drag your files here…"
@keydown.up="editCurrentUserLastNote();" @keydown.up="editCurrentUserLastNote();"
@keydown.meta.enter="handleSave();" @keydown.meta.enter="handleSave();"
@keydown.ctrl.enter="handleSave();" @keydown.ctrl.enter="handleSave();"
> >
</textarea> </textarea>
</markdown-field> </markdown-field>
<div class="note-form-actions"> <div class="note-form-actions">
<div <div
class="float-left btn-group class="float-left btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
> >
<button <button
:disabled="isSubmitButtonDisabled" :disabled="isSubmitButtonDisabled"
class="btn btn-create comment-btn js-comment-button js-comment-submit-button class="btn btn-create comment-btn js-comment-button js-comment-submit-button
qa-comment-button" qa-comment-button"
type="submit" type="submit"
@click.prevent="handleSave();" @click.prevent="handleSave();"
> >
{{ __(commentButtonTitle) }} {{ __(commentButtonTitle) }}
</button> </button>
<button
:disabled="isSubmitButtonDisabled"
name="button"
type="button"
class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
data-display="static"
data-toggle="dropdown"
aria-label="Open comment type dropdown"
>
<i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
</button>
<ul class="note-type-dropdown dropdown-open-top dropdown-menu">
<li :class="{ 'droplab-item-selected': noteType === 'comment' }">
<button
type="button"
class="btn btn-transparent"
@click.prevent="setNoteType('comment');"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
<strong>Comment</strong>
<p>Add a general comment to this {{ noteableDisplayName }}.</p>
</div>
</button>
</li>
<li class="divider droplab-item-ignore"></li>
<li :class="{ 'droplab-item-selected': noteType === 'discussion' }">
<button
type="button"
class="btn btn-transparent qa-discussion-option"
@click.prevent="setNoteType('discussion');"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
<strong>Start discussion</strong>
<p>{{ startDiscussionDescription }}</p>
</div>
</button>
</li>
</ul>
</div>
<loading-button
v-if="canUpdateIssue"
:loading="isToggleStateButtonLoading"
:container-class="[
actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button',
]"
:disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
@click="handleSave(true);"
/>
<button <button
v-if="note.length" :disabled="isSubmitButtonDisabled"
name="button"
type="button" type="button"
class="btn btn-cancel js-note-discard" class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
@click="discard" data-display="static"
data-toggle="dropdown"
aria-label="Open comment type dropdown"
> >
Discard draft <i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
</button> </button>
<ul class="note-type-dropdown dropdown-open-top dropdown-menu">
<li :class="{ 'droplab-item-selected': noteType === 'comment' }">
<button
type="button"
class="btn btn-transparent"
@click.prevent="setNoteType('comment');"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
<strong>Comment</strong>
<p>Add a general comment to this {{ noteableDisplayName }}.</p>
</div>
</button>
</li>
<li class="divider droplab-item-ignore"></li>
<li :class="{ 'droplab-item-selected': noteType === 'discussion' }">
<button
type="button"
class="btn btn-transparent qa-discussion-option"
@click.prevent="setNoteType('discussion');"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
<strong>Start discussion</strong>
<p>{{ startDiscussionDescription }}</p>
</div>
</button>
</li>
</ul>
</div> </div>
</form>
</div> <loading-button
v-if="canUpdateIssue"
:loading="isToggleStateButtonLoading"
:container-class="[
actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button',
]"
:disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
@click="handleSave(true);"
/>
<button
v-if="note.length"
type="button"
class="btn btn-cancel js-note-discard"
@click="discard"
>
Discard draft
</button>
</div>
</form>
</div> </div>
</div> </timeline-entry-item>
</div> </ul>
</div> </div>
</template> </template>
...@@ -6,6 +6,7 @@ import { truncateSha } from '~/lib/utils/text_utility'; ...@@ -6,6 +6,7 @@ import { truncateSha } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import systemNote from '~/vue_shared/components/notes/system_note.vue'; import systemNote from '~/vue_shared/components/notes/system_note.vue';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
...@@ -37,6 +38,7 @@ export default { ...@@ -37,6 +38,7 @@ export default {
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
systemNote, systemNote,
TimelineEntryItem,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -301,162 +303,156 @@ Please check your network connection and try again.`; ...@@ -301,162 +303,156 @@ Please check your network connection and try again.`;
</script> </script>
<template> <template>
<li class="note note-discussion timeline-entry" :class="componentClassName"> <timeline-entry-item class="note note-discussion" :class="componentClassName">
<div class="timeline-entry-inner"> <div class="timeline-content">
<div class="timeline-content"> <div :data-discussion-id="discussion.id" class="discussion js-discussion-container">
<div :data-discussion-id="discussion.id" class="discussion js-discussion-container"> <div v-if="shouldRenderDiffs" class="discussion-header note-wrapper">
<div v-if="shouldRenderDiffs" class="discussion-header note-wrapper"> <div v-once class="timeline-icon">
<div v-once class="timeline-icon"> <user-avatar-link
<user-avatar-link v-if="author"
v-if="author" :link-href="author.path"
:link-href="author.path" :img-src="author.avatar_url"
:img-src="author.avatar_url" :img-alt="author.name"
:img-alt="author.name" :img-size="40"
:img-size="40"
/>
</div>
<note-header
:author="author"
:created-at="initialDiscussion.created_at"
:note-id="initialDiscussion.id"
:include-toggle="true"
:expanded="discussion.expanded"
@toggleHandler="toggleDiscussionHandler"
>
<span v-html="actionText"></span>
</note-header>
<note-edited-text
v-if="discussion.resolved"
:edited-at="discussion.resolved_at"
:edited-by="discussion.resolved_by"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline"
/>
<note-edited-text
v-else-if="lastUpdatedAt"
:edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy"
action-text="Last updated"
class-name="discussion-headline-light js-discussion-headline"
/> />
</div> </div>
<div v-if="shouldShowDiscussions" class="discussion-body"> <note-header
<component :author="author"
:is="wrapperComponent" :created-at="initialDiscussion.created_at"
v-bind="wrapperComponentProps" :note-id="initialDiscussion.id"
class="card discussion-wrapper" :include-toggle="true"
> :expanded="discussion.expanded"
<div class="discussion-notes"> @toggleHandler="toggleDiscussionHandler"
<ul class="notes"> >
<template v-if="shouldGroupReplies"> <span v-html="actionText"></span>
<component </note-header>
:is="componentName(initialDiscussion)" <note-edited-text
:note="componentData(initialDiscussion)" v-if="discussion.resolved"
@handleDeleteNote="deleteNoteHandler" :edited-at="discussion.resolved_at"
> :edited-by="discussion.resolved_by"
<slot slot="avatar-badge" name="avatar-badge"></slot> :action-text="resolvedText"
</component> class-name="discussion-headline-light js-discussion-headline"
<toggle-replies-widget />
v-if="hasReplies" <note-edited-text
:collapsed="isRepliesCollapsed" v-else-if="lastUpdatedAt"
:replies="replies" :edited-at="lastUpdatedAt"
@toggle="toggleReplies" :edited-by="lastUpdatedBy"
/> action-text="Last updated"
<template v-if="!isRepliesCollapsed"> class-name="discussion-headline-light js-discussion-headline"
<component />
:is="componentName(note)" </div>
v-for="note in replies" <div v-if="shouldShowDiscussions" class="discussion-body">
:key="note.id" <component
:note="componentData(note)" :is="wrapperComponent"
@handleDeleteNote="deleteNoteHandler" v-bind="wrapperComponentProps"
/> class="card discussion-wrapper"
</template> >
</template> <div class="discussion-notes">
<template v-else> <ul class="notes">
<template v-if="shouldGroupReplies">
<component
:is="componentName(initialDiscussion)"
:note="componentData(initialDiscussion)"
@handleDeleteNote="deleteNoteHandler"
>
<slot slot="avatar-badge" name="avatar-badge"></slot>
</component>
<toggle-replies-widget
v-if="hasReplies"
:collapsed="isRepliesCollapsed"
:replies="replies"
@toggle="toggleReplies"
/>
<template v-if="!isRepliesCollapsed">
<component <component
:is="componentName(note)" :is="componentName(note)"
v-for="(note, index) in discussion.notes" v-for="note in replies"
:key="note.id" :key="note.id"
:note="componentData(note)" :note="componentData(note)"
@handleDeleteNote="deleteNoteHandler" @handleDeleteNote="deleteNoteHandler"
> />
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
</component>
</template> </template>
</ul> </template>
<div <template v-else>
v-if="!isRepliesCollapsed" <component
:class="{ 'is-replying': isReplying }" :is="componentName(note)"
class="discussion-reply-holder" v-for="(note, index) in discussion.notes"
> :key="note.id"
<template v-if="!isReplying && canReply"> :note="componentData(note)"
<div class="discussion-with-resolve-btn"> @handleDeleteNote="deleteNoteHandler"
>
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
</component>
</template>
</ul>
<div
v-if="!isRepliesCollapsed"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
<template v-if="!isReplying && canReply">
<div class="discussion-with-resolve-btn">
<button
type="button"
class="js-vue-discussion-reply btn btn-text-field mr-sm-2 qa-discussion-reply"
title="Add a reply"
@click="showReplyForm"
>
Reply...
</button>
<div v-if="discussion.resolvable">
<button <button
type="button" type="button"
class="js-vue-discussion-reply btn btn-text-field mr-sm-2 qa-discussion-reply" class="btn btn-default mr-sm-2"
title="Add a reply" @click="resolveHandler();"
@click="showReplyForm"
> >
Reply... <i v-if="isResolving" aria-hidden="true" class="fa fa-spinner fa-spin"></i>
{{ resolveButtonTitle }}
</button> </button>
<div v-if="discussion.resolvable"> </div>
<div
v-if="discussion.resolvable"
class="btn-group discussion-actions ml-sm-2"
role="group"
>
<div v-if="!discussionResolved" class="btn-group" role="group">
<a
v-gl-tooltip
:href="discussion.resolve_with_issue_path"
:title="s__('MergeRequests|Resolve this discussion in a new issue')"
class="new-issue-for-discussion btn btn-default discussion-create-issue-btn"
>
<icon name="issue-new" />
</a>
</div>
<div v-if="hasUnresolvedDiscussions" class="btn-group" role="group">
<button <button
type="button" v-gl-tooltip
class="btn btn-default mr-sm-2" class="btn btn-default discussion-next-btn"
@click="resolveHandler();" title="Jump to next unresolved discussion"
@click="jumpToNextDiscussion"
> >
<i <icon name="comment-next" />
v-if="isResolving"
aria-hidden="true"
class="fa fa-spinner fa-spin"
></i>
{{ resolveButtonTitle }}
</button> </button>
</div> </div>
<div
v-if="discussion.resolvable"
class="btn-group discussion-actions ml-sm-2"
role="group"
>
<div v-if="!discussionResolved" class="btn-group" role="group">
<a
v-gl-tooltip
:href="discussion.resolve_with_issue_path"
:title="s__('MergeRequests|Resolve this discussion in a new issue')"
class="new-issue-for-discussion btn btn-default discussion-create-issue-btn"
>
<icon name="issue-new" />
</a>
</div>
<div v-if="hasUnresolvedDiscussions" class="btn-group" role="group">
<button
v-gl-tooltip
class="btn btn-default discussion-next-btn"
title="Jump to next unresolved discussion"
@click="jumpToNextDiscussion"
>
<icon name="comment-next" />
</button>
</div>
</div>
</div> </div>
</template> </div>
<note-form </template>
v-if="isReplying" <note-form
ref="noteForm" v-if="isReplying"
:discussion="discussion" ref="noteForm"
:is-editing="false" :discussion="discussion"
save-button-title="Comment" :is-editing="false"
@handleFormUpdate="saveReply" save-button-title="Comment"
@cancelForm="cancelReplyForm" @handleFormUpdate="saveReply"
/> @cancelForm="cancelReplyForm"
<note-signed-out-widget v-if="!canReply" /> />
</div> <note-signed-out-widget v-if="!canReply" />
</div> </div>
</component> </div>
</div> </component>
</div> </div>
</div> </div>
</div> </div>
</li> </timeline-entry-item>
</template> </template>
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore'; import { escape } from 'underscore';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
...@@ -18,6 +19,7 @@ export default { ...@@ -18,6 +19,7 @@ export default {
noteHeader, noteHeader,
noteActions, noteActions,
noteBody, noteBody,
TimelineEntryItem,
}, },
mixins: [noteable, resolvable], mixins: [noteable, resolvable],
props: { props: {
...@@ -169,62 +171,60 @@ export default { ...@@ -169,62 +171,60 @@ export default {
</script> </script>
<template> <template>
<li <timeline-entry-item
:id="noteAnchorId" :id="noteAnchorId"
:class="classNameBindings" :class="classNameBindings"
:data-award-url="note.toggle_award_path" :data-award-url="note.toggle_award_path"
:data-note-id="note.id" :data-note-id="note.id"
class="note timeline-entry note-wrapper" class="note note-wrapper"
> >
<div class="timeline-entry-inner"> <div v-once class="timeline-icon">
<div v-once class="timeline-icon"> <user-avatar-link
<user-avatar-link :link-href="author.path"
:link-href="author.path" :img-src="author.avatar_url"
:img-src="author.avatar_url" :img-alt="author.name"
:img-alt="author.name" :img-size="40"
:img-size="40" >
> <slot slot="avatar-badge" name="avatar-badge"> </slot>
<slot slot="avatar-badge" name="avatar-badge"> </slot> </user-avatar-link>
</user-avatar-link> </div>
</div> <div class="timeline-content">
<div class="timeline-content"> <div class="note-header">
<div class="note-header"> <note-header
<note-header v-once
v-once :author="author"
:author="author" :created-at="note.created_at"
:created-at="note.created_at" :note-id="note.id"
:note-id="note.id" action-text="commented"
action-text="commented" />
/> <note-actions
<note-actions :author-id="author.id"
:author-id="author.id" :note-id="note.id"
:note-id="note.id" :note-url="note.noteable_note_url"
:note-url="note.noteable_note_url" :access-level="note.human_access"
:access-level="note.human_access"
:can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
:can-resolve="note.current_user.can_resolve"
:report-abuse-path="note.report_abuse_path"
:resolvable="note.resolvable"
:is-resolved="note.resolved"
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
/>
</div>
<note-body
ref="noteBody"
:note="note"
:can-edit="note.current_user.can_edit" :can-edit="note.current_user.can_edit"
:is-editing="isEditing" :can-award-emoji="note.current_user.can_award_emoji"
@handleFormUpdate="formUpdateHandler" :can-delete="note.current_user.can_edit"
@cancelForm="formCancelHandler" :can-report-as-abuse="canReportAsAbuse"
:can-resolve="note.current_user.can_resolve"
:report-abuse-path="note.report_abuse_path"
:resolvable="note.resolvable"
:is-resolved="note.resolved"
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
/> />
</div> </div>
<note-body
ref="noteBody"
:note="note"
:can-edit="note.current_user.can_edit"
:is-editing="isEditing"
@handleFormUpdate="formUpdateHandler"
@cancelForm="formCancelHandler"
/>
</div> </div>
</li> </timeline-entry-item>
</template> </template>
...@@ -17,12 +17,14 @@ ...@@ -17,12 +17,14 @@
* /> * />
*/ */
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import userAvatarLink from '../user_avatar/user_avatar_link.vue'; import userAvatarLink from '../user_avatar/user_avatar_link.vue';
export default { export default {
name: 'PlaceholderNote', name: 'PlaceholderNote',
components: { components: {
userAvatarLink, userAvatarLink,
TimelineEntryItem,
}, },
props: { props: {
note: { note: {
...@@ -37,30 +39,28 @@ export default { ...@@ -37,30 +39,28 @@ export default {
</script> </script>
<template> <template>
<li class="note being-posted fade-in-half timeline-entry"> <timeline-entry-item class="note being-posted fade-in-half">
<div class="timeline-entry-inner"> <div class="timeline-icon">
<div class="timeline-icon"> <user-avatar-link
<user-avatar-link :link-href="getUserData.path"
:link-href="getUserData.path" :img-src="getUserData.avatar_url"
:img-src="getUserData.avatar_url" :img-size="40"
:img-size="40" />
/> </div>
</div> <div :class="{ discussion: !note.individual_note }" class="timeline-content">
<div :class="{ discussion: !note.individual_note }" class="timeline-content"> <div class="note-header">
<div class="note-header"> <div class="note-header-info">
<div class="note-header-info"> <a :href="getUserData.path">
<a :href="getUserData.path"> <span class="d-none d-sm-inline-block">{{ getUserData.name }}</span>
<span class="d-none d-sm-inline-block">{{ getUserData.name }}</span> <span class="note-headline-light">@{{ getUserData.username }}</span>
<span class="note-headline-light">@{{ getUserData.username }}</span> </a>
</a>
</div>
</div> </div>
<div class="note-body"> </div>
<div class="note-text"> <div class="note-body">
<p>{{ note.body }}</p> <div class="note-text">
</div> <p>{{ note.body }}</p>
</div> </div>
</div> </div>
</div> </div>
</li> </timeline-entry-item>
</template> </template>
<script> <script>
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
/** /**
* Common component to render a placeholder system note. * Common component to render a placeholder system note.
* *
...@@ -9,6 +11,9 @@ ...@@ -9,6 +11,9 @@
*/ */
export default { export default {
name: 'PlaceholderSystemNote', name: 'PlaceholderSystemNote',
components: {
TimelineEntryItem,
},
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -19,11 +24,9 @@ export default { ...@@ -19,11 +24,9 @@ export default {
</script> </script>
<template> <template>
<li class="note system-note timeline-entry being-posted fade-in-half"> <timeline-entry-item class="note system-note being-posted fade-in-half">
<div class="timeline-entry-inner"> <div class="timeline-content">
<div class="timeline-content"> <em>{{ note.body }}</em>
<em>{{ note.body }}</em>
</div>
</div> </div>
</li> </timeline-entry-item>
</template> </template>
<script> <script>
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
export default { export default {
name: 'SkeletonNote', name: 'SkeletonNote',
components: { components: {
GlSkeletonLoading, GlSkeletonLoading,
TimelineEntryItem,
}, },
}; };
</script> </script>
<template> <template>
<li class="timeline-entry note note-wrapper"> <timeline-entry-item class="note note-wrapper">
<div class="timeline-entry-inner"> <div class="timeline-icon"></div>
<div class="timeline-icon"></div> <div class="timeline-content">
<div class="timeline-content"> <div class="note-header"></div>
<div class="note-header"></div> <div class="note-body"><gl-skeleton-loading /></div>
<div class="note-body"><gl-skeleton-loading /></div>
</div>
</div> </div>
</li> </timeline-entry-item>
</template> </template>
...@@ -20,6 +20,7 @@ import $ from 'jquery'; ...@@ -20,6 +20,7 @@ import $ from 'jquery';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import noteHeader from '~/notes/components/note_header.vue'; import noteHeader from '~/notes/components/note_header.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from './timeline_entry_item.vue';
import { spriteIcon } from '../../../lib/utils/common_utils'; import { spriteIcon } from '../../../lib/utils/common_utils';
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
...@@ -29,6 +30,7 @@ export default { ...@@ -29,6 +30,7 @@ export default {
components: { components: {
Icon, Icon,
noteHeader, noteHeader,
TimelineEntryItem,
}, },
props: { props: {
note: { note: {
...@@ -73,36 +75,34 @@ export default { ...@@ -73,36 +75,34 @@ export default {
</script> </script>
<template> <template>
<li <timeline-entry-item
:id="noteAnchorId" :id="noteAnchorId"
:class="{ target: isTargetNote }" :class="{ target: isTargetNote }"
class="note system-note timeline-entry note-wrapper" class="note system-note note-wrapper"
> >
<div class="timeline-entry-inner"> <div class="timeline-icon" v-html="iconHtml"></div>
<div class="timeline-icon" v-html="iconHtml"></div> <div class="timeline-content">
<div class="timeline-content"> <div class="note-header">
<div class="note-header"> <note-header :author="note.author" :created-at="note.created_at" :note-id="note.id">
<note-header :author="note.author" :created-at="note.created_at" :note-id="note.id"> <span v-html="actionTextHtml"></span>
<span v-html="actionTextHtml"></span> </note-header>
</note-header> </div>
</div> <div class="note-body">
<div class="note-body"> <div
<div :class="{
:class="{ 'system-note-commit-list': hasMoreCommits,
'system-note-commit-list': hasMoreCommits, 'hide-shade': expanded,
'hide-shade': expanded, }"
}" class="note-text"
class="note-text" v-html="note.note_html"
v-html="note.note_html" ></div>
></div> <div v-if="hasMoreCommits" class="flex-list">
<div v-if="hasMoreCommits" class="flex-list"> <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded;">
<div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded;"> <icon :name="toggleIcon" :size="8" class="append-right-5" />
<icon :name="toggleIcon" :size="8" class="append-right-5" /> <span>Toggle commit list</span>
<span>Toggle commit list</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</li> </timeline-entry-item>
</template> </template>
<script>
export default {
name: 'TimelineEntryItem',
};
</script>
<template>
<li class="timeline-entry">
<div class="timeline-entry-inner"><slot></slot></div>
</li>
</template>
import { shallowMount, createLocalVue } from '@vue/test-utils';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
describe(TimelineEntryItem.name, () => {
let wrapper;
const factory = (options = {}) => {
const localVue = createLocalVue();
wrapper = shallowMount(TimelineEntryItem, {
localVue,
...options,
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders correctly', () => {
factory();
expect(wrapper.is('.timeline-entry')).toBe(true);
expect(wrapper.contains('.timeline-entry-inner')).toBe(true);
});
it('accepts default slot', () => {
const dummyContent = '<p>some content</p>';
factory({
slots: {
default: dummyContent,
},
});
const content = wrapper.find('.timeline-entry-inner :first-child');
expect(content.html()).toBe(dummyContent);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment