summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhaseeb <haseebeqx@yahoo.com>2018-02-06 19:03:18 +0530
committerhaseeb <haseebeqx@yahoo.com>2018-02-28 18:48:26 +0530
commit27e8d38cea92d165a2e8400b25df23f408b4dca0 (patch)
tree0a16f63c8f380c92fb4185ca21ba8a264c572641
parent56af0631c6c6d838301ac068f2e79b8f4de9fda7 (diff)
downloadgitlab-ce-27e8d38cea92d165a2e8400b25df23f408b4dca0.tar.gz
embedded snippets support
-rw-r--r--app/assets/images/ext_snippet_icons/doc_code.pngbin0 -> 1058 bytes
-rw-r--r--app/assets/images/ext_snippet_icons/doc_text.pngbin0 -> 996 bytes
-rw-r--r--app/assets/images/ext_snippet_icons/download.pngbin0 -> 1098 bytes
-rw-r--r--app/assets/images/ext_snippet_icons/logo.pngbin0 -> 736 bytes
-rw-r--r--app/assets/javascripts/snippet/snippet_embed.js18
-rw-r--r--app/assets/stylesheets/application.scss6
-rw-r--r--app/assets/stylesheets/highlight/embedded.scss3
-rw-r--r--app/assets/stylesheets/highlight/white.scss291
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss290
-rw-r--r--app/assets/stylesheets/snippets.scss132
-rw-r--r--app/controllers/concerns/snippets_actions.rb4
-rw-r--r--app/controllers/projects/snippets_controller.rb3
-rw-r--r--app/controllers/snippets_controller.rb4
-rw-r--r--app/helpers/icons_helper.rb4
-rw-r--r--app/helpers/snippets_helper.rb35
-rw-r--r--app/views/projects/blob/_viewer.html.haml3
-rw-r--r--app/views/projects/blob/viewers/_highlight_embed.html.haml7
-rw-r--r--app/views/shared/snippets/_embed.html.haml23
-rw-r--r--app/views/shared/snippets/_header.html.haml21
-rw-r--r--app/views/shared/snippets/show.js.haml2
-rw-r--r--config/application.rb1
-rw-r--r--config/webpack.config.js6
-rw-r--r--spec/helpers/icons_helper_spec.rb7
-rw-r--r--spec/helpers/snippets_helper_spec.rb33
24 files changed, 602 insertions, 291 deletions
diff --git a/app/assets/images/ext_snippet_icons/doc_code.png b/app/assets/images/ext_snippet_icons/doc_code.png
new file mode 100644
index 00000000000..b1a589a848f
--- /dev/null
+++ b/app/assets/images/ext_snippet_icons/doc_code.png
Binary files differ
diff --git a/app/assets/images/ext_snippet_icons/doc_text.png b/app/assets/images/ext_snippet_icons/doc_text.png
new file mode 100644
index 00000000000..aee32cc3620
--- /dev/null
+++ b/app/assets/images/ext_snippet_icons/doc_text.png
Binary files differ
diff --git a/app/assets/images/ext_snippet_icons/download.png b/app/assets/images/ext_snippet_icons/download.png
new file mode 100644
index 00000000000..045fb0f2e95
--- /dev/null
+++ b/app/assets/images/ext_snippet_icons/download.png
Binary files differ
diff --git a/app/assets/images/ext_snippet_icons/logo.png b/app/assets/images/ext_snippet_icons/logo.png
new file mode 100644
index 00000000000..12f8315cad8
--- /dev/null
+++ b/app/assets/images/ext_snippet_icons/logo.png
Binary files differ
diff --git a/app/assets/javascripts/snippet/snippet_embed.js b/app/assets/javascripts/snippet/snippet_embed.js
new file mode 100644
index 00000000000..c032414552e
--- /dev/null
+++ b/app/assets/javascripts/snippet/snippet_embed.js
@@ -0,0 +1,18 @@
+(() => {
+ $(() => {
+ const { protocol, host, pathname } = location;
+
+ $('#share-btn').click((event) => {
+ event.preventDefault();
+ $('#snippet-url-area').val(`${protocol}//${host + pathname}`);
+ $('#embed-action').html('Share');
+ });
+
+ $('#embed-btn').click((event) => {
+ event.preventDefault();
+ const scriptTag = `<script src="${protocol}//${host + pathname}.js"></script>`;
+ $('#snippet-url-area').val(scriptTag);
+ $('#embed-action').html('Embed');
+ });
+ });
+}).call(window);
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 0665622fe4a..f2950308019 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -37,7 +37,11 @@
/*
* Code highlight
*/
-@import "highlight/**/*";
+@import "highlight/dark";
+@import "highlight/monokai";
+@import "highlight/solarized_dark";
+@import "highlight/solarized_light";
+@import "highlight/white";
/*
* Styles for JS behaviors.
diff --git a/app/assets/stylesheets/highlight/embedded.scss b/app/assets/stylesheets/highlight/embedded.scss
new file mode 100644
index 00000000000..44c8a1d39ec
--- /dev/null
+++ b/app/assets/stylesheets/highlight/embedded.scss
@@ -0,0 +1,3 @@
+.code {
+ @import "white_base";
+}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index c3d8f0c61a2..355c8d223f7 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,292 +1,3 @@
-/* https://github.com/aahan/pygments-github-style */
-
-/*
-* White Syntax Colors
-*/
-$white-code-color: $gl-text-color;
-$white-highlight: #fafe3d;
-$white-pre-hll-bg: #f8eec7;
-$white-hll-bg: #f8f8f8;
-$white-over-bg: #ded7fc;
-$white-expanded-border: #e0e0e0;
-$white-expanded-bg: #f7f7f7;
-$white-c: #998;
-$white-err: #a61717;
-$white-err-bg: #e3d2d2;
-$white-cm: #998;
-$white-cp: #999;
-$white-c1: #998;
-$white-cs: #999;
-$white-gd: $black;
-$white-gd-bg: #fdd;
-$white-gd-x: $black;
-$white-gd-x-bg: #faa;
-$white-gr: #a00;
-$white-gh: #999;
-$white-gi: $black;
-$white-gi-bg: #dfd;
-$white-gi-x: $black;
-$white-gi-x-bg: #afa;
-$white-go: #888;
-$white-gp: #555;
-$white-gu: #800080;
-$white-gt: #a00;
-$white-kt: #458;
-$white-m: #099;
-$white-s: #d14;
-$white-n: #333;
-$white-na: teal;
-$white-nb: #0086b3;
-$white-nc: #458;
-$white-no: teal;
-$white-ni: purple;
-$white-ne: #900;
-$white-nf: #900;
-$white-nn: #555;
-$white-nt: navy;
-$white-nv: teal;
-$white-w: #bbb;
-$white-mf: #099;
-$white-mh: #099;
-$white-mi: #099;
-$white-mo: #099;
-$white-sb: #d14;
-$white-sc: #d14;
-$white-sd: #d14;
-$white-s2: #d14;
-$white-se: #d14;
-$white-sh: #d14;
-$white-si: #d14;
-$white-sx: #d14;
-$white-sr: #009926;
-$white-s1: #d14;
-$white-ss: #990073;
-$white-bp: #999;
-$white-vc: teal;
-$white-vg: teal;
-$white-vi: teal;
-$white-il: #099;
-$white-gc-color: #999;
-$white-gc-bg: #eaf2f5;
-
-
-@mixin matchLine {
- color: $black-transparent;
- background-color: $gray-light;
-}
-
.code.white {
- // Line numbers
- .line-numbers,
- .diff-line-num {
- background-color: $gray-light;
- }
-
- .diff-line-num,
- .diff-line-num a {
- color: $black-transparent;
- }
-
- // Code itself
- pre.code,
- .diff-line-num {
- border-color: $white-normal;
- }
-
- &,
- pre.code,
- .line_holder .line_content {
- background-color: $white-light;
- color: $white-code-color;
- }
-
- // Diff line
- .line_holder {
-
- &.match .line_content {
- @include matchLine;
- }
-
- .diff-line-num {
- &.old {
- background-color: $line-number-old;
- border-color: $line-removed-dark;
-
- a {
- color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
- }
- }
-
- &.new {
- background-color: $line-number-new;
- border-color: $line-added-dark;
-
- a {
- color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
- }
- }
-
- &.is-over,
- &.hll:not(.empty-cell).is-over {
- background-color: $white-over-bg;
- border-color: darken($white-over-bg, 5%);
-
- a {
- color: darken($white-over-bg, 15%);
- }
- }
-
- &.hll:not(.empty-cell) {
- background-color: $line-number-select;
- border-color: $line-select-yellow-dark;
- }
- }
-
- &:not(.diff-expanded) + .diff-expanded,
- &.diff-expanded + .line_holder:not(.diff-expanded) {
- > .diff-line-num,
- > .line_content {
- border-top: 1px solid $white-expanded-border;
- }
- }
-
- &.diff-expanded {
- > .diff-line-num,
- > .line_content {
- background: $white-expanded-bg;
- border-color: $white-expanded-bg;
- }
- }
-
- .line_content {
- &.old {
- background-color: $line-removed;
-
- &::before {
- color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
- }
-
- span.idiff {
- background-color: $line-removed-dark;
- }
- }
-
- &.new {
- background-color: $line-added;
-
- &::before {
- color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
- }
-
- span.idiff {
- background-color: $line-added-dark;
- }
- }
-
- &.match {
- @include matchLine;
- }
-
- &.hll:not(.empty-cell) {
- background-color: $line-select-yellow;
- }
- }
- }
-
- // highlight line via anchor
- pre .hll {
- background-color: $white-pre-hll-bg !important;
- }
-
- // Search result highlight
- span.highlight_word {
- background-color: $white-highlight !important;
- }
-
- // Links to URLs, emails, or dependencies
- .line a {
- color: $white-nb;
- }
-
- .hll { background-color: $white-hll-bg; }
- .c { color: $white-c; font-style: italic; }
- .err { color: $white-err; background-color: $white-err-bg; }
- .k { font-weight: $gl-font-weight-bold; }
- .o { font-weight: $gl-font-weight-bold; }
- .cm { color: $white-cm; font-style: italic; }
- .cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
- .c1 { color: $white-c1; font-style: italic; }
- .cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
-
- .gd {
- color: $white-gd;
- background-color: $white-gd-bg;
-
- .x {
- color: $white-gd-x;
- background-color: $white-gd-x-bg;
- }
- }
-
- .ge { font-style: italic; }
- .gr { color: $white-gr; }
- .gh { color: $white-gh; }
-
- .gi {
- color: $white-gi;
- background-color: $white-gi-bg;
-
- .x {
- color: $white-gi-x;
- background-color: $white-gi-x-bg;
- }
- }
-
- .go { color: $white-go; }
- .gp { color: $white-gp; }
- .gs { font-weight: $gl-font-weight-bold; }
- .gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
- .gt { color: $white-gt; }
- .kc { font-weight: $gl-font-weight-bold; }
- .kd { font-weight: $gl-font-weight-bold; }
- .kn { font-weight: $gl-font-weight-bold; }
- .kp { font-weight: $gl-font-weight-bold; }
- .kr { font-weight: $gl-font-weight-bold; }
- .kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
- .m { color: $white-m; }
- .s { color: $white-s; }
- .n { color: $white-n; }
- .na { color: $white-na; }
- .nb { color: $white-nb; }
- .nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
- .no { color: $white-no; }
- .ni { color: $white-ni; }
- .ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
- .nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
- .nn { color: $white-nn; }
- .nt { color: $white-nt; }
- .nv { color: $white-nv; }
- .ow { font-weight: $gl-font-weight-bold; }
- .w { color: $white-w; }
- .mf { color: $white-mf; }
- .mh { color: $white-mh; }
- .mi { color: $white-mi; }
- .mo { color: $white-mo; }
- .sb { color: $white-sb; }
- .sc { color: $white-sc; }
- .sd { color: $white-sd; }
- .s2 { color: $white-s2; }
- .se { color: $white-se; }
- .sh { color: $white-sh; }
- .si { color: $white-si; }
- .sx { color: $white-sx; }
- .sr { color: $white-sr; }
- .s1 { color: $white-s1; }
- .ss { color: $white-ss; }
- .bp { color: $white-bp; }
- .vc { color: $white-vc; }
- .vg { color: $white-vg; }
- .vi { color: $white-vi; }
- .il { color: $white-il; }
- .gc { color: $white-gc-color; background-color: $white-gc-bg; }
+ @import "white_base";
}
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
new file mode 100644
index 00000000000..8cc5252648d
--- /dev/null
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -0,0 +1,290 @@
+/* https://github.com/aahan/pygments-github-style */
+
+/*
+* White Syntax Colors
+*/
+$white-code-color: $gl-text-color;
+$white-highlight: #fafe3d;
+$white-pre-hll-bg: #f8eec7;
+$white-hll-bg: #f8f8f8;
+$white-over-bg: #ded7fc;
+$white-expanded-border: #e0e0e0;
+$white-expanded-bg: #f7f7f7;
+$white-c: #998;
+$white-err: #a61717;
+$white-err-bg: #e3d2d2;
+$white-cm: #998;
+$white-cp: #999;
+$white-c1: #998;
+$white-cs: #999;
+$white-gd: $black;
+$white-gd-bg: #fdd;
+$white-gd-x: $black;
+$white-gd-x-bg: #faa;
+$white-gr: #a00;
+$white-gh: #999;
+$white-gi: $black;
+$white-gi-bg: #dfd;
+$white-gi-x: $black;
+$white-gi-x-bg: #afa;
+$white-go: #888;
+$white-gp: #555;
+$white-gu: #800080;
+$white-gt: #a00;
+$white-kt: #458;
+$white-m: #099;
+$white-s: #d14;
+$white-n: #333;
+$white-na: teal;
+$white-nb: #0086b3;
+$white-nc: #458;
+$white-no: teal;
+$white-ni: purple;
+$white-ne: #900;
+$white-nf: #900;
+$white-nn: #555;
+$white-nt: navy;
+$white-nv: teal;
+$white-w: #bbb;
+$white-mf: #099;
+$white-mh: #099;
+$white-mi: #099;
+$white-mo: #099;
+$white-sb: #d14;
+$white-sc: #d14;
+$white-sd: #d14;
+$white-s2: #d14;
+$white-se: #d14;
+$white-sh: #d14;
+$white-si: #d14;
+$white-sx: #d14;
+$white-sr: #009926;
+$white-s1: #d14;
+$white-ss: #990073;
+$white-bp: #999;
+$white-vc: teal;
+$white-vg: teal;
+$white-vi: teal;
+$white-il: #099;
+$white-gc-color: #999;
+$white-gc-bg: #eaf2f5;
+
+
+@mixin matchLine {
+ color: $black-transparent;
+ background-color: $gray-light;
+}
+
+ // Line numbers
+.line-numbers,
+.diff-line-num {
+ background-color: $gray-light;
+}
+
+.diff-line-num,
+.diff-line-num a {
+ color: $black-transparent;
+}
+
+// Code itself
+pre.code,
+.diff-line-num {
+ border-color: $white-normal;
+}
+
+&,
+pre.code,
+.line_holder .line_content {
+ background-color: $white-light;
+ color: $white-code-color;
+}
+
+// Diff line
+.line_holder {
+
+ &.match .line_content {
+ @include matchLine;
+ }
+
+ .diff-line-num {
+ &.old {
+ background-color: $line-number-old;
+ border-color: $line-removed-dark;
+
+ a {
+ color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+ }
+ }
+
+ &.new {
+ background-color: $line-number-new;
+ border-color: $line-added-dark;
+
+ a {
+ color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+ }
+ }
+
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $white-over-bg;
+ border-color: darken($white-over-bg, 5%);
+
+ a {
+ color: darken($white-over-bg, 15%);
+ }
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-number-select;
+ border-color: $line-select-yellow-dark;
+ }
+ }
+
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $white-expanded-border;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $white-expanded-bg;
+ border-color: $white-expanded-bg;
+ }
+ }
+
+ .line_content {
+ &.old {
+ background-color: $line-removed;
+
+ &::before {
+ color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+ }
+
+ span.idiff {
+ background-color: $line-removed-dark;
+ }
+ }
+
+ &.new {
+ background-color: $line-added;
+
+ &::before {
+ color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+ }
+
+ span.idiff {
+ background-color: $line-added-dark;
+ }
+ }
+
+ &.match {
+ @include matchLine;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
+ }
+ }
+}
+
+// highlight line via anchor
+pre .hll {
+ background-color: $white-pre-hll-bg !important;
+}
+
+ // Search result highlight
+span.highlight_word {
+ background-color: $white-highlight !important;
+}
+
+ // Links to URLs, emails, or dependencies
+.line a {
+ color: $white-nb;
+}
+
+.hll { background-color: $white-hll-bg; }
+.c { color: $white-c; font-style: italic; }
+.err { color: $white-err; background-color: $white-err-bg; }
+.k { font-weight: $gl-font-weight-bold; }
+.o { font-weight: $gl-font-weight-bold; }
+.cm { color: $white-cm; font-style: italic; }
+.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
+.c1 { color: $white-c1; font-style: italic; }
+.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
+
+.gd {
+ color: $white-gd;
+ background-color: $white-gd-bg;
+
+ .x {
+ color: $white-gd-x;
+ background-color: $white-gd-x-bg;
+ }
+}
+
+.ge { font-style: italic; }
+.gr { color: $white-gr; }
+.gh { color: $white-gh; }
+
+.gi {
+ color: $white-gi;
+ background-color: $white-gi-bg;
+
+ .x {
+ color: $white-gi-x;
+ background-color: $white-gi-x-bg;
+ }
+}
+
+.go { color: $white-go; }
+.gp { color: $white-gp; }
+.gs { font-weight: $gl-font-weight-bold; }
+.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
+.gt { color: $white-gt; }
+.kc { font-weight: $gl-font-weight-bold; }
+.kd { font-weight: $gl-font-weight-bold; }
+.kn { font-weight: $gl-font-weight-bold; }
+.kp { font-weight: $gl-font-weight-bold; }
+.kr { font-weight: $gl-font-weight-bold; }
+.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
+.m { color: $white-m; }
+.s { color: $white-s; }
+.n { color: $white-n; }
+.na { color: $white-na; }
+.nb { color: $white-nb; }
+.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
+.no { color: $white-no; }
+.ni { color: $white-ni; }
+.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
+.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
+.nn { color: $white-nn; }
+.nt { color: $white-nt; }
+.nv { color: $white-nv; }
+.ow { font-weight: $gl-font-weight-bold; }
+.w { color: $white-w; }
+.mf { color: $white-mf; }
+.mh { color: $white-mh; }
+.mi { color: $white-mi; }
+.mo { color: $white-mo; }
+.sb { color: $white-sb; }
+.sc { color: $white-sc; }
+.sd { color: $white-sd; }
+.s2 { color: $white-s2; }
+.se { color: $white-se; }
+.sh { color: $white-sh; }
+.si { color: $white-si; }
+.sx { color: $white-sx; }
+.sr { color: $white-sr; }
+.s1 { color: $white-s1; }
+.ss { color: $white-ss; }
+.bp { color: $white-bp; }
+.vc { color: $white-vc; }
+.vg { color: $white-vg; }
+.vi { color: $white-vi; }
+.il { color: $white-il; }
+.gc { color: $white-gc-color; background-color: $white-gc-bg; }
diff --git a/app/assets/stylesheets/snippets.scss b/app/assets/stylesheets/snippets.scss
new file mode 100644
index 00000000000..4de295629f4
--- /dev/null
+++ b/app/assets/stylesheets/snippets.scss
@@ -0,0 +1,132 @@
+@import "framework/variables";
+
+.gitlab-embed-snippets {
+ @import "highlight/embedded";
+ @import "framework/images";
+
+ $border-style: 1px solid $border-color;
+
+ font-family: $regular_font;
+ font-size: $gl-font-size;
+ line-height: $code_line_height;
+ color: $gl-text-color;
+ margin: 20px;
+ font-weight: 200;
+
+ .blob-viewer {
+ background-color: $white-light;
+ text-align: left;
+ }
+
+ .file-content.code {
+ border: $border-style;
+ border-radius: 0 0 4px 4px;
+ display: flex;
+ box-shadow: none;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+
+ .blob-content {
+ overflow-x: auto;
+
+ pre {
+ padding: 10px;
+ border: 0;
+ border-radius: 0;
+ font-family: $monospace_font;
+ font-size: $code_font_size;
+ line-height: $code_line_height;
+ margin: 0;
+ overflow: auto;
+ overflow-y: hidden;
+ white-space: pre;
+ word-wrap: normal;
+ border-left: $border-style;
+ }
+ }
+
+ .line-numbers {
+ padding: 10px;
+ text-align: right;
+ float: left;
+
+ .diff-line-num {
+ font-family: $monospace_font;
+ display: block;
+ font-size: $code_font_size;
+ min-height: $code_line_height;
+ white-space: nowrap;
+ color: $black-transparent;
+ min-width: 30px;
+ }
+
+ .diff-line-num:hover {
+ color: $almost-black;
+ cursor: pointer;
+ }
+ }
+ }
+
+ .file-title-flex-parent {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: $gray-light;
+ border: $border-style;
+ border-bottom: 0;
+ padding: $gl-padding-top $gl-padding;
+ margin: 0;
+ border-radius: $border-radius-default $border-radius-default 0 0;
+
+ .file-header-content {
+ .file-title-name {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .gitlab-logo {
+ display: inline-block;
+ padding-left: 5px;
+ text-decoration: none;
+ color: $gl-text-color-secondary;
+
+ .logo-text {
+ background: image_url('ext_snippet_icons/logo.png') no-repeat left center;
+ background-size: 18px;
+ font-weight: $gl-font-weight-normal;
+ padding-left: 24px;
+ }
+ }
+ }
+
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ }
+ }
+
+ .btn-group {
+ a.btn {
+ background-color: $white-light;
+ text-decoration: none;
+ padding: 7px 9px;
+ border: $border-style;
+ border-right: 0;
+
+ &:hover {
+ background-color: $white-normal;
+ border-color: $border-white-normal;
+ text-decoration: none;
+ }
+
+ &:first-child {
+ border-radius: 3px 0 0 3px;
+ }
+
+ &:last-child {
+ border-radius: 0 3px 3px 0;
+ border-right: $border-style;
+ }
+ }
+ }
+}
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 9095cc7f783..120614739aa 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -17,6 +17,10 @@ module SnippetsActions
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ def js_request?
+ request.format.js?
+ end
+
private
def convert_line_endings(content)
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 7c19aa7bb23..208a1d19862 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController
include SnippetsActions
include RendersBlob
+ skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
+
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController
format.json do
render_blob_json(blob)
end
+ format.js { render 'shared/snippets/show'}
end
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index be2d3f638ff..3d51520ddf4 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController
include RendersBlob
include PreviewMarkdown
+ skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
+
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController
format.json do
render_blob_json(blob)
end
+
+ format.js { render 'shared/snippets/show' }
end
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index c5522ff7a69..67001c55e88 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -43,6 +43,10 @@ module IconsHelper
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end
+ def external_snippet_icon(name)
+ content_tag(:img, "", src: "#{image_url('/assets/ext_snippet_icons')}/#{name}.png", width: '16px', height: '16px')
+ end
+
def audit_icon(names, options = {})
case names
when "standard"
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 00e7e4230b9..830db46d67f 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -101,4 +101,39 @@ module SnippetsHelper
# Return snippet with chunk array
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
+
+ def snippet_embed
+ "<script src=\"#{url_for(only_path: false, overwrite_params: nil)}.js\"></script>"
+ end
+
+ def embedded_snippet_raw_button
+ blob = @snippet.blob
+ return if blob.empty? || blob.raw_binary? || blob.stored_externally?
+
+ snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
+ raw_snippet_url(@snippet)
+ else
+ raw_project_snippet_url(@snippet.project, @snippet)
+ end
+
+ link_to external_snippet_icon('doc_code'), snippet_raw_url, class: 'btn', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' }
+ end
+
+ def embedded_snippet_download_button
+ download_url = if @snippet.is_a?(PersonalSnippet)
+ raw_snippet_url(@snippet, inline: false)
+ else
+ raw_project_snippet_url(@snippet.project, @snippet, inline: false)
+ end
+
+ link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', data: { container: 'body' }, rel: 'noopener noreferrer'
+ end
+
+ def public_snippet?
+ if @snippet.project_id?
+ can?(nil, :read_project_snippet, @snippet)
+ else
+ can?(nil, :read_personal_snippet, @snippet)
+ end
+ end
end
diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml
index cc85e5de40f..fc62c1f06eb 100644
--- a/app/views/projects/blob/_viewer.html.haml
+++ b/app/views/projects/blob/_viewer.html.haml
@@ -1,6 +1,7 @@
- hidden = local_assigns.fetch(:hidden, false)
- render_error = viewer.render_error
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
+- external_embed = local_assigns.fetch(:external_embed, false)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) }
@@ -8,6 +9,8 @@
= render 'projects/blob/render_error', viewer: viewer
- elsif load_async
= render viewer.loading_partial_path, viewer: viewer
+ - elsif external_embed
+ = render 'projects/blob/viewers/highlight_embed', blob: viewer.blob
- else
- viewer.prepare!
diff --git a/app/views/projects/blob/viewers/_highlight_embed.html.haml b/app/views/projects/blob/viewers/_highlight_embed.html.haml
new file mode 100644
index 00000000000..9bd4ef6ad0b
--- /dev/null
+++ b/app/views/projects/blob/viewers/_highlight_embed.html.haml
@@ -0,0 +1,7 @@
+.file-content.code.js-syntax-highlight
+ .line-numbers
+ - if blob.data.present?
+ - blob.data.each_line.each_with_index do |_, index|
+ %span.diff-line-num= index + 1
+ .blob-content{ data: { blob_id: blob.id } }
+ = highlight(blob.path, blob.data, repository: nil, plain: blob.no_highlighting?)
diff --git a/app/views/shared/snippets/_embed.html.haml b/app/views/shared/snippets/_embed.html.haml
new file mode 100644
index 00000000000..9a5bf4c9f04
--- /dev/null
+++ b/app/views/shared/snippets/_embed.html.haml
@@ -0,0 +1,23 @@
+- blob = @snippet.blob
+.gitlab-embed-snippets
+ .js-file-title.file-title-flex-parent
+ .file-header-content
+ = external_snippet_icon('doc_text')
+
+ %strong.file-title-name
+ = blob.name
+
+ %small
+ = number_to_human_size(blob.raw_size)
+ %a.gitlab-logo{ href: url_for(only_path: false, overwrite_params: nil) }
+ on &nbsp;
+ %span.logo-text
+ GitLab
+
+ .file-actions.hidden-xs
+ .btn-group{ role: "group" }<
+ = embedded_snippet_raw_button
+
+ = embedded_snippet_download_button
+ %article.file-holder.snippet-file-content
+ = render 'projects/blob/viewer', viewer: @snippet.blob.simple_viewer, load_async: false, external_embed: true
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 12df79a28c7..de14356b82f 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,3 +1,6 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('snippet_embed')
+
.detail-page-header
.detail-page-header-body
.snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
@@ -27,3 +30,21 @@
= markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field
= @snippet.description
+ - if public_snippet?
+ .pull-right
+ .input-group
+ .input-group-btn
+ %button.btn.btn-default.dropdown-toggle{ 'data-toggle': 'dropdown' }
+ %span#embed-action Embed
+ = sprite_icon('angle-down', size: 16)
+ .dropdown-menu
+ %ul
+ %li
+ %a#embed-btn{ href: "#" }Embed
+ %li
+ %a#share-btn{ href: "#" }Share
+ %input#snippet-url-area.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
+ .input-group-btn
+ %a#clipboard-btn.btn.btn-default{ title: "Copy to clipboard", data: { toggle: "tooltip", placement: "bottom", title: "Copy source to clipboard", container: "body", clipboard_target: '#snippet-url-area' } }
+ = sprite_icon('duplicate', size: 16)
+ .clearfix
diff --git a/app/views/shared/snippets/show.js.haml b/app/views/shared/snippets/show.js.haml
new file mode 100644
index 00000000000..a9af732bbb5
--- /dev/null
+++ b/app/views/shared/snippets/show.js.haml
@@ -0,0 +1,2 @@
+document.write('#{escape_javascript(stylesheet_link_tag "#{stylesheet_url 'snippets'}")}');
+document.write('#{escape_javascript(render 'shared/snippets/embed')}');
diff --git a/config/application.rb b/config/application.rb
index 918bd4d57cf..f7f0563519a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -114,6 +114,7 @@ module Gitlab
config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "test.css"
+ config.assets.precompile << "snippets.css"
config.assets.precompile << "locale/**/app.js"
# Version of your assets, change this if you want to expire all your assets
diff --git a/config/webpack.config.js b/config/webpack.config.js
index b01cfd6595e..de55350d804 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -57,6 +57,12 @@ function generateEntries() {
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
+ ide: './ide/index.js',
+ sidebar: './sidebar/sidebar_bundle.js',
+ schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
+ schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
+ snippet: './snippet/snippet_bundle.js',
+ snippet_embed: './snippet/snippet_embed.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js',
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 2f23ed55d99..d87c81a28d1 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -162,4 +162,11 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'CHANGELOG')).to eq 'file-text-o'
end
end
+
+ describe '#external_snippet_icon' do
+ it 'returns external snippet icon' do
+ expect(external_snippet_icon('download').to_s)
+ .to eq("<img src=\"#{asset_url('/assets/ext_snippet_icons/download.png')}\" width=\"16px\" height=\"16px\"></img>")
+ end
+ end
end
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
new file mode 100644
index 00000000000..d432875b5ab
--- /dev/null
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe SnippetsHelper do
+ include IconsHelper
+
+ describe '#embedded_snippet_raw_button' do
+ it 'gives view raw button of embedded snippets for project snippets' do
+ @snippet = create(:project_snippet, :public)
+
+ expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" data-container=\"body\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet)}\">#{external_snippet_icon('doc_code')}</a>")
+ end
+
+ it 'gives view raw button of embedded snippets for personal snippets' do
+ @snippet = create(:personal_snippet, :public)
+
+ expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" data-container=\"body\" href=\"#{raw_snippet_url(@snippet)}\">#{external_snippet_icon('doc_code')}</a>")
+ end
+ end
+
+ describe '#embedded_snippet_download_button' do
+ it 'gives download button of embedded snippets for project snippets' do
+ @snippet = create(:project_snippet, :public)
+
+ expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" data-container=\"body\" rel=\"noopener noreferrer\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
+ end
+
+ it 'gives download button of embedded snippets for personal snippets' do
+ @snippet = create(:personal_snippet, :public)
+
+ expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" data-container=\"body\" rel=\"noopener noreferrer\" href=\"#{raw_snippet_url(@snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
+ end
+ end
+end