Merge branch '8088_embedded_snippets_support' into 'master'
Embedded Snippets Support Closes #8088 See merge request gitlab-org/gitlab-ce!15695
This commit is contained in:
commit
cee3df6c24
27 changed files with 696 additions and 301 deletions
BIN
app/assets/images/ext_snippet_icons/ext_snippet_icons.png
Normal file
BIN
app/assets/images/ext_snippet_icons/ext_snippet_icons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,018 B |
BIN
app/assets/images/ext_snippet_icons/logo.png
Normal file
BIN
app/assets/images/ext_snippet_icons/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 494 B |
|
@ -1,11 +1,13 @@
|
|||
import initNotes from '~/init_notes';
|
||||
import ZenMode from '~/zen_mode';
|
||||
import LineHighlighter from '../../../../line_highlighter';
|
||||
import BlobViewer from '../../../../blob/viewer';
|
||||
import LineHighlighter from '~/line_highlighter';
|
||||
import BlobViewer from '~/blob/viewer';
|
||||
import snippetEmbed from '~/snippet/snippet_embed';
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import LineHighlighter from '../../../line_highlighter';
|
||||
import BlobViewer from '../../../blob/viewer';
|
||||
import ZenMode from '../../../zen_mode';
|
||||
import initNotes from '../../../init_notes';
|
||||
import LineHighlighter from '~/line_highlighter';
|
||||
import BlobViewer from '~/blob/viewer';
|
||||
import ZenMode from '~/zen_mode';
|
||||
import initNotes from '~/init_notes';
|
||||
import snippetEmbed from '~/snippet/snippet_embed';
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
23
app/assets/javascripts/snippet/snippet_embed.js
Normal file
23
app/assets/javascripts/snippet/snippet_embed.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export default () => {
|
||||
const { protocol, host, pathname } = location;
|
||||
const shareBtn = document.querySelector('.js-share-btn');
|
||||
const embedBtn = document.querySelector('.js-embed-btn');
|
||||
const snippetUrlArea = document.querySelector('.js-snippet-url-area');
|
||||
const embedAction = document.querySelector('.js-embed-action');
|
||||
const url = `${protocol}//${host + pathname}`;
|
||||
|
||||
shareBtn.addEventListener('click', () => {
|
||||
shareBtn.classList.add('is-active');
|
||||
embedBtn.classList.remove('is-active');
|
||||
snippetUrlArea.value = url;
|
||||
embedAction.innerText = 'Share';
|
||||
});
|
||||
|
||||
embedBtn.addEventListener('click', () => {
|
||||
embedBtn.classList.add('is-active');
|
||||
shareBtn.classList.remove('is-active');
|
||||
const scriptTag = `<script src="${url}.js"></script>`;
|
||||
snippetUrlArea.value = scriptTag;
|
||||
embedAction.innerText = 'Embed';
|
||||
});
|
||||
};
|
|
@ -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.
|
||||
|
|
|
@ -481,7 +481,8 @@
|
|||
|
||||
.dropdown-menu-selectable {
|
||||
li {
|
||||
a {
|
||||
a,
|
||||
button {
|
||||
padding: 8px 40px;
|
||||
position: relative;
|
||||
|
||||
|
|
|
@ -29,8 +29,10 @@
|
|||
}
|
||||
|
||||
.snippet-title {
|
||||
font-size: 24px;
|
||||
color: $gl-text-color;
|
||||
font-size: 2em;
|
||||
font-weight: $gl-font-weight-bold;
|
||||
min-height: $header-height;
|
||||
}
|
||||
|
||||
.snippet-edited-ago {
|
||||
|
@ -46,3 +48,26 @@
|
|||
.snippet-scope-menu .btn-new {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.snippet-embed-input {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.embed-snippet {
|
||||
padding-right: 0;
|
||||
padding-top: $gl-padding;
|
||||
|
||||
.form-control {
|
||||
cursor: auto;
|
||||
width: 101%;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.embed-toggle-list li button {
|
||||
padding: 8px 40px;
|
||||
}
|
||||
|
||||
.embed-toggle {
|
||||
height: 35px;
|
||||
}
|
||||
}
|
||||
|
|
3
app/assets/stylesheets/highlight/embedded.scss
Normal file
3
app/assets/stylesheets/highlight/embedded.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.code {
|
||||
@import "white_base";
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
|
290
app/assets/stylesheets/highlight/white_base.scss
Normal file
290
app/assets/stylesheets/highlight/white_base.scss
Normal file
|
@ -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; }
|
156
app/assets/stylesheets/snippets.scss
Normal file
156
app/assets/stylesheets/snippets.scss
Normal file
|
@ -0,0 +1,156 @@
|
|||
@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;
|
||||
|
||||
.gl-snippet-icon {
|
||||
display: inline-block;
|
||||
background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: cover;
|
||||
|
||||
&.gl-snippet-icon-doc_code { background-position: 0 0; }
|
||||
&.gl-snippet-icon-doc_text { background-position: 0 -16px; }
|
||||
&.gl-snippet-icon-download { background-position: 0 -32px; }
|
||||
}
|
||||
|
||||
.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-embedded-snippets-title {
|
||||
text-decoration: none;
|
||||
color: $gl-text-color;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.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,
|
||||
.gl-snippet-icon {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,10 @@ module SnippetsActions
|
|||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def js_request?
|
||||
request.format.js?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def convert_line_endings(content)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
|
||||
end
|
||||
|
||||
def audit_icon(names, options = {})
|
||||
case names
|
||||
when "standard"
|
||||
|
|
|
@ -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'
|
||||
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', 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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
- render_error = viewer.render_error
|
||||
- rich_type = viewer.type == :rich ? viewer.partial_name : nil
|
||||
- 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, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
|
||||
|
@ -9,6 +10,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!
|
||||
|
||||
|
|
|
@ -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?)
|
24
app/views/shared/snippets/_embed.html.haml
Normal file
24
app/views/shared/snippets/_embed.html.haml
Normal file
|
@ -0,0 +1,24 @@
|
|||
- 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
|
||||
%a.gitlab-embedded-snippets-title{ href: url_for(only_path: false, overwrite_params: nil) }
|
||||
= blob.name
|
||||
|
||||
%small
|
||||
= number_to_human_size(blob.raw_size)
|
||||
%a.gitlab-logo{ href: url_for(only_path: false, overwrite_params: nil), title: 'view on gitlab' }
|
||||
on
|
||||
%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
|
|
@ -19,11 +19,32 @@
|
|||
%h2.snippet-title.prepend-top-0.append-bottom-0
|
||||
= markdown_field(@snippet, :title)
|
||||
|
||||
- if @snippet.updated_at != @snippet.created_at
|
||||
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
|
||||
- if @snippet.description.present?
|
||||
.description
|
||||
.wiki
|
||||
= markdown_field(@snippet, :description)
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @snippet.description
|
||||
|
||||
- if @snippet.updated_at != @snippet.created_at
|
||||
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
|
||||
|
||||
- if public_snippet?
|
||||
.embed-snippet
|
||||
.input-group
|
||||
.input-group-btn
|
||||
%button.btn.embed-toggle{ 'data-toggle': 'dropdown', type: 'button' }
|
||||
%span.js-embed-action= _("Embed")
|
||||
= sprite_icon('angle-down', size: 12)
|
||||
%ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
|
||||
%li
|
||||
%button.js-embed-btn.btn.btn-transparent.is-active{ type: 'button' }
|
||||
%strong.embed-toggle-list-item= _("Embed")
|
||||
%li
|
||||
%button.js-share-btn.btn.btn-transparent{ type: 'button' }
|
||||
%strong.embed-toggle-list-item= _("Share")
|
||||
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
|
||||
.input-group-btn
|
||||
%button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' }
|
||||
= sprite_icon('duplicate', size: 16)
|
||||
.clearfix
|
||||
|
|
2
app/views/shared/snippets/show.js.haml
Normal file
2
app/views/shared/snippets/show.js.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
document.write('#{escape_javascript(stylesheet_link_tag "#{stylesheet_url 'snippets'}")}');
|
||||
document.write('#{escape_javascript(render 'shared/snippets/embed')}');
|
5
changelogs/unreleased/8088_embedded_snippets_support.yml
Normal file
5
changelogs/unreleased/8088_embedded_snippets_support.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds Embedded Snippets Support
|
||||
merge_request: 15695
|
||||
author: haseebeqx
|
||||
type: added
|
|
@ -113,6 +113,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"
|
||||
|
||||
# Import gitlab-svgs directly from vendored directory
|
||||
|
|
25
spec/features/snippets/embedded_snippet_spec.rb
Normal file
25
spec/features/snippets/embedded_snippet_spec.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Embedded Snippets' do
|
||||
let(:snippet) { create(:personal_snippet, :public, file_name: 'random_dir.rb', content: content) }
|
||||
let(:content) { "require 'fileutils'\nFileUtils.mkdir_p 'some/random_dir'\n" }
|
||||
|
||||
it 'loads snippet', :js do
|
||||
script_url = "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/#{snippet_path(snippet, format: 'js')}"
|
||||
embed_body = "<html><body><script src=\"#{script_url}\"></script></body></html>"
|
||||
|
||||
rack_app = proc do
|
||||
['200', { 'Content-Type' => 'text/html' }, [embed_body]]
|
||||
end
|
||||
|
||||
server = Capybara::Server.new(rack_app)
|
||||
server.boot
|
||||
|
||||
visit("http://#{server.host}:#{server.port}/embedded_snippet.html")
|
||||
|
||||
expect(page).to have_content("random_dir.rb")
|
||||
expect(page).to have_content("require 'fileutils'")
|
||||
expect(page).to have_link('Open raw')
|
||||
expect(page).to have_link('Download')
|
||||
end
|
||||
end
|
|
@ -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("<span class=\"gl-snippet-icon gl-snippet-icon-download\"></span>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
33
spec/helpers/snippets_helper_spec.rb
Normal file
33
spec/helpers/snippets_helper_spec.rb
Normal file
|
@ -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\" 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\" 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\" 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\" rel=\"noopener noreferrer\" href=\"#{raw_snippet_url(@snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue