diff options
Diffstat (limited to 'app/assets/javascripts')
19 files changed, 372 insertions, 38 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 8fc8a8d0495..8ea443814e9 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -220,9 +220,6 @@ export default { this.assignedDiscussions = false; this.fetchData(false); }, - isLatestVersion() { - return window.location.search.indexOf('diff_id') === -1; - }, startDiffRendering() { requestIdleCallback( () => { @@ -232,7 +229,7 @@ export default { ); }, fetchData(toggleTree = true) { - if (this.isLatestVersion() && this.glFeatures.diffsBatchLoad) { + if (this.glFeatures.diffsBatchLoad) { this.fetchDiffFilesMeta() .then(() => { if (toggleTree) this.hideTreeListIfJustOneFile(); diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 44672659f56..992b45c97ac 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -90,14 +90,13 @@ export const fetchDiffFiles = ({ state, commit }) => { }; export const fetchDiffFilesBatch = ({ commit, state }) => { - const baseUrl = `${state.endpointBatch}?per_page=${DIFFS_PER_PAGE}`; - const url = page => (page ? `${baseUrl}&page=${page}` : baseUrl); - commit(types.SET_BATCH_LOADING, true); const getBatch = page => axios - .get(url(page)) + .get(state.endpointBatch, { + params: { page, per_page: DIFFS_PER_PAGE, w: state.showWhitespace ? '0' : '1' }, + }) .then(({ data: { pagination, diff_files } }) => { commit(types.SET_DIFF_DATA_BATCH, { diff_files }); commit(types.SET_BATCH_LOADING, false); diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index a176fd0aca8..bb8374b4e78 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -115,5 +115,30 @@ export const isOnDefaultBranch = (_state, getters) => export const canPushToBranch = (_state, getters) => getters.currentBranch && getters.currentBranch.can_push; +export const isFileDeletedAndReadded = (state, getters) => path => { + const stagedFile = getters.getStagedFile(path); + const file = state.entries[path]; + return Boolean(stagedFile && stagedFile.deleted && file.tempFile); +}; + +// checks if any diff exists in the staged or unstaged changes for this path +export const getDiffInfo = (state, getters) => path => { + const stagedFile = getters.getStagedFile(path); + const file = state.entries[path]; + const renamed = file.prevPath ? file.path !== file.prevPath : false; + const deletedAndReadded = getters.isFileDeletedAndReadded(path); + const deleted = deletedAndReadded ? false : file.deleted; + const tempFile = deletedAndReadded ? false : file.tempFile; + const changed = file.content !== (deletedAndReadded ? stagedFile.raw : file.raw); + + return { + exists: changed || renamed || deleted || tempFile, + changed, + renamed, + deleted, + tempFile, + }; +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js index c35b9c30058..738bf08f1bf 100644 --- a/app/assets/javascripts/pages/projects/snippets/show/index.js +++ b/app/assets/javascripts/pages/projects/snippets/show/index.js @@ -3,11 +3,16 @@ import ZenMode from '~/zen_mode'; import LineHighlighter from '~/line_highlighter'; import BlobViewer from '~/blob/viewer'; import snippetEmbed from '~/snippet/snippet_embed'; +import initSnippetsApp from '~/snippets'; document.addEventListener('DOMContentLoaded', () => { - new LineHighlighter(); // eslint-disable-line no-new - new BlobViewer(); // eslint-disable-line no-new - initNotes(); - new ZenMode(); // eslint-disable-line no-new - snippetEmbed(); + if (!gon.features.snippetsVue) { + new LineHighlighter(); // eslint-disable-line no-new + new BlobViewer(); // eslint-disable-line no-new + initNotes(); + new ZenMode(); // eslint-disable-line no-new + snippetEmbed(); + } else { + initSnippetsApp(); + } }); diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index f6b9ea5d30d..e1382aa86d9 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -104,10 +104,10 @@ export default { return acc.concat({ name, path, - to: `/tree/${this.ref}${path}`, + to: `/-/tree/${this.ref}${path}`, }); }, - [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }], + [{ name: this.projectShortPath, path: '/', to: `/-/tree/${this.ref}/` }], ); }, canCreateMrFromFork() { diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue index 3c39f404226..38aa672bc1c 100644 --- a/app/assets/javascripts/repository/components/table/parent_row.vue +++ b/app/assets/javascripts/repository/components/table/parent_row.vue @@ -15,7 +15,7 @@ export default { const splitArray = this.path.split('/'); splitArray.pop(); - return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` }; + return { path: `/-/tree/${this.commitRef}/${splitArray.join('/')}` }; }, }, methods: { diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index cf0457a2abf..57dd08c2142 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -84,7 +84,7 @@ export default { }, computed: { routerLinkTo() { - return this.isFolder ? { path: `/tree/${this.ref}/${this.path}` } : null; + return this.isFolder ? { path: `/-/tree/${this.ref}/${this.path}` } : null; }, iconName() { return `fa-${getIconName(this.type, this.path)}`; diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js index ebf0a7091ea..fa544444be8 100644 --- a/app/assets/javascripts/repository/router.js +++ b/app/assets/javascripts/repository/router.js @@ -12,7 +12,7 @@ export default function createRouter(base, baseRef) { base: joinPaths(gon.relative_url_root || '', base), routes: [ { - path: `/tree/${baseRef}(/.*)?`, + path: `/-/tree/${baseRef}(/.*)?`, name: 'treePath', component: TreePage, props: route => ({ diff --git a/app/assets/javascripts/snippets/components/app.vue b/app/assets/javascripts/snippets/components/app.vue index e3d6cdd4606..bd2cb8e4595 100644 --- a/app/assets/javascripts/snippets/components/app.vue +++ b/app/assets/javascripts/snippets/components/app.vue @@ -1,10 +1,16 @@ <script> -import getSnippet from '../queries/getSnippet.query.graphql'; +import GetSnippetQuery from '../queries/snippet.query.graphql'; +import SnippetHeader from './snippet_header.vue'; +import { GlLoadingIcon } from '@gitlab/ui'; export default { + components: { + SnippetHeader, + GlLoadingIcon, + }, apollo: { - snippetData: { - query: getSnippet, + snippet: { + query: GetSnippetQuery, variables() { return { ids: this.snippetGid, @@ -21,11 +27,24 @@ export default { }, data() { return { - snippetData: {}, + snippet: {}, }; }, + computed: { + isLoading() { + return this.$apollo.queries.snippet.loading; + }, + }, }; </script> <template> - <div class="js-snippet-view"></div> + <div class="js-snippet-view"> + <gl-loading-icon + v-if="isLoading" + :label="__('Loading snippet')" + :size="2" + class="loading-animation prepend-top-20 append-bottom-20" + /> + <snippet-header v-else :snippet="snippet" /> + </div> </template> diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue new file mode 100644 index 00000000000..e8f1bfeaf43 --- /dev/null +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -0,0 +1,241 @@ +<script> +import { __ } from '~/locale'; +import { + GlAvatar, + GlIcon, + GlSprintf, + GlButton, + GlModal, + GlAlert, + GlLoadingIcon, + GlDropdown, + GlDropdownItem, +} from '@gitlab/ui'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + +import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql'; +import CanCreatePersonalSnippet from '../queries/userPermissions.query.graphql'; +import CanCreateProjectSnippet from '../queries/projectPermissions.query.graphql'; + +export default { + components: { + GlAvatar, + GlIcon, + GlSprintf, + GlButton, + GlModal, + GlAlert, + GlLoadingIcon, + GlDropdown, + GlDropdownItem, + TimeAgoTooltip, + }, + apollo: { + canCreateSnippet: { + query() { + return this.snippet.project ? CanCreateProjectSnippet : CanCreatePersonalSnippet; + }, + variables() { + return { + fullPath: this.snippet.project ? this.snippet.project.fullPath : undefined, + }; + }, + update(data) { + return this.snippet.project + ? data.project.userPermissions.createSnippet + : data.currentUser.userPermissions.createSnippet; + }, + }, + }, + props: { + snippet: { + type: Object, + required: true, + }, + }, + data() { + return { + isDeleting: false, + errorMessage: '', + canCreateSnippet: false, + }; + }, + computed: { + personalSnippetActions() { + return [ + { + condition: this.snippet.userPermissions.updateSnippet, + text: __('Edit'), + href: this.editLink, + click: undefined, + variant: 'outline-info', + cssClass: undefined, + }, + { + condition: this.snippet.userPermissions.adminSnippet, + text: __('Delete'), + href: undefined, + click: this.showDeleteModal, + variant: 'outline-danger', + cssClass: 'btn-inverted btn-danger ml-2', + }, + { + condition: this.canCreateSnippet, + text: __('New snippet'), + href: this.snippet.project + ? `${this.snippet.project.webUrl}/snippets/new` + : '/snippets/new', + click: undefined, + variant: 'outline-success', + cssClass: 'btn-inverted btn-success ml-2', + }, + ]; + }, + editLink() { + return `${this.snippet.webUrl}/edit`; + }, + visibility() { + return this.snippet.visibilityLevel; + }, + snippetVisibilityLevelDescription() { + switch (this.visibility) { + case 'private': + return this.snippet.project !== null + ? __('The snippet is visible only to project members.') + : __('The snippet is visible only to me.'); + case 'internal': + return __('The snippet is visible to any logged in user.'); + default: + return __('The snippet can be accessed without any authentication.'); + } + }, + visibilityLevelIcon() { + switch (this.visibility) { + case 'private': + return 'lock'; + case 'internal': + return 'shield'; + default: + return 'earth'; + } + }, + }, + methods: { + redirectToSnippets() { + window.location.pathname = 'dashboard/snippets'; + }, + closeDeleteModal() { + this.$refs.deleteModal.hide(); + }, + showDeleteModal() { + this.$refs.deleteModal.show(); + }, + deleteSnippet() { + this.isDeleting = true; + this.$apollo + .mutate({ + mutation: DeleteSnippetMutation, + variables: { id: this.snippet.id }, + }) + .then(() => { + this.isDeleting = false; + this.errorMessage = undefined; + this.closeDeleteModal(); + this.redirectToSnippets(); + }) + .catch(err => { + this.isDeleting = false; + this.errorMessage = err.message; + }); + }, + }, +}; +</script> +<template> + <div class="detail-page-header"> + <div class="detail-page-header-body"> + <div + class="snippet-box qa-snippet-box has-tooltip d-flex align-items-center append-right-5 mb-1" + :title="snippetVisibilityLevelDescription" + data-container="body" + > + <span class="sr-only"> + {{ s__(`VisibilityLevel|${visibility}`) }} + </span> + <gl-icon :name="visibilityLevelIcon" :size="14" /> + </div> + <div class="creator"> + <gl-sprintf message="Authored %{timeago} by %{author}"> + <template #timeago> + <time-ago-tooltip + :time="snippet.createdAt" + tooltip-placement="bottom" + css-class="snippet_updated_ago" + /> + </template> + <template #author> + <a :href="snippet.author.webUrl" class="d-inline"> + <gl-avatar :size="24" :src="snippet.author.avatarUrl" /> + <span class="bold">{{ snippet.author.name }}</span> + </a> + </template> + </gl-sprintf> + </div> + </div> + + <div class="detail-page-header-actions"> + <div class="d-none d-sm-block"> + <template v-for="(action, index) in personalSnippetActions"> + <gl-button + v-if="action.condition" + :key="index" + :variant="action.variant" + :class="action.cssClass" + :href="action.href || undefined" + @click="action.click ? action.click() : undefined" + > + {{ action.text }} + </gl-button> + </template> + </div> + <div class="d-block d-sm-none dropdown"> + <gl-dropdown :text="__('Options')" class="w-100" toggle-class="text-center"> + <gl-dropdown-item + v-for="(action, index) in personalSnippetActions" + :key="index" + :href="action.href || undefined" + @click="action.click ? action.click() : undefined" + >{{ action.text }}</gl-dropdown-item + > + </gl-dropdown> + </div> + </div> + + <gl-modal ref="deleteModal" modal-id="delete-modal" title="Example title"> + <template #modal-title>{{ __('Delete snippet?') }}</template> + + <gl-alert v-if="errorMessage" variant="danger" class="mb-2" @dismiss="errorMessage = ''">{{ + errorMessage + }}</gl-alert> + + <gl-sprintf message="Are you sure you want to delete %{name}?"> + <template #name + ><strong>{{ snippet.title }}</strong></template + > + </gl-sprintf> + + <template #modal-footer> + <gl-button @click="closeDeleteModal">{{ __('Cancel') }}</gl-button> + <gl-button + variant="danger" + :disabled="isDeleting" + data-qa-selector="delete_snippet_button" + @click="deleteSnippet" + > + <gl-loading-icon v-if="isDeleting" inline /> + {{ __('Delete snippet') }} + </gl-button> + </template> + </gl-modal> + </div> +</template> diff --git a/app/assets/javascripts/snippets/fragments/author.fragment.graphql b/app/assets/javascripts/snippets/fragments/author.fragment.graphql new file mode 100644 index 00000000000..2684bd0fa37 --- /dev/null +++ b/app/assets/javascripts/snippets/fragments/author.fragment.graphql @@ -0,0 +1,8 @@ +fragment Author on Snippet { + author { + name, + avatarUrl, + username, + webUrl + } +}
\ No newline at end of file diff --git a/app/assets/javascripts/snippets/fragments/project.fragment.graphql b/app/assets/javascripts/snippets/fragments/project.fragment.graphql new file mode 100644 index 00000000000..7d65789c67b --- /dev/null +++ b/app/assets/javascripts/snippets/fragments/project.fragment.graphql @@ -0,0 +1,6 @@ +fragment Project on Snippet { + project { + fullPath + webUrl + } +}
\ No newline at end of file diff --git a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql new file mode 100644 index 00000000000..57348a422ec --- /dev/null +++ b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql @@ -0,0 +1,13 @@ +fragment SnippetBase on Snippet { + id + title + description + createdAt + updatedAt + visibilityLevel + webUrl + userPermissions { + adminSnippet + updateSnippet + } +}
\ No newline at end of file diff --git a/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql new file mode 100644 index 00000000000..0c829cbdee6 --- /dev/null +++ b/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql @@ -0,0 +1,5 @@ +mutation DeleteSnippet($id: ID!) { + destroySnippet(input: {id: $id}) { + errors + } +}
\ No newline at end of file diff --git a/app/assets/javascripts/snippets/queries/getSnippet.query.graphql b/app/assets/javascripts/snippets/queries/getSnippet.query.graphql deleted file mode 100644 index 5a5f0d05c5b..00000000000 --- a/app/assets/javascripts/snippets/queries/getSnippet.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -query getSnippet($ids: [ID!]) { - snippets(ids: $ids) { - edges { - node { - title - description - createdAt - updatedAt - visibility - } - } - } -} diff --git a/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql b/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql new file mode 100644 index 00000000000..288bd0889bf --- /dev/null +++ b/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql @@ -0,0 +1,7 @@ +query CanCreateProjectSnippet($fullPath: ID!) { + project(fullPath: $fullPath) { + userPermissions { + createSnippet + } + } +}
\ No newline at end of file diff --git a/app/assets/javascripts/snippets/queries/snippet.query.graphql b/app/assets/javascripts/snippets/queries/snippet.query.graphql new file mode 100644 index 00000000000..1cb2c86c4d8 --- /dev/null +++ b/app/assets/javascripts/snippets/queries/snippet.query.graphql @@ -0,0 +1,15 @@ +#import '../fragments/snippetBase.fragment.graphql' +#import '../fragments/project.fragment.graphql' +#import '../fragments/author.fragment.graphql' + +query GetSnippetQuery($ids: [ID!]) { + snippets(ids: $ids) { + edges { + node { + ...SnippetBase + ...Project + ...Author + } + } + } +} diff --git a/app/assets/javascripts/snippets/queries/userPermissions.query.graphql b/app/assets/javascripts/snippets/queries/userPermissions.query.graphql new file mode 100644 index 00000000000..f5b97b3d0f0 --- /dev/null +++ b/app/assets/javascripts/snippets/queries/userPermissions.query.graphql @@ -0,0 +1,7 @@ +query CanCreatePersonalSnippet { + currentUser { + userPermissions { + createSnippet + } + } +}
\ No newline at end of file diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue index b874bedab36..bf3c3666300 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue @@ -79,10 +79,10 @@ export default { return this.projectPath.indexOf('/') === 0 ? '' : `${gon.relative_url_root}/`; }, fullOldPath() { - return `${this.basePath}${this.projectPath}/raw/${this.oldSha}/${this.oldPath}`; + return `${this.basePath}${this.projectPath}/-/raw/${this.oldSha}/${this.oldPath}`; }, fullNewPath() { - return `${this.basePath}${this.projectPath}/raw/${this.newSha}/${this.newPath}`; + return `${this.basePath}${this.projectPath}/-/raw/${this.newSha}/${this.newPath}`; }, }, }; |