summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/diffs/components/app.vue5
-rw-r--r--app/assets/javascripts/diffs/store/actions.js7
-rw-r--r--app/assets/javascripts/ide/stores/getters.js25
-rw-r--r--app/assets/javascripts/pages/projects/snippets/show/index.js15
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue4
-rw-r--r--app/assets/javascripts/repository/components/table/parent_row.vue2
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue2
-rw-r--r--app/assets/javascripts/repository/router.js2
-rw-r--r--app/assets/javascripts/snippets/components/app.vue29
-rw-r--r--app/assets/javascripts/snippets/components/snippet_header.vue241
-rw-r--r--app/assets/javascripts/snippets/fragments/author.fragment.graphql8
-rw-r--r--app/assets/javascripts/snippets/fragments/project.fragment.graphql6
-rw-r--r--app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql13
-rw-r--r--app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql5
-rw-r--r--app/assets/javascripts/snippets/queries/getSnippet.query.graphql13
-rw-r--r--app/assets/javascripts/snippets/queries/projectPermissions.query.graphql7
-rw-r--r--app/assets/javascripts/snippets/queries/snippet.query.graphql15
-rw-r--r--app/assets/javascripts/snippets/queries/userPermissions.query.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue4
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}`;
},
},
};