Commit 4b20cb5d authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file

* upstream/master: (130 commits)
  Fix: Guest sees some repository details and gets 404
  Update commits.scss
  updated styling commit SHA on branches page + added to changelog
  change build list height to show 6,5 builds + improve padding of list, with first/last child selectors
  Ignore builds directory from eslint
  Add changelog entry
  Document multiple repository storage paths
  Allow multiple repository storage shards to be enabled, and automatically round-robin between them
  Cleaned up global namespace JS
  Add tip for using Chrome to run and debug teaspoon tests.
  Add CHANGELOG entry file
  Add jquery.timeago.js to application.js
  Update match-regex to fix filename convention
  Move jquery.timeago to vendor directory
  Change a bunch of doc links to either relative or https://docs.gitlab.com.
  Show log corresponding to env in admin/logs
  Remove g from svg colors
  Add new icon for skipped builds; show created state in mini graph
  Clarify the author field for the changelog documentation
  DRY up the specs for bin/changelog
  ...
parents a431ca0f e4c05de7
/coverage-javascript/
/public/
/tmp/
/vendor/
/builds/
{
"extends": "airbnb",
"plugins": [
"filenames"
],
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"]
},
"globals": {
"$": false,
"_": false,
......
Please view this file on the master branch, on stable branches it's out of date.
**Note:** This file is automatically generated. Please see the [developer
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 8.14.0 (2016-11-22)
- Show correct environment log in admin/logs (@duk3luk3 !7191)
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Backups do not fail anymore when using tar on annex and custom_hooks only. !5814
- Adds user project membership expired event to clarify why user was removed (Callum Dryden)
- Trim leading and trailing whitespace on project_path (Linus Thiel)
- Prevent award emoji via notes for issues/MRs authored by user (barthc)
- Adds support for the `token` attribute in project hooks API (Gauvain Pocentek)
- Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO)
- Fix Markdown styling inside reference links (Jan Zdráhal)
- Fix extra space on Build sidebar on Firefox !7060
- Fail gracefully when creating merge request with non-existing branch (alexsanford)
- Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato)
- Cleaned up global namespace JS !19661 (Jose Ivan Vargas)
- Refactor Jira service to use jira-ruby gem
- Improved todos empty state
- Add hover to trash icon in notes !7008 (blackst0ne)
- Hides project activity tabs when features are disabled
- Only show one error message for an invalid email !5905 (lycoperdon)
- Added guide describing how to upgrade PostgreSQL using Slony
- Fix sidekiq stats in admin area (blackst0ne)
- Added label description as tooltip to issue board list title
- Created cycle analytics bundle JavaScript file
- Hides container registry when repository is disabled
- API: Fix booleans not recognized as such when using the `to_boolean` helper
- Removed delete branch tooltip !6954
- Stop unauthorized users dragging on milestone page (blackst0ne)
- Restore issue boards welcome message when a project is created !6899
- Check that JavaScript file names match convention !7238 (winniehell)
- Do not show tooltip for active element !7105 (winniehell)
- Escape ref and path for relative links !6050 (winniehell)
- Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose)
- Fix broken issue/merge request links in JIRA comments. !6143 (Brian Kintz)
- Fix filtering of milestones with quotes in title (airatshigapov)
- Fix issue boards dragging bug in Safari
- Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison)
- Update mail_room and enable sentinel support to Reply By Email (!7101)
- Add task completion status in Issues and Merge Requests tabs: "X of Y tasks completed" (!6527, @gmesalazar)
......@@ -30,16 +48,35 @@ Please view this file on the master branch, on stable branches it's out of date.
- New issue board list dropdown stays open after adding a new list
- Fix: Backup restore doesn't clear cache
- Optimize Event queries by removing default order
- Add new icon for skipped builds
- Show created icon in pipeline mini-graph
- Remove duplicate links from sidebar
- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh)
- Add Rake task to create/repair GitLab Shell hooks symlinks !5634
- Add job for removal of unreferenced LFS objects from both the database and the filesystem (Frank Groeneveld)
- Replace jquery.cookie plugin with js.cookie !7085
- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method
- Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens
- Show full status link on MR & commit pipelines
- Fix documents and comments on Build API `scope`
- Initialize Sidekiq with the list of queues used by GitLab
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
- Shortened merge request modal to let clipboard button not overlap
- Adds JavaScript validation for group path editing field
- In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo)
- Improve search query parameter naming in /admin/users !7115 (YarNayar)
- Fix table pagination to be responsive
- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
- Updated commit SHA styling on the branches page.
## 8.13.3 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
- Fix relative links in Markdown wiki when displayed in "Project" tab !7218
- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project
- Fix project features default values
- Changed build dropdown list length to be 6,5 builds long in the pipeline graph
## 8.13.2 (2016-10-31)
......@@ -229,6 +266,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix broken Project API docs (Takuya Noguchi)
- Migrate invalid project members (owner -> master)
## 8.12.8 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
## 8.12.7
- Prevent running `GfmAutocomplete` setup for each diff note. !6569
......@@ -488,6 +530,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs
## 8.11.10 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
## 8.11.9
- Don't send Private-Token (API authentication) headers to Sentry
......@@ -728,6 +774,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done
## 8.10.13 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
## 8.10.12
- Don't send Private-Token (API authentication) headers to Sentry
......
......@@ -19,7 +19,6 @@
- [Technical debt](#technical-debt)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
......@@ -247,13 +246,7 @@ request is as follows:
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, branch away from `master`
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG.md](CHANGELOG.md):
1. If you are fixing a ~regression issue, you can add your entry to the next
patch release (e.g. `8.12.5` if current version is `8.12.4`)
1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if
current version is `8.12.4`
1. Please add your entry at a random place among the entries of the targeted
release
1. [Generate a changelog entry with `bin/changelog`][changelog]
1. If you are writing documentation, make sure to follow the
[documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
......@@ -262,8 +255,11 @@ request is as follows:
1. Submit a merge request (MR) to the `master` branch
1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you
used to achieve it, see the [merge request description format]
(#merge-request-description-format)
used to achieve it.
1. If you are contributing code, fill in the template already provided in the
"Description" field.
1. If you are contributing documentation, choose `Documentation` from the
"Choose a template" menu and fill in the template.
1. If the MR changes the UI it should include *Before* and *After* screenshots
1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R`
......@@ -469,6 +465,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[contributor-covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[changelog]: doc/development/changelog.md "Generate a changelog entry"
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
......
......@@ -104,7 +104,7 @@ gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie'
gem 'gitlab-markup', '~> 1.5.0'
gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~>3.6'
gem 'rdoc', '~> 4.2'
gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
......@@ -117,7 +117,7 @@ gem 'truncato', '~> 0.7.8'
gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs
gem 'diffy', '~> 3.0.3'
gem 'diffy', '~> 3.1.0'
# Application server
group :unicorn do
......@@ -196,7 +196,7 @@ gem 'loofah', '~> 2.0.3'
gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing
gem 'rack-attack', '~> 4.3.1'
gem 'rack-attack', '~> 4.4.1'
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
......@@ -260,9 +260,6 @@ group :development do
gem 'better_errors', '~> 1.0.1'
gem 'binding_of_caller', '~> 0.7.2'
# Docs generator
gem 'sdoc', '~> 0.3.20'
# thin instead webrick
gem 'thin', '~> 1.7.0'
end
......
......@@ -180,7 +180,7 @@ GEM
railties
rotp (~> 2.0)
diff-lcs (1.2.5)
diffy (3.0.7)
diffy (3.1.0)
docile (1.1.5)
doorkeeper (4.2.0)
railties (>= 4.2)
......@@ -520,7 +520,7 @@ GEM
rack (1.6.4)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.3.1)
rack-attack (4.4.1)
rack
rack-cors (0.4.0)
rack-mount (0.8.3)
......@@ -567,7 +567,7 @@ GEM
ffi (>= 0.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rdoc (3.12.2)
rdoc (4.2.2)
json (~> 1.4)
recaptcha (3.0.0)
json
......@@ -663,9 +663,6 @@ GEM
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
......@@ -847,7 +844,7 @@ DEPENDENCIES
default_value_for (~> 3.0.0)
devise (~> 4.2)
devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3)
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
......@@ -929,14 +926,14 @@ DEPENDENCIES
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
pry-rails (~> 0.3.4)
rack-attack (~> 4.3.1)
rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rails (= 4.2.7.1)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rblineprof (~> 0.3.6)
rdoc (~> 3.6)
rdoc (~> 4.2)
recaptcha (~> 3.0)
redcarpet (~> 3.3.3)
redis (~> 3.2)
......@@ -956,7 +953,6 @@ DEPENDENCIES
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0)
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 2.0.0)
......
......@@ -80,7 +80,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Redis 2.8+
- MySQL or PostgreSQL
For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
## Third-party applications
......@@ -96,7 +96,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/
## Documentation
All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ce/).
## Getting help
......
......@@ -22,16 +22,14 @@
});
},
// Return groups list. Filtered by query
// Only active groups retrieved
groups: function(query, skip_ldap, skip_groups, callback) {
groups: function(query, options, callback) {
var url = Api.buildUrl(Api.groupsPath);
return $.ajax({
url: url,
data: {
search: query,
skip_groups: skip_groups,
per_page: 20
},
data: $.extend({
search: query,
per_page: 20
}, options),
dataType: "json"
}).done(function(groups) {
return callback(groups);
......
This diff is collapsed.
/* eslint-disable */
(function() {
this.AwardsHandler = (function() {
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
function AwardsHandler() {
this.aliases = gl.emojiAliases();
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
......
......@@ -22,7 +22,7 @@
fallbackClass: 'is-dragging',
fallbackOnBody: true,
ghostClass: 'is-ghost',
filter: '.has-tooltip, .btn',
filter: '.board-delete, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 50,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
......
......@@ -12,7 +12,7 @@
submit.disable();
$('.js-confirm-danger-input').off('input');
$('.js-confirm-danger-input').on('input', function() {
if (rstrip($(this).val()) === project_path) {
if (gl.utils.rstrip($(this).val()) === project_path) {
return submit.enable();
} else {
return submit.disable();
......
......@@ -299,7 +299,7 @@
};
Dispatcher.prototype.initFieldErrors = function() {
$('.show-gl-field-errors').each((i, form) => {
$('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form);
});
};
......
......@@ -126,8 +126,8 @@
}
return {
username: m.username,
title: sanitize(title),
search: sanitize(m.username + " " + m.name)
title: gl.utils.sanitize(title),
search: gl.utils.sanitize(m.username + " " + m.name)
};
});
}
......@@ -159,7 +159,7 @@
}
return {
id: i.iid,
title: sanitize(i.title),
title: gl.utils.sanitize(i.title),
search: i.iid + " " + i.title
};
});
......@@ -189,7 +189,7 @@
}
return {
id: m.iid,
title: sanitize(m.title),
title: gl.utils.sanitize(m.title),
search: "" + m.title
};
});
......@@ -222,7 +222,7 @@
}
return {
id: m.iid,
title: sanitize(m.title),
title: gl.utils.sanitize(m.title),
search: m.iid + " " + m.title
};
});
......@@ -240,9 +240,9 @@
var sanitizeLabelTitle;
sanitizeLabelTitle = function(title) {
if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
return "\"" + (sanitize(title)) + "\"";
return "\"" + (gl.utils.sanitize(title)) + "\"";
} else {
return sanitize(title);
return gl.utils.sanitize(title);
}
};
return $.map(merges, function(m) {
......
/* eslint-disable no-param-reassign */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `gl-show-field-errors` to the form element, and ensure error messages are
* declared in each inputs' `title` attribute. If no title is declared for an invalid
* field the user attempts to submit, "This field is required." will be shown by default.
*
* Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
*
* Set a custom error anchor for error message to be injected after with the
* class `gl-field-error-anchor`
*
* Examples:
*
* Basic:
*
* <form class='gl-show-field-errors'>
* <input type='text' name='username' title='Username is required.'/>
* </form>
*
* Ignore specific inputs (e.g. UsernameValidator):
*
* <form class='gl-show-field-errors'>
* <div class="form-group>
* <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
* </div>
* <div class="form-group">
* <input type='text' name='username' title='Username is required.'/>
* </div>
* </form>
*
* Custom Error Anchor (allows error message to be injected after specified element):
*
* <form class='gl-show-field-errors'>
* <div class="form-group gl-field-error-anchor">
* <input type='text' name='username' title='Username is required.'/>
* // Error message typically injected here
* </div>
* // Error message now injected here
* </form>
*
* */
/*
* Regex Patterns in use:
*
* Only alphanumeric: : "[a-zA-Z0-9]+"
* No special characters : "[a-zA-Z0-9-_]+",
*
* */
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore';
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
this.state = {
valid: false,
empty: true,
};
this.initFieldValidation();
}
initFieldValidation() {
const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
// hidden when injected into DOM
errorAnchor.after(this.fieldErrorElement);
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
safelySelectSiblings() {
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
const parentContainer = this.inputElement.parent('.form-group');
// Only select siblings when they're scoped within a form-group with one input
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
}
renderValidity() {
this.renderClear();
if (this.state.valid) {
this.renderValid();
} else if (this.state.empty) {
this.renderEmpty();
} else if (!this.state.valid) {
this.renderInvalid();
}
}
handleInvalidSubmit(event) {
event.preventDefault();
const currentValue = this.accessCurrentValue();
this.state.valid = false;
this.state.empty = currentValue === '';
this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.fieldValidator')
.on('keyup.fieldValidator', this.updateValidity.bind(this));
}
/* Get or set current input value */
accessCurrentValue(newVal) {
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
}
getInputValidity() {
return this.inputDomElement.validity.valid;
}
updateValidity() {
const inputVal = this.accessCurrentValue();
this.state.empty = !inputVal.length;
this.state.valid = this.getInputValidity();
this.renderValidity();
}
renderValid() {
return this.renderClear();
}
renderEmpty() {
return this.renderInvalid();
}
renderInvalid() {
this.inputElement.addClass(inputErrorClass);
this.scopedSiblings.hide();
return this.fieldErrorElement.show();
}
renderClear() {
const inputVal = this.accessCurrentValue();
if (!inputVal.split(' ').length) {
const trimmedInput = inputVal.trim();
this.accessCurrentValue(trimmedInput);
}
this.inputElement.removeClass(inputErrorClass);
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
global.GlFieldError = GlFieldError;
})(window.gl || (window.gl = {}));
/* eslint-disable */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `show-gl-field-errors` to the form element, and ensure error messages are
* declared in each inputs' title attribute.
*
* Example:
*
* <form class='show-gl-field-errors'>
* <input type='text' name='username' title='Username is required.'/>
*</form>
*
* */
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
this.state = {
valid: false,
empty: true
};
this.initFieldValidation();
}
initFieldValidation() {
// hidden when injected into DOM
this.inputElement.after(this.fieldErrorElement);
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
safelySelectSiblings() {
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
const ignoreSelector = '.validation-ignore';
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
const parentContainer = this.inputElement.parent('.form-group');
// Only select siblings when they're scoped within a form-group with one input
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
}
renderValidity() {
this.renderClear();
if (this.state.valid) {
return this.renderValid();
}
if (this.state.empty) {
return this.renderEmpty();
}
if (!this.state.valid) {
return this.renderInvalid();
}
}
//= require gl_field_error
handleInvalidSubmit(event) {
event.preventDefault();
const currentValue = this.accessCurrentValue();
this.state.valid = false;
this.state.empty = currentValue === '';
this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.field_validator')
.on('keyup.field_validator', this.updateValidity.bind(this));
}
/* Get or set current input value */
accessCurrentValue(newVal) {
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
}
getInputValidity() {
return this.inputDomElement.validity.valid;
}
updateValidity() {
const inputVal = this.accessCurrentValue();
this.state.empty = !inputVal.length;
this.state.valid = this.getInputValidity();
this.renderValidity();
}
renderValid() {
return this.renderClear();
}
renderEmpty() {
return this.renderInvalid();
}
renderInvalid() {
this.inputElement.addClass(inputErrorClass);
this.scopedSiblings.hide();
return this.fieldErrorElement.show();
}
renderClear() {
const inputVal = this.accessCurrentValue();
if (!inputVal.split(' ').length) {
const trimmedInput = inputVal.trim();
this.accessCurrentValue(trimmedInput);
}
this.inputElement.removeClass(inputErrorClass);
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
const customValidationFlag = 'no-gl-field-errors';
((global) => {
const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors {
constructor(form) {
......@@ -144,7 +22,7 @@
this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new GlFieldError({ input, formErrors: this }));
.map((input) => new global.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit);
}
......
......@@ -24,8 +24,8 @@
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
......
/* eslint-disable */
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// Add new JavaScript code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
......
......@@ -6,15 +6,16 @@
function GroupsSelect() {
$('.ajax-groups-select').each((function(_this) {
return function(i, select) {
var skip_ldap, skip_groups;
skip_ldap = $(select).hasClass('skip_ldap');
var all_available, skip_groups;
all_available = $(select).data('all-available');
skip_groups = $(select).data('skip-groups') || [];
return $(select).select2({
placeholder: "Search for a group",
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
return Api.groups(query.term, skip_ldap, skip_groups, function(groups) {
options = { all_available: all_available, skip_groups: skip_groups };
return Api.groups(query.term, options, function(groups) {
var data;
data = {
results: groups
......
......@@ -24,6 +24,81 @@
return null;
}
};
w.gl.utils.ajaxGet = function(url) {
return $.ajax({
type: "GET",
url: url,
dataType: "script"
});
};
w.gl.utils.split = function(val) {
return val.split(/,\s*/);
};
w.gl.utils.extractLast = function(term) {
return this.split(term).pop();
};
w.gl.utils.rstrip = function rstrip(val) {
if (val) {
return val.replace(/\s+$/, '');
} else {
return val;
}
};
w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input';
var closest_submit, field, that;
that = this;
field = $(field_selector);
closest_submit = field.closest('form').find(button_selector);
if (this.rstrip(field.val()) === "") {
closest_submit.disable();
}
return field.on(event_name, function() {
if (that.rstrip($(this).val()) === "") {
return closest_submit.disable();
} else {
return closest_submit.enable();
}
});
};
w.gl.utils.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
var closest_submit, updateButtons;
closest_submit = form.find(button_selector);
updateButtons = function() {
var filled;
filled = true;
form.find('input').filter(form_selector).each(function() {
return filled = this.rstrip($(this).val()) !== "" || !$(this).attr('required');
});
if (filled) {
return closest_submit.enable();
} else {
return closest_submit.disable();
}
};
updateButtons();
return form.keyup(updateButtons);
};
w.gl.utils.sanitize = function(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
};
w.gl.utils.unbindEvents = function() {
return $(document).off('scroll');
};
w.gl.utils.shiftWindow = function() {
return w.scrollBy(0, -100);
};
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
};
......
......@@ -11,7 +11,7 @@
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
$('.js-member-update-control').off('change').on('change', this.formSubmit);
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
removeRow(e) {
......
......@@ -238,8 +238,11 @@
_this.expandViewContainer();
}
_this.diffsLoaded = true;
_this.scrollToElement("#diffs");
_this.highlighSelectedLine();
var anchoredDiff = gl.utils.getLocationHash();
if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() {
_this.scrollToElement("#diffs");
_this.highlighSelectedLine();
});
_this.filesCommentButton = $('.files .diff-file').filesCommentButton();
return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) {
e.preventDefault();
......@@ -252,6 +255,17 @@
});
};
MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) {
var diffTitle = $('#file-path-' + anchoredDiff);
var diffFile = diffTitle.closest('.diff-file');
var nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
diffFile.singleFileDiff(true, cb);
} else {
cb();
}
};
MergeRequestTabs.prototype.highlighSelectedLine = function() {
var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight;
$('.hll').removeClass('hll');
......
/* eslint-disable */
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// Add new JavaScript code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
......
......@@ -45,7 +45,9 @@
};
ProjectNew.prototype.toggleRepoVisibility = function () {
var $repoAccessLevel = $('.js-repo-access-level select');
var $repoAccessLevel = $('.js-repo-access-level select'),
containerRegistry = document.querySelectorAll('.js-container-registry')[0],
containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll()
......@@ -70,8 +72,17 @@
if (selectedVal) {
this.$repoSelects.removeClass('disabled');
if (containerRegistry) {
containerRegistry.style.display = '';
}
} else {
this.$repoSelects.addClass('disabled');
if (containerRegistry) {
containerRegistry.style.display = 'none';
containerRegistryCheckbox.checked = false;
}
}
}.bind(this));
};
......
......@@ -24,7 +24,7 @@
data = groups.concat(projects);
return finalCallback(data);
};
return Api.groups(term, false, false, groupsCallback);
return Api.groups(term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
......@@ -73,7 +73,7 @@
data = groups.concat(projects);
return finalCallback(data);
};
return Api.groups(query.term, false, false, groupsCallback);
return Api.groups(query.term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
......
......@@ -11,7 +11,7 @@
filterable: true,
fieldName: 'group_id',
data: function(term, callback) {
return Api.groups(term, false, false, function(data) {
return Api.groups(term, {}, function(data) {
data.unshift({
name: 'Any'
});
......
......@@ -13,7 +13,7 @@
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file) {
function SingleFileDiff(file, forceLoad, cb) {
this.file = file;
this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file);
......@@ -32,9 +32,12 @@
this.$toggleIcon.addClass('fa-caret-down');
}
$('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
if (forceLoad) {
this.toggleDiff(null, cb);
}
}
SingleFileDiff.prototype.toggleDiff = function(e) {
SingleFileDiff.prototype.toggleDiff = function(e, cb) {
var $target = $(e.target);
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen;
......@@ -54,11 +57,11 @@
}
} else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML();
return this.getContentHTML(cb);
}
};
SingleFileDiff.prototype.getContentHTML = function() {
SingleFileDiff.prototype.getContentHTML = function(cb) {
this.collapsedContent.hide();
this.loadingContent.show();
$.get(this.diffForPath, (function(_this) {
......@@ -76,6 +79,8 @@
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
}
if (cb) cb();
};
})(this));
};
......@@ -84,10 +89,10 @@
})();
$.fn.singleFileDiff = function() {
$.fn.singleFileDiff = function(forceLoad, cb) {
return this.each(function() {
if (!$.data(this, 'singleFileDiff')) {
return $.data(this, 'singleFileDiff', new SingleFileDiff(this));
if (!$.data(this, 'singleFileDiff') || forceLoad) {
return $.data(this, 'singleFileDiff', new SingleFileDiff(this, forceLoad, cb));
}
});
};
......
......@@ -11,11 +11,9 @@
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text('Star');
gl.utils.updateTooltipTitle($this, 'Star project');
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
$starSpan.addClass('starred').text('Unstar');
gl.utils.updateTooltipTitle($this, 'Unstar project');
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
};
......
.avatar {
@mixin avatar-size($size, $margin-right) {
width: $size;
height: $size;
margin-right: $margin-right;
}
.avatar-container {
float: left;
margin-right: 12px;
margin-right: 15px;
border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1);
&.s16 { @include avatar-size(16px, 6px); }
&.s20 { @include avatar-size(20px, 7px); }
&.s24 { @include avatar-size(24px, 8px); }
&.s26 { @include avatar-size(26px, 8px); }
&.s32 { @include avatar-size(32px, 10px); }
&.s36 { @include avatar-size(36px, 10px); }
&.s40 { @include avatar-size(40px, 10px); }
&.s46 { @include avatar-size(46px, 15px); }
&.s48 { @include avatar-size(48px, 10px); }
&.s60 { @include avatar-size(60px, 12px); }
&.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); }
&.s110 { @include avatar-size(110px, 15px); }
&.s140 { @include avatar-size(140px, 15px); }
&.s160 { @include avatar-size(160px, 20px); }
}
.avatar {
@extend .avatar-container;
width: 40px;
height: 40px;
padding: 0;
border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline {
float: none;
......@@ -20,22 +45,6 @@
border-radius: 0;
border: none;
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
&.s20 { width: 20px; height: 20px; margin-right: 7px; }
&.s24 { width: 24px; height: 24px; margin-right: 8px; }
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; }
&.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s70 { width: 70px; height: 70px; margin-right: 14px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
.identicon {
......@@ -54,3 +63,17 @@
&.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; }
}
.image-container {
@extend .avatar-container;
overflow: hidden;
display: flex;
.avatar {
border-radius: 0;
border: none;
height: auto;
margin: 0;
align-self: center;
}
}
\ No newline at end of file
......@@ -216,7 +216,7 @@
svg,
.fa {
&:not(:last-child) {
margin-right: 3px;
margin-right: 5px;
}
}
}
......
......@@ -36,7 +36,7 @@
color: $dropdown-toggle-color;
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
border: 1px solid $border-color;
border-radius: $border-radius-base;
outline: 0;
text-overflow: ellipsis;
......@@ -45,11 +45,9 @@
.fa {
position: absolute;
top: 50%;
right: 6px;
margin-top: -6px;
top: 10px;
right: 8px;
color: $dropdown-toggle-icon-color;
font-size: 10px;
&.fa-spinner {
font-size: 16px;
......
......@@ -136,3 +136,35 @@ label {
color: $red-normal;
}
.gl-show-field-errors {
.gl-field-success-outline {
border: 1px solid $green-normal;
&:focus {
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
border: 0 none;
}
}
.gl-field-error-outline {
border: 1px solid $red-normal;
&:focus {
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
border: 0 none;
}
}
.gl-field-success-message {
color: $green-normal;
}
.gl-field-error-message {
color: $red-normal;
}
.gl-field-hint {
color: $gl-text-color;
}
}
......@@ -21,57 +21,66 @@
background: $color-darker;
}
.nav-sidebar li {
a {
color: $color-light;
&:hover,
&:focus,
&:active {
background: $color-dark;
}
.sidebar-header,
.sidebar-action-buttons {
color: $color-light;
background-color: lighten($color-darker, 5%);
}
i {
.nav-sidebar {
li {
a {
color: $color-light;
}
path,
polygon {
fill: $color-light;
}
.count {
color: $color-light;
background: $color-dark;
&:hover,
&:focus,
&:active {
background: $color-dark;
}
i {
color: $color-light;
}
path,
polygon {
fill: $color-light;
}
.count {
color: $color-light;
background: $color-dark;
}
svg {
position: relative;
top: 3px;
}
}
svg {
position: relative;
top: 3px;
&.separate-item {
border-top: 1px solid $color;
}
}
&.separate-item {
border-top: 1px solid $color;
}
&.active a {
color: $white-light;
background: $color-dark;
&.active a {
color: $white-light;
background: $color-dark;
&.no-highlight {
border: none;
}
&.no-highlight {
border: none;
}
i {
color: $white-light;
}
i {
color: $white-light;
}
path,
polygon {
fill: $white-light;
path,
polygon {
fill: $white-light;
}
}
}
}
}
}
......
......@@ -49,12 +49,16 @@ header {
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
margin-left: 10px;
margin-left: 8px;
height: 28px;
min-width: 28px;
line-height: 28px;
text-align: center;
&.header-user-dropdown-toggle {
margin-left: 14px;
}
&:hover,
&:focus,
&:active {
......
......@@ -142,10 +142,6 @@ ul.content-list {
}
}
.avatar {
margin-right: 15px;
}
.controls {
float: right;
......
......@@ -7,8 +7,70 @@
.pagination {
padding: 0;
}
.gap,
.gap:hover {
background-color: $gray-light;
padding: $gl-vert-padding;
cursor: default;
}
}
.panel > .gl-pagination {
margin: 0;
}
/**
* Extra-small screen pagination.
*/
@media (max-width: 320px) {
.gl-pagination {
.first,
.last {
display: none;
}
.page {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Small screen pagination
*/
@media (max-width: $screen-xs) {
.gl-pagination {
.pagination li a {
padding: 6px 10px;
}
.page {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Medium screen pagination
*/
@media (min-width: $screen-xs) and (max-width: $screen-md-max) {
.gl-pagination {
.page {
display: none;
&.active,
&.sibling {
display: inline;
}
}
}
}
......@@ -27,9 +27,9 @@
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: $caret-width-base dashed;
border-right: $caret-width-base solid transparent;
border-left: $caret-width-base solid transparent;
border-top: 5px dashed;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
color: $gray-darkest;
}
}
......
......@@ -59,6 +59,11 @@
padding: 0 !important;
}
.sidebar-header {
padding: 11px 22px 12px;
font-size: 20px;
}
li {
&.separate-item {
padding-top: 10px;
......
.awards {
.emoji-icon {
width: 20px;
height: 20px;
width: 19px;
height: 19px;
}
}
......@@ -94,7 +94,7 @@
.award-control {
margin: 3px 5px 3px 0;
padding: 6px 5px;
padding: 5px 6px;
outline: 0;
&:hover,
......@@ -127,7 +127,7 @@
.award-control-icon {
float: left;
margin-right: 5px;
font-size: 20px;
font-size: 19px;
}
.award-control-icon-loading {
......
......@@ -12,6 +12,10 @@
opacity: 1!important;
* {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
// !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important;
cursor: grabbing!important;
......@@ -45,11 +49,6 @@
.page-with-sidebar {
padding-bottom: 0;
}
.issues-filters {
position: relative;
z-index: 999999;
}
}
.boards-app {
......
......@@ -52,10 +52,25 @@
.build-header {
position: relative;
padding-right: 40px;
padding: 0;
display: flex;
min-height: 58px;
align-items: center;
@media (min-width: $screen-sm-min) {
padding-right: 0;
.btn-inverted {
@include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light);
}
@media (max-width: $screen-sm-max) {
padding-right: 40px;
.btn-inverted {
display: none;
}
}
.header-content {
flex: 1;
}
a {
......@@ -137,10 +152,15 @@
.retry-link {
color: $gl-link-color;
display: none;
&:hover {
text-decoration: underline;
}
@media (max-width: $screen-sm-max) {
display: block;
}
}
.stage-item {
......
......@@ -164,7 +164,22 @@
.branch-commit {
color: $gl-gray;
.commit-id,
.commit-icon {
text-align: center;
display: inline-block;
svg {
height: 14px;
width: 14px;
vertical-align: middle;
fill: $table-text-gray;
}
}
.commit-id {
color: $gl-link-color;
}
.commit-row-message {
color: $gl-gray;
}
......
......@@ -36,10 +36,6 @@
}
}
.dash-project-avatar {
float: left;
}
.dash-project-access-icon {
float: left;
margin-right: 5px;
......
......@@ -55,6 +55,10 @@
float: left;
}
.file-buttons {
font-size: 0;
}
.select2 {
float: right;
}
......
......@@ -3,12 +3,14 @@
}
.dashboard .side .panel .panel-heading .input-group {
.form-control {
height: 42px;
}
}
.group-row {
.stats {
float: right;
line-height: $list-text-height;
......@@ -21,12 +23,14 @@
}
.ldap-group-links {
.form-actions {
margin-bottom: $gl-padding;
}
}
.groups-cover-block {
.container-fluid {
position: relative;
}
......@@ -41,9 +45,14 @@
background-color: $background-color;
}
}
.group-avatar {
border: 0;
}
}
.groups-header {
@media (min-width: $screen-sm-min) {
.nav-links {
width: 35%;
......
// CI icon colors
.ci-status-icon {
&-created {
fill: $gray-darkest;
}
&-skipped,
&-canceled {
fill: $gl-text-color;
}
}
......@@ -75,43 +75,17 @@
.login-body {
font-size: 13px;
input + p {
margin-top: 5px;
}
.gl-field-success-outline {
border: 1px solid $green-normal;
&:focus {
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
border: 0 none;
}
}
.gl-field-error-outline {
border: 1px solid $red-normal;
&:focus {
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
border: 0 none;
}
}
.username .validation-success,
.gl-field-success-message {
.username .validation-success {
color: $green-normal;
}
.username .validation-error,
.gl-field-error-message {
.username .validation-error {
color: $red-normal;
}
.gl-field-hint {
color: $gl-text-color;
}
}
}
......
......@@ -105,11 +105,6 @@ ul.notes {
padding: 2px;
margin-top: 10px;
}
.award-control {
font-size: 13px;
padding: 2px 5px;
}
}
.note-header {
......
......@@ -85,6 +85,11 @@
}
.commit-link {
a {
&:focus {
text-decoration: none;
}
}
.ci-status {
......@@ -439,7 +444,7 @@
}
.grouped-pipeline-dropdown {
padding: 8px 0;
padding: 0;
width: 186px;
left: auto;
right: -197px;
......@@ -448,6 +453,14 @@
ul {
max-height: 245px;
overflow: auto;
li:first-child {
padding-top: 8px;
}
li:last-child {
padding-bottom: 8px;
}
}
a {
......
......@@ -96,8 +96,8 @@
.project-avatar {
float: none;
margin-left: auto;
margin-right: auto;
margin: 0 auto;
border: none;
&.identicon {
border-radius: 50%;
......
......@@ -6,7 +6,8 @@
white-space: nowrap;
border-radius: 4px;
&:hover {
&:hover,
&:focus {
text-decoration: none;
}
......
......@@ -161,3 +161,63 @@
}
}
}
.todos-empty {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
max-width: 900px;
margin-left: auto;
margin-right: auto;
@media (min-width: $screen-sm-min) {
-webkit-flex-direction: row;
flex-direction: row;
padding-top: 80px;
}
}
.todos-empty-content {
-webkit-align-self: center;
align-self: center;
max-width: 480px;
margin-right: 20px;
}
.todos-empty-hero {
width: 200px;
margin-left: auto;
margin-right: auto;
@media (min-width: $screen-sm-min) {
width: 300px;
margin-right: 0;
-webkit-order: 2;
order: 2;
}
}
.todos-all-done {
padding-top: 20px;
@media (min-width: $screen-sm-min) {
padding-top: 50px;
}
> svg {
display: block;
max-width: 300px;
margin: 0 auto 20px;
}
p {
max-width: 470px;
margin-left: auto;
margin-right: auto;
}
a {
font-weight: 600;
}
}
......@@ -116,8 +116,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_packet_size,
:send_user_confirmation_email,
:container_registry_token_expire_delay,
:repository_storage,
:enabled_git_access_protocol,
repository_storages: [],
restricted_visibility_levels: [],
import_sources: [],
disabled_oauth_sign_in_sources: []
......
......@@ -3,7 +3,7 @@ class Admin::UsersController < Admin::ApplicationController
def index
@users = User.order_name_asc.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present?
@users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.page(params[:page])
end
......
......@@ -126,7 +126,7 @@ class Projects::LabelsController < Projects::ApplicationController
alias_method :subscribable_resource, :label
def find_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities)
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end
def authorize_admin_labels!
......
......@@ -25,18 +25,15 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def create
if params[:user_ids].blank?
return redirect_to(namespace_project_project_members_path(@project.namespace, @project), alert: 'No users or groups specified.')
end
status = Members::CreateService.new(@project, current_user, params).execute
@project.team.add_users(
params[:user_ids].split(','),
params[:access_level],
expires_at: params[:expires_at],
current_user: current_user
)
redirect_url = namespace_project_project_members_path(@project.namespace, @project)
redirect_to namespace_project_project_members_path(@project.namespace, @project), notice: 'Users were successfully added.'
if status
redirect_to redirect_url, notice: 'Users were successfully added.'
else
redirect_to redirect_url, alert: 'No users or groups specified.'
end
end
def update
......
......@@ -289,7 +289,8 @@ class ProjectsController < Projects::ApplicationController
render 'projects/empty' if @project.empty_repo?
else
if @project.wiki_enabled?
@wiki_home = @project.wiki.find_page('home', params[:version_id])
@project_wiki = @project.wiki
@wiki_home = @project_wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
@issues = issues_collection
@issues = @issues.page(params[:page])
......
......@@ -126,7 +126,7 @@ class IssuableFinder
@labels =
if labels? && !filter_by_no_label?
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true)
else
Label.none
end
......@@ -273,7 +273,7 @@ class IssuableFinder
items = items.with_label(label_names, params[:sort])
if projects
label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id)
label_ids = LabelsFinder.new(current_user, project_ids: projects).execute(skip_authorization: true).select(:id)
items = items.where(labels: { id: label_ids })
end
end
......
......@@ -93,11 +93,11 @@ module ApplicationSettingsHelper
end
end
def repository_storage_options_for_select
def repository_storages_options_for_select
options = Gitlab.config.repositories.storages.map do |name, path|
["#{name} - #{path}", name]
end
options_for_select(options, @application_setting.repository_storage)
options_for_select(options, @application_setting.repository_storages)
end
end
......@@ -47,8 +47,10 @@ module CiStatusHelper
'icon_play'
when 'created'
'icon_status_created'
when 'skipped'
'icon_status_skipped'
else
'icon_status_cancel'
'icon_status_canceled'
end
custom_icon(icon_name)
......
......@@ -43,7 +43,7 @@ module DropdownsHelper
default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down')
output << icon('caret-down')
output.html_safe
end
end
......
......@@ -39,6 +39,12 @@ module EventsHelper
end
end
def event_filter_visible(feature_key)
return true unless @project
@project.feature_available?(feature_key, current_user)
end
def event_preposition(event)
if event.push? || event.commented? || event.target
"at"
......
......@@ -18,6 +18,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :disabled_oauth_sign_in_sources, Array
serialize :domain_whitelist, Array
serialize :domain_blacklist, Array
serialize :repository_storages
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
......@@ -74,9 +75,8 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :repository_storages, presence: true
validate :check_repository_storages
validates :enabled_git_access_protocol,
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
......@@ -166,7 +166,7 @@ class ApplicationSetting < ActiveRecord::Base
disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false,
container_registry_token_expire_delay: 5,
repository_storage: 'default',
repository_storages: ['default'],
user_default_external: false,
)
end
......@@ -201,6 +201,29 @@ class ApplicationSetting < ActiveRecord::Base
self.domain_blacklist_raw = file.read
end
def repository_storages
value = read_attribute(:repository_storages)
value = [value] if value.is_a?(String)
value = [] if value.nil?
value
end
# repository_storage is still required in the API. Remove in 9.0
def repository_storage
repository_storages.first
end
def repository_storage=(value)
self.repository_storages = [value]
end
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
repository_storages.sample
end
def runners_registration_token
ensure_runners_registration_token!
end
......@@ -208,4 +231,12 @@ class ApplicationSetting < ActiveRecord::Base
def health_check_access_token
ensure_health_check_access_token!
end
private
def check_repository_storages
invalid = repository_storages - Gitlab.config.repositories.storages.keys
errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
invalid.empty?
end
end
......@@ -31,7 +31,7 @@ module ProjectFeaturesCompatibility
def write_feature_attribute(field, value)
build_project_feature unless project_feature
access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.update_attribute(field, access_level)
end
end
......@@ -5,6 +5,10 @@ class GroupLabel < Label
alias_attribute :subject, :group
def subject_foreign_key
'group_id'
end
def to_reference(source_project = nil, target_project = nil, format: :id)
super(source_project, target_project, format: format)
end
......
......@@ -92,16 +92,23 @@ class Label < ActiveRecord::Base
nil
end
def open_issues_count(user = nil, project = nil)
issues_count(user, project_id: project.try(:id) || project_id, state: 'opened')
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
def closed_issues_count(user = nil, project = nil)
issues_count(user, project_id: project.try(:id) || project_id, state: 'closed')
def closed_issues_count(user = nil)
issues_count(user, state: 'closed')
end
def open_merge_requests_count(user = nil, project = nil)
merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened')
def open_merge_requests_count(user = nil)
params = {
subject_foreign_key => subject.id,
label_name: title,
scope: 'all',
state: 'opened'
}
MergeRequestsFinder.new(user, params.with_indifferent_access).execute.count
end
def prioritize!(project, value)
......@@ -167,15 +174,8 @@ class Label < ActiveRecord::Base
end
def issues_count(user, params = {})
IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
.execute
.count
end
def merge_requests_count(user, params = {})
MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
.execute
.count
params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all')
IssuesFinder.new(user, params.with_indifferent_access).execute.count
end
def label_format_reference(format = :id)
......
......@@ -441,11 +441,11 @@ class MergeRequest < ActiveRecord::Base
end
def should_remove_source_branch?
merge_params['should_remove_source_branch'].present?
Gitlab::Utils.to_boolean(merge_params['should_remove_source_branch'])
end
def force_remove_source_branch?
merge_params['force_remove_source_branch'].present?
Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end
def remove_source_branch?
......
......@@ -28,8 +28,13 @@ class Project < ActiveRecord::Base
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
after_create :ensure_dir_exist
after_create :create_project_feature, unless: :project_feature
......@@ -390,7 +395,7 @@ class Project < ActiveRecord::Base
end
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id)
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
end
......
......@@ -12,6 +12,10 @@ class ProjectLabel < Label
alias_attribute :subject, :project
def subject_foreign_key
'project_id'
end
def to_reference(target_project = nil, format: :id)
super(project, target_project, format: format)
end
......
......@@ -237,7 +237,7 @@ class JiraService < IssueTrackerService
end
def resource_url(resource)
"#{Settings.gitlab['url'].chomp("/")}#{resource}"
"#{Settings.gitlab.base_url.chomp("/")}#{resource}"
end
def build_entity_url(entity_name, entity_id)
......
......@@ -258,6 +258,24 @@ class User < ActiveRecord::Base
)
end
# searches user by given pattern
# it compares name, email, username fields and user's secondary emails with given pattern
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query)
table = arel_table
email_table = Email.arel_table
pattern = "%#{query}%"
matched_by_emails_user_ids = email_table.project(email_table[:user_id]).where(email_table[:email].matches(pattern))
where(
table[:name].matches(pattern).
or(table[:email].matches(pattern)).
or(table[:username].matches(pattern)).
or(table[:id].in(matched_by_emails_user_ids))
)
end
def by_login(login)
return nil unless login
......
......@@ -2,11 +2,11 @@ class ProjectPolicy < BasePolicy
def rules
team_access!(user)
owner = user.admin? ||
project.owner == user ||
owner = project.owner == user ||
(project.group && project.group.has_owner?(user))
owner_access! if owner
owner_access! if user.admin? || owner
team_member_owner_access! if owner
if project.public? || (project.internal? && !user.external?)
guest_access!
......@@ -16,7 +16,7 @@ class ProjectPolicy < BasePolicy
can! :read_build if project.public_builds?
if project.request_access_enabled &&
!(owner || project.team.member?(user) || project_group_member?(user))
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access
end
end
......@@ -135,6 +135,10 @@ class ProjectPolicy < BasePolicy
can! :destroy_issue
end
def team_member_owner_access!
team_member_reporter_access!
end
# Push abilities on the users team role
def team_access!(user)
access = project.team.max_member_access(user.id)
......
module Members
class CreateService < BaseService
def execute
return false if params[:user_ids].blank?
project.team.add_users(
params[:user_ids].split(','),
params[:access_level],
expires_at: params[:expires_at],
current_user: current_user
)
true
end
end
end
......@@ -13,20 +13,8 @@ module MergeRequests
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
message =
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
return build_failed(merge_request, message)
end
if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch
return build_failed(merge_request, 'You must select different branches')
end
messages = validate_branches(merge_request)
return build_failed(merge_request, messages) unless messages.empty?
compare = CompareService.new.execute(
merge_request.source_project,
......@@ -43,6 +31,34 @@ module MergeRequests
private
def validate_branches(merge_request)
messages = []
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
messages <<
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
end
if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch
messages << 'You must select different branches'
end
# See if source and target branches exist
unless merge_request.source_project.commit(merge_request.source_branch)
messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
end
unless merge_request.target_project.commit(merge_request.target_branch)
messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
end
messages
end
# When your branch name starts with an iid followed by a dash this pattern will be
# interpreted as the user wants to close that issue on this project.
#
......@@ -91,8 +107,10 @@ module MergeRequests
merge_request
end
def build_failed(merge_request, message)
merge_request.errors.add(:base, message) unless message.nil?
def build_failed(merge_request, messages)
messages.compact.each do |message|
merge_request.errors.add(:base, message)
end
merge_request.compare_commits = []
merge_request.can_be_created = false
merge_request
......
= render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box
%form.show-gl-field-errors
%form.gl-show-field-errors
.form-group
= label_tag :login
= text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
......
......@@ -353,9 +353,9 @@
%fieldset
%legend Repository Storage
.form-group
= f.label :repository_storage, 'Storage path for new projects', class: 'control-label col-sm-2'
= f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
.col-sm-10
= f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control'
= f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control'
.help-block
Manage repository storage paths. Learn more in the
= succeed "." do
......
......@@ -16,7 +16,8 @@
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
.image-container.s40
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
......
......@@ -13,7 +13,8 @@
Group info:
%ul.well-list
%li
= image_tag group_icon(@group), class: "avatar s60"
.image-container.s60
= image_tag group_icon(@group), class: "avatar s60"
%li
%span.light Name:
%strong= @group.name
......
- @no_container = true
- page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
Gitlab::EnvironmentLogger, Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger]
= render 'admin/background_jobs/head'
......
......@@ -76,7 +76,8 @@
.title
= link_to [:admin, project.namespace.becomes(Namespace), project] do
.dash-project-avatar
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
.image-container.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace
......
......@@ -10,7 +10,7 @@
= hidden_field_tag "filter", h(params[:filter])
.search-holder
.search-field-holder
= search_field_tag :name, params[:name], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false
= search_field_tag :search_query, params[:search_query], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false
= icon("search", class: "search-icon")
.dropdown
- toggle_text = if @sort.present? then sort_options_hash[@sort] else sort_title_name end
......
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
......
......@@ -2,13 +2,13 @@
- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
= auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{current_user.name} issues")
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
= link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
......
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
.top-area
%ul.nav-links
- todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
%li{class: "todos-pending #{todo_pending_active}"}
= link_to todos_filter_path(state: 'pending') do
%span
To do
%span.badge
= number_with_delimiter(todos_pending_count)
- todo_done_active = ('active' if params[:state] == 'done')
%li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
%span.badge
= number_with_delimiter(todos_done_count)
- if current_user.todos.any?
.top-area
%ul.nav-links
- todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
%li{class: "todos-pending #{todo_pending_active}"}
= link_to todos_filter_path(state: 'pending') do
%span
To do
%span.badge
= number_with_delimiter(todos_pending_count)
- todo_done_active = ('active' if params[:state] == 'done')
%li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
%span.badge
= number_with_delimiter(todos_done_count)
.nav-controls
- if @todos.any?(&:pending?)
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
Mark all as done
= icon('spinner spin')
.nav-controls
- if @todos.any?(&:pending?)
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
Mark all as done
= icon('spinner spin')
.todos-filters
.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
- if params[:project_id].present?
= hidden_field_tag(:project_id, params[:project_id])
= dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: 'Search projects', data: { data: todo_projects_options } })
.filter-item.inline
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
.filter-item.inline
- if params[:type].present?
= hidden_field_tag(:type, params[:type])
= dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
data: { data: todo_types_options } })
.filter-item.inline.actions-filter
- if params[:action_id].present?
= hidden_field_tag(:action_id, params[:action_id])
= dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
data: { data: todo_actions_options }})
.pull-right
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to todos_filter_path(sort: sort_value_priority) do
= sort_title_priority
= link_to todos_filter_path(sort: sort_value_recently_created) do
.todos-filters
.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
- if params[:project_id].present?
= hidden_field_tag(:project_id, params[:project_id])
= dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: 'Search projects', data: { data: todo_projects_options } })
.filter-item.inline
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
.filter-item.inline
- if params[:type].present?
= hidden_field_tag(:type, params[:type])
= dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
data: { data: todo_types_options } })
.filter-item.inline.actions-filter
- if params[:action_id].present?
= hidden_field_tag(:action_id, params[:action_id])
= dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
data: { data: todo_actions_options }})
.pull-right
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
= link_to todos_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to todos_filter_path(sort: sort_value_priority) do
= sort_title_priority
= link_to todos_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to todos_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
.prepend-top-default
......@@ -78,5 +79,29 @@
%ul.content-list.todos-list
= render group[1]
= paginate @todos, theme: "gitlab"
- elsif current_user.todos.any?
.todos-all-done
= render "shared/empty_states/todos_all_done.svg"
%h4.text-center
Good job! Looks like you don't have any todos left.
%p.text-center
Are you looking for things to do? Take a look at
= succeed "," do
= link_to "the opened issues", issues_dashboard_path
contribute to
= link_to "merge requests", merge_requests_dashboard_path
or mention someone in a comment to assign a new todo automatically.
- else
.nothing-here-block You're all done!
.todos-empty
.todos-empty-hero
= render "shared/empty_states/todos_empty.svg"
.todos-empty-content
%h4
Todos let you see what you should do next.
%p
When an issue or merge request is assigned to you, or when you
%strong
@mention
in a comment, this will trigger a new item in your todo list, automatically.
%p
You will always know what to work on next.
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box
.login-body
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group
......
= render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box
.login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f|
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
......
= render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box
.login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group
......
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f|
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
%div.form-group
= f.label "Username or email", for: :login
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
......
= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do
= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do
.form-group
= label_tag :username, 'Username or email'
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
......
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group
= label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
......
......@@ -7,7 +7,7 @@
.login-box
.login-body
- if @user.two_factor_otp_enabled?
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f|
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f|
- resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
......
#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f|
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= devise_error_messages!
%div.form-group
......@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
%div.username.form-group
= f.label :username
= f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
= f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
......
= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box
.login-body
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group.append-bottom-20
......
......@@ -2,13 +2,14 @@
.panel-heading
Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
= form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f|
= form_errors(@group)
= render 'shared/group_form', f: f
.form-group
.col-sm-offset-2.col-sm-10
= image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
.image-container.s160
= image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
%p.light
- if @group.avatar?
You can change your group avatar here
......
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@group.name} issues"
xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
......
- page_title "Issues"
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
= auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues")
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
= link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
......
......@@ -13,7 +13,7 @@
.other-labels
- if @labels.present?
%ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', collection: @labels, as: :label
= render partial: 'shared/label', subject: @group, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- else
.nothing-here-block
......
......@@ -5,7 +5,7 @@
New Group
%hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
= form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f|
= form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true
......
......@@ -6,7 +6,8 @@
.cover-block.groups-cover-block
%div{ class: container_class }
= image_tag group_icon(@group), class: "avatar group-avatar s70 avatar-tile"
.image-container.s70.group-avatar
= image_tag group_icon(@group), class: "avatar s70 avatar-tile"
.group-info
.cover-title
%h1
......
......@@ -4,6 +4,6 @@
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%li{class: "page"}
%span.page.gap
%li
%span.gap
= raw(t 'views.pagination.truncate')
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment