Commit 934713b6 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee' into 'master'

CE to EE

Fixes gitlab/gitlabhq#2496

cc @valery 

See merge request !442
parents 5c3bdaf1 b80cfd30
Please view this file on the master branch, on stable branches it's out of date.
v 7.13.0 (unreleased)
- Only enable HSTS header for HTTPS and port 443 (Stan Hu)
- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
- Add branch switching support for graphs (Daniel Gerhardt)
- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
- Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
- Add support for unlocking users in admin settings (Stan Hu)
- Fix order of issues imported form GitHub (Hiroyuki Sato)
- Add Irker service configuration options (Stan Hu)
- Fix order of issues imported from GitHub (Hiroyuki Sato)
- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
- Add `two_factor_enabled` field to admin user API (Stan Hu)
- Fix invalid timestamps in RSS feeds (Rowan Wookey)
- Fix error when deleting a user who has projects (Stan Hu)
- Fix downloading of patches on public merge requests when user logged out (Stan Hu)
- The password for the default administrator (root) account has been changed from "5iveL!fe" to "password".
- Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
......@@ -17,6 +21,7 @@ v 7.13.0 (unreleased)
- Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
- Support commenting on diffs in side-by-side mode (Stan Hu)
- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
- Return 40x error codes if branch could not be deleted in UI (Stan Hu)
- Remove project visibility icons from dashboard projects list
- Rename "Design" profile settings page to "Preferences".
- Allow users to customize their default Dashboard page.
......@@ -34,6 +39,20 @@ v 7.13.0 (unreleased)
- Faster automerge check and merge itself when source and target branches are in same repository
- Correctly show anonymous authorized applications under Profile > Applications.
- Query Optimization in MySQL.
- Allow users to be blocked and unblocked via the API
- Use native Postgres database cleaning during backup restore
- Redesign project page. Show README as default instead of activity. Move project activity to separate page
- Make left menu more hierarchical and less contextual by adding back item at top
- A fork can’t have a visibility level that is greater than the original project.
- Faster code search in repository and wiki. Fixes search page timeout for big repositories
- Allow administrators to disable 2FA for a specific user
- Add error message for SSH key linebreaks
v 7.12.2
- Correctly show anonymous authorized applications under Profile > Applications.
- Faster automerge check and merge itself when source and target branches are in same repository
- Audit log for user authentication
- Fix transferring of project to another group using the API.
v 7.12.1
- Fix error when deleting a user who has projects (Stan Hu)
......@@ -43,6 +62,8 @@ v 7.12.1
- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
- Revert merge request states renaming
- Fix hooks for web based events with external issue references (Daniel Gerhardt)
- Improve performance for issue and merge request pages
- Compress database dumps to reduce backup size
v 7.12.0
- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
......
......@@ -67,7 +67,7 @@ To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gi
If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
1. Fork the project on GitLab Cloud
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
......
......@@ -204,7 +204,7 @@ gem 'jquery-ui-rails'
gem 'nprogress-rails'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store'
gem 'select2-rails'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus'
gem "gitlab-license", "~> 0.0.2"
......@@ -234,26 +234,20 @@ group :development, :test do
gem 'fuubar', '~> 2.0.0'
gem 'pry-rails'
gem 'coveralls', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails'
gem 'rspec-rails', '~> 3.3.0'
gem 'rubocop', '0.28.0', require: false
gem 'rspec-rails', '~> 3.3.0'
gem 'rubocop', '0.28.0', require: false
gem 'spinach-rails'
# rest-client is a coveralls dependency and not used directly in GitLab, but
# we specify a version here to pick up some security fixes.
# See https://github.com/rest-client/rest-client/issues/369
# and http://www.osvdb.org/show/osvdb/117461
gem 'rest-client', '~> 1.8.0'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.3.0'
# Generate Fake data
gem 'ffaker', '~> 2.0.0'
gem 'capybara', '~> 2.3.0'
gem 'capybara', '~> 2.4.0'
gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.6.0'
......
......@@ -82,7 +82,7 @@ GEM
columnize (~> 0.8)
debugger-linecache (~> 1.2)
cal-heatmap-rails (0.0.1)
capybara (2.3.0)
capybara (2.4.4)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
......@@ -599,7 +599,7 @@ GEM
seed-fu (2.3.5)
activerecord (>= 3.1, < 4.3)
activesupport (>= 3.1, < 4.3)
select2-rails (3.5.2)
select2-rails (3.5.9.3)
thor (~> 0.14)
settingslogic (2.0.9)
sexp_processor (4.4.5)
......@@ -704,7 +704,7 @@ GEM
underscore-rails (1.4.4)
unf (0.1.4)
unf_ext
unf_ext (0.0.6)
unf_ext (0.0.7.1)
unicorn (4.6.3)
kgio (~> 2.6)
rack
......@@ -754,13 +754,13 @@ DEPENDENCIES
browser (~> 0.8.0)
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.3.0)
capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0)
carrierwave
charlock_holmes
coffee-rails
colored
coveralls
coveralls (~> 0.8.2)
creole (~> 0.3.6)
d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0)
......@@ -836,7 +836,6 @@ DEPENDENCIES
redis-rails
request_store
rerun (~> 0.10.0)
rest-client (~> 1.8.0)
rqrcode-rails3
rspec-rails (~> 3.3.0)
rubocop (= 0.28.0)
......@@ -845,7 +844,7 @@ DEPENDENCIES
sass-rails (~> 4.0.5)
sdoc
seed-fu
select2-rails
select2-rails (~> 3.5.9)
settingslogic
shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3)
......@@ -881,4 +880,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.10.4
1.10.5
......@@ -49,7 +49,7 @@ To use EE and get official support please [become a subscriber](https://about.gi
## Code status
- [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
- [![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) on ci.gitlab.com (master branch)
- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
......@@ -98,7 +98,7 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m
## Upgrading
For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For installations from source there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update) detailing all necessary commands to migrate to the next version.
For upgrading information please see our [update page](https://about.gitlab.com/update/).
## Install a development environment
......@@ -125,4 +125,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
[These people](https://twitter.com/gitlab/favorites) seem to like it.
[These people](https://twitter.com/gitlab/favorites) seem to like it.
\ No newline at end of file
......@@ -16,7 +16,6 @@
#= require jquery.scrollTo
#= require jquery.blockUI
#= require jquery.turbolinks
#= require jquery.sticky-kit.min
#= require turbolinks
#= require autosave
#= require bootstrap
......@@ -41,6 +40,7 @@
#= require shortcuts_issuable
#= require shortcuts_network
#= require cal-heatmap
#= require jquery.nicescroll.min
#= require_tree .
window.slugify = (text) ->
......@@ -107,6 +107,8 @@ window.addEventListener "hashchange", shiftWindow
$.timeago.settings.allowFuture = true
$ ->
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
# Click a .js-select-on-focus field, select the contents
$(".js-select-on-focus").on "focusin", ->
# Prevent a mouseup event from deselecting the input
......
......@@ -62,8 +62,9 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
when 'projects:activity'
shortcut_handler = new ShortcutsNavigation()
when 'projects:show'
new Activities()
shortcut_handler = new ShortcutsNavigation()
when 'groups:show'
new Activities()
......
......@@ -25,10 +25,10 @@ class @DropzoneInput
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
form_dropzone.append divHover
$(".div-dropzone-hover").append iconPaperclip
form_dropzone.find(".div-dropzone-hover").append iconPaperclip
form_dropzone.append divSpinner
$(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css
form_dropzone.find(".div-dropzone-spinner").append iconSpinner
form_dropzone.find(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
......
......@@ -69,7 +69,6 @@ class @MergeRequestTabs
@loadCommits($target.attr('href'))
else if action == 'diffs'
@loadDiff($target.attr('href'))
@stickyDiffHeaders()
@setCurrentAction(action)
......@@ -137,15 +136,11 @@ class @MergeRequestTabs
url: "#{source}.json"
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@stickyDiffHeaders()
@diffsLoaded = true
toggleLoading: ->
$('.mr-loading-status .loading').toggle()
stickyDiffHeaders: ->
$('.diff-header').trigger('sticky_kit:recalc')
_get: (options) ->
defaults = {
beforeSend: @toggleLoading
......
......@@ -12,7 +12,7 @@
@loading.show()
$.ajax
type: "GET"
url: location.href
url: $(".content_list").data('href') || location.href
data: "limit=" + @limit + "&offset=" + @offset
complete: =>
@loading.hide()
......
class @ProjectShow
constructor: ->
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href"), { expires: 30, path: '/' }
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
# I kept class for future
......@@ -4,6 +4,7 @@ class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project'))
Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
......
......@@ -109,7 +109,7 @@
font-size: 1.2em;
}
blockquote p {
blockquote {
color: #888;
font-size: 15px;
line-height: 1.5;
......
......@@ -44,20 +44,18 @@
.project-home-panel {
padding-left: 0 !important;
.project-home-row {
.project-home-desc {
margin-right: 0 !important;
float: none !important;
}
.project-repo-buttons {
position: static;
margin-top: 15px;
width: 100%;
float: none;
text-align: left;
}
.project-avatar {
display: block;
}
.project-repo-buttons,
.git-clone-holder {
display: none;
}
}
.project-stats {
display: none;
}
.container .title {
......
......@@ -2,6 +2,9 @@
.sidebar-wrapper {
position: fixed;
top: 0;
bottom: 0;
overflow-y: auto;
overflow-x: hidden;
left: 0;
height: 100%;
transition-duration: .3s;
......@@ -21,8 +24,9 @@
}
.nav-sidebar {
margin-top: 29 + $header-height;
margin-bottom: 50px;
transition-duration: .3s;
margin: 0;
list-style: none;
overflow: hidden;
......@@ -39,12 +43,12 @@
}
a {
padding: 8px 15px;
font-size: 13px;
line-height: 18px;
color: $gray;
display: block;
text-decoration: none;
padding: 8px 15px;
font-size: 14px;
line-height: 20px;
padding-left: 16px;
&:hover {
......@@ -88,14 +92,17 @@
width: $sidebar_width;
.nav-sidebar {
margin-top: 29px;
position: fixed;
top: $header-height;
width: $sidebar_width;
}
.nav-sidebar li a{
width: 230px;
&.back-link {
i {
visibility: hidden;
}
}
}
}
}
......@@ -108,15 +115,9 @@
width: $sidebar_collapsed_width;
.nav-sidebar {
margin-top: 29px;
position: fixed;
top: $header-height;
width: $sidebar_collapsed_width;
li a {
font-size: 14px;
padding: 8px 15px;
text-align: left;
padding-left: 16px;
}
}
......@@ -175,7 +176,7 @@
}
.sidebar-user {
position: absolute;
position: fixed;
bottom: 0;
width: $sidebar_width;
padding: 10px;
......
......@@ -17,6 +17,14 @@ pre {
background: #333;
color: $background-color;
}
&.plain-readme {
background: none;
border: none;
padding: 0;
margin: 0;
font-size: 14px;
}
}
.monospace {
......
......@@ -72,13 +72,28 @@ ul.notes {
.note {
display: block;
position:relative;
.note-body {
overflow: auto;
.note-text {
overflow: auto;
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
& > ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
// Reduce left padding of first task list ul element
ul.task-list:first-child {
padding-left: 10px;
......@@ -94,6 +109,7 @@ ul.notes {
}
}
}
.note-header {
padding-bottom: 3px;
}
......
......@@ -15,48 +15,31 @@
}
.project-home-panel {
margin-top: 10px;
margin-bottom: 15px;
position: relative;
padding-left: 65px;
min-height: 50px;
text-align: center;
margin-bottom: 20px;
.project-identicon-holder {
position: absolute;
left: 0;
top: -14px;
margin-bottom: 15px;
.avatar {
width: 50px;
height: 50px;
.avatar, .identicon {
margin: 0 auto;
float: none;
}
.identicon {
font-size: 26px;
line-height: 50px;
@include border-radius(50%);
}
}
.project-home-row {
@extend .clearfix;
margin-bottom: 15px;
&.project-home-row-top {
margin-bottom: 15px;
.lead {
p {
display: inline;
}
}
.project-home-desc {
color: $gray;
float: left;
font-size: 16px;
line-height: 1.3;
margin-right: 250px;
// Render Markdown-generated HTML inline for this block
p {
display: inline;
}
}
.git-clone-holder {
max-width: 600px;
margin: 0 auto;
}
.visibility-level-label {
......@@ -67,22 +50,22 @@
}
.project-repo-buttons {
margin-top: -3px;
position: absolute;
right: 0;
width: 265px;
text-align: right;
margin-top: 25px;
margin-bottom: 25px;
.btn {
@extend .btn-info;
margin-left: 10px;
font-weight: bold;
font-size: 14px;
line-height: 16px;
padding: 8px 12px;
.count {
padding-left: 10px;
border-left: 1px solid #ccc;
padding-left: 7px;
display: inline-block;
margin-left: 10px;
margin-left: 7px;
}
}
}
......@@ -307,3 +290,15 @@ table.table.protected-branches-list tr.no-border {
float: left;
margin-right: 10px;
}
.project-stats {
text-align: center;
ul.nav-pills { display:inline-block; }
li { display:inline; }
a { float:left; }
}
pre.light-well {
border-color: #f1f1f1;
}
......@@ -35,9 +35,9 @@
.sidebar-wrapper {
background: $color-darker;
border-right: 1px solid $color-darker;
.sidebar-user {
background: $color-darker;
color: $color-light;
&:hover {
......
......@@ -23,7 +23,8 @@ class Admin::ProjectsController < Admin::ApplicationController
end
def transfer
::Projects::TransferService.new(@project, current_user, params.dup).execute
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@project.reload
redirect_to admin_namespace_project_path(@project.namespace, @project)
......
......@@ -55,6 +55,12 @@ class Admin::UsersController < Admin::ApplicationController
end
end
def disable_two_factor
user.disable_two_factor!
redirect_to admin_user_path(user),
notice: 'Two-factor Authentication has been disabled for this user'
end
def create
opts = {
force_random_password: true,
......
......@@ -56,7 +56,7 @@ class ApplicationController < ActionController::Base
def authenticate_user!(*args)
# If user is not signed-in and tries to access root_path - redirect him to landing page
if current_application_settings.home_page_url.present?
if current_user.nil? && controller_name == 'dashboard' && action_name == 'show'
if current_user.nil? && root_path == request.path
redirect_to current_application_settings.home_page_url and return
end
end
......@@ -183,7 +183,10 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
headers['Strict-Transport-Security'] = 'max-age=31536000' if Gitlab.config.gitlab.https
# Enabling HSTS for non-standard ports would send clients to the wrong port
if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443
headers['Strict-Transport-Security'] = 'max-age=31536000'
end
end
def add_gon_variables
......@@ -271,6 +274,7 @@ class ApplicationController < ActionController::Base
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@sort = params[:sort]
@filter_params = params.dup
if @project
......
class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users]
def users
@users =
if params[:project_id].present?
project = Project.find(params[:project_id])
begin
@users =
if params[:project_id].present?
project = Project.find(params[:project_id])
if can?(current_user, :read_project, project)
project.team.users
end
elsif params[:group_id]
group = Group.find(params[:group_id])
if can?(current_user, :read_project, project)
project.team.users
end
elsif params[:group_id]
group = Group.find(params[:group_id])
if can?(current_user, :read_group, group)
group.users
if can?(current_user, :read_group, group)
group.users
end
elsif current_user
User.all
end
else
User.all
rescue ActiveRecord::RecordNotFound
if current_user
return render json: {}, status: 404
end
end
if @users.nil? && current_user.nil?
authenticate_user!
end
@users ||= User.none
@users = @users.non_ldap if params[:skip_ldap] == 'true'
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
......
......@@ -32,6 +32,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.otp_required_for_login?
prompt_for_two_factor(gl_user)
else
log_audit_event(gl_user, with: :ldap)
sign_in_and_redirect(gl_user)
end
else
......@@ -52,6 +53,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if current_user
# Add new authentication method
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
@user = Gitlab::OAuth::User.new(oauth)
......@@ -59,6 +61,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
log_audit_event(@user.gl_user, with: oauth['provider'])
sign_in_and_redirect(@user.gl_user)
else
error_message =
......@@ -88,4 +91,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth
@oauth ||= request.env['omniauth.auth']
end
def log_audit_event(user, options = {})
AuditEventService.new(user, user, options).
for_authentication.security_event
end
end
......@@ -32,6 +32,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
params.require(:user).permit(
:color_scheme_id,
:dashboard,
:project_view,
:theme_id
)
end
......
......@@ -29,13 +29,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def destroy
current_user.update_attributes({
two_factor_enabled: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
otp_backup_codes: nil
})
current_user.disable_two_factor!
redirect_to profile_account_path
end
......
......@@ -38,8 +38,11 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_account_path
end
def history
@events = current_user.recent_events.page(params[:page]).per(PER_PAGE)
def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC").
page(params[:page]).
per(PER_PAGE)
end
def update_username
......
......@@ -32,7 +32,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def destroy
DeleteBranchService.new(project, current_user).execute(params[:id])
status = DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id]
respond_to do |format|
......@@ -40,7 +40,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace,
@project)
end
format.js
format.js { render status: status[:return_code] }
end
end
end
class Projects::GraphsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
def show
......@@ -13,7 +16,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
@commits = @project.repository.commits(nil, nil, 2000, 0, true)
@commits = @project.repository.commits(@ref, nil, 2000, 0, true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
......@@ -23,7 +26,7 @@ class Projects::GraphsController < Projects::ApplicationController
private
def fetch_graph
@commits = @project.repository.commits(nil, nil, 6000, 0, true)
@commits = @project.repository.commits(@ref, nil, 6000, 0, true)
@log = []
@commits.each do |commit|
......
......@@ -8,17 +8,21 @@ class Projects::RefsController < Projects::ApplicationController
def switch
respond_to do |format|
format.html do
new_path = if params[:destination] == "tree"
namespace_project_tree_path(@project.namespace, @project,
(@id))
elsif params[:destination] == "blob"
namespace_project_blob_path(@project.namespace, @project,
(@id))
elsif params[:destination] == "graph"
namespace_project_network_path(@project.namespace, @project, @id, @options)
else
namespace_project_commits_path(@project.namespace, @project, @id)
end
new_path =
case params[:destination]
when "tree"
namespace_project_tree_path(@project.namespace, @project, @id)
when "blob"
namespace_project_blob_path(@project.namespace, @project, @id)
when "graph"
namespace_project_network_path(@project.namespace, @project, @id, @options)
when "graphs"
namespace_project_graph_path(@project.namespace, @project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
else
namespace_project_commits_path(@project.namespace, @project, @id)
end
redirect_to new_path
end
......
......@@ -7,7 +7,9 @@ class Projects::ServicesController < Projects::ApplicationController
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:jira_issue_transition_id, :notify, :color]
:jira_issue_transition_id,
:notify, :color,
:server_host, :server_port, :default_irc_uri]
# Authorize
before_action :authorize_admin_project!
......
......@@ -6,7 +6,7 @@ class ProjectsController < ApplicationController
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
before_action :event_filter, only: :show
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
......@@ -52,10 +52,21 @@ class ProjectsController < ApplicationController
end
def transfer
transfer_params = params.permit(:new_namespace_id)
::Projects::TransferService.new(project, current_user, transfer_params).execute
if @project.errors[:namespace_id].present?
flash[:alert] = @project.errors[:namespace_id].first
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user).execute(namespace)
if @project.errors[:new_namespace].present?
flash[:alert] = @project.errors[:new_namespace].first
end
end
def activity
respond_to do |format|
format.html
format.json do
load_events
pager_json('events/_events', @events.count)
end
end
end
......@@ -65,15 +76,12 @@ class ProjectsController < ApplicationController
return
end
@show_star = !(current_user && current_user.starred?(@project))
respond_to do |format|
format.html do
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
@last_push = current_user.recent_push(@project.id) if current_user
render :show
end
else
......@@ -81,11 +89,6 @@ class ProjectsController < ApplicationController
end
end
format.json do
load_events
pager_json('events/_events', @events.count)
end
format.atom do
load_events
render layout: false
......@@ -147,7 +150,10 @@ class ProjectsController < ApplicationController
def toggle_star
current_user.toggle_star(@project)
@project.reload
render json: { star_count: @project.star_count }
render json: {
html: view_to_html_string("projects/buttons/_star")
}
end
def markdown_preview
......
......@@ -37,6 +37,8 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
authenticated_with = user_params[:otp_attempt] ? "two-factor" : "standard"
log_audit_event(current_user, with: authenticated_with)
end
end
......@@ -95,4 +97,9 @@ class SessionsController < Devise::SessionsController
user.valid_otp?(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
def log_audit_event(user, options = {})
AuditEventService.new(user, user, options).
for_authentication.security_event
end
end
......@@ -10,7 +10,7 @@
# state: 'open' or 'closed' or 'all'
# group_id: integer
# project_id: integer
# milestone_id: integer
# milestone_title: string
# assignee_id: integer
# search: string
# label_name: string
......@@ -76,7 +76,7 @@ class IssuableFinder
return @milestones if defined?(@milestones)
@milestones =
if milestones? && params[:milestone_title] != NONE
if milestones? && params[:milestone_title] != Milestone::None.title
Milestone.where(title: params[:milestone_title])
else
nil
......
......@@ -213,6 +213,10 @@ module ApplicationHelper
Haml::Helpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
elsif plain?(file_name)
content_tag :pre, class: 'plain-readme' do
file_content
end
else
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
......@@ -221,6 +225,10 @@ module ApplicationHelper
simple_format(file_content)
end
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
end
def markup?(filename)
Gitlab::MarkupHelper.markup?(filename)
end
......
......@@ -32,7 +32,7 @@ module ApplicationSettingsHelper
def restricted_level_checkboxes(help_block_id)
Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level)
css_class = 'btn btn-primary'
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[restricted_visibility_levels][]'
......
......@@ -118,7 +118,7 @@ module GitlabMarkdownHelper
# Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip
"Tip: #{MARKDOWN_TIPS.sample}"
MARKDOWN_TIPS.sample
end
private
......
......@@ -17,6 +17,10 @@ module GitlabRoutingHelper
namespace_project_path(project.namespace, project, *args)
end
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args)
end
......
......@@ -29,6 +29,8 @@ module MilestonesHelper
end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute
grouped_milestones.unshift(Milestone::None)
options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title])
end
end
......@@ -42,6 +42,13 @@ module PreferencesHelper
end
end
def project_view_choices
[
['Readme (default)', :readme],
['Activity view', :activity]
]
end
def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class
......@@ -50,4 +57,9 @@ module PreferencesHelper
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
def prefer_readme?
!current_user ||
current_user.project_view == 'readme'
end
end
......@@ -84,53 +84,6 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC")
end
def link_to_toggle_star(title, starred)
cls = 'star-btn btn btn-sm btn-default'
toggle_text =
if starred
' Unstar'
else
' Star'
end
toggle_html = content_tag('span', class: 'toggle') do
icon('star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
@project.star_count.to_s
end
link_opts = {
title: title,
class: cls,
method: :post,
remote: true,
data: { type: 'json' }
}
path = toggle_star_namespace_project_path(@project.namespace, @project)
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to(path, link_opts) do
toggle_html + ' ' + count_html
end
end
end
def link_to_toggle_fork
html = content_tag('span') do
icon('code-fork') + ' Fork'
end
count_html = content_tag(:span, class: 'count') do
@project.forks_count.to_s
end
html + count_html
end
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
......@@ -139,6 +92,16 @@ module ProjectsHelper
end
end
def can_change_visibility_level?(project, current_user)
return false unless can?(current_user, :change_visibility_level, project)
if project.forked?
project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
else
true
end
end
private
def get_project_nav_tabs(project, current_user)
......@@ -285,14 +248,6 @@ module ProjectsHelper
end
end
def github_import_enabled?
enabled_oauth_providers.include?(:github)
end
def gitlab_import_enabled?
enabled_oauth_providers.include?(:gitlab)
end
def membership_locked?
if @project.group && @project.group.membership_lock
true
......@@ -301,16 +256,6 @@ module ProjectsHelper
end
end
def service_field_value(type, value)
return value unless type == 'password'
if value.present?
"***********"
else
nil
end
end
def user_max_access_in_project(user, project)
level = project.team.max_member_access(user)
......@@ -322,4 +267,21 @@ module ProjectsHelper
def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?"
end
def new_readme_path
ref = @repository.root_ref if @repository
ref ||= 'master'
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
end
def last_push_event
if current_user
current_user.recent_push(@project.id)
end
end
def readme_cache_key
[@project.id, @project.commit.sha, "readme"].join('-')
end
end
......@@ -86,4 +86,10 @@ module VisibilityLevelHelper
def default_snippet_visibility
current_application_settings.default_snippet_visibility
end
def skip_level?(form_model, level)
form_model.is_a?(Project) &&
form_model.forked? &&
!Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
end
end
......@@ -79,22 +79,36 @@ module Mentionable
end
end
# If the mentionable_text field is about to change, locate any *added* references and create cross references for
# them. Invoke from an observer's #before_save implementation.
def notice_added_references(p = project, a = author)
ch = changed_attributes
original, mentionable_changed = "", false
self.class.mentionable_attrs.each do |attr|
if ch[attr]
original << ch[attr]
mentionable_changed = true
end
end
# When a mentionable field is changed, creates cross-reference notes that
# don't already exist
def create_new_cross_references!(p = project, a = author)
changes = detect_mentionable_changes
return if changes.empty?
# Only proceed if the saved changes actually include a chance to an attr_mentionable field.
return unless mentionable_changed
original_text = changes.collect { |_, vals| vals.first }.join(' ')
preexisting = references(p, self.author, original)
preexisting = references(p, self.author, original_text)
create_cross_references!(p, a, preexisting)
end
private
# Returns a Hash of changed mentionable fields
#
# Preference is given to the `changes` Hash, but falls back to
# `previous_changes` if it's empty (i.e., the changes have already been
# persisted).
#
# See ActiveModel::Dirty.
#
# Returns a Hash.
def detect_mentionable_changes
source = (changes.present? ? changes : previous_changes).dup
mentionable = self.class.mentionable_attrs
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
end
......@@ -24,6 +24,7 @@ class Key < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
scope :ldap, -> { where(type: 'LDAPKey') }
......
......@@ -14,6 +14,10 @@
#
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
None = Struct.new(:title).new('No Milestone')
include InternalId
include Sortable
......
......@@ -356,7 +356,7 @@ class Note < ActiveRecord::Base
end
def set_references
notice_added_references(project, author)
create_new_cross_references!(project, author)
end
def editable?
......
......@@ -21,26 +21,11 @@
require 'uri'
class IrkerService < Service
prop_accessor :server_host, :server_port, :default_irc_uri
prop_accessor :colorize_messages, :recipients, :channels
validates :recipients, presence: true, if: :activated?
validate :check_recipients_count, if: :activated?
before_validation :get_channels
after_initialize :initialize_settings
# Writer for RSpec tests
attr_writer :settings
def initialize_settings
# See the documentation (doc/project_services/irker.md) for possible values
# here
@settings ||= {
server_ip: 'localhost',
server_port: 6659,
max_channels: 3,
default_irc_uri: nil
}
end
def title
'Irker (IRC gateway)'
......@@ -51,20 +36,6 @@ class IrkerService < Service
'gateway.'
end
def help
msg = 'Recipients have to be specified with a full URI: '\
'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\
'the channel to be a nickname instead, append ",isnick" to the channel '\
'name; if the channel is protected by a secret password, append '\
'"?key=secretpassword" to the URI.'
unless @settings[:default_irc].nil?
msg += ' Note that a default IRC URI is provided by this service\'s '\
"administrator: #{default_irc}. You can thus just give a channel name."
end
msg
end
def to_param
'irker'
end
......@@ -77,30 +48,45 @@ class IrkerService < Service
return unless supported_events.include?(data[:object_kind])
IrkerWorker.perform_async(project_id, channels,
colorize_messages, data, @settings)
colorize_messages, data, settings)
end
def settings
{ server_host: server_host.present? ? server_host : 'localhost',
server_port: server_port.present? ? server_port : 6659
}
end
def fields
[
{ type: 'text', name: 'server_host', placeholder: 'localhost',
help: 'Irker daemon hostname (defaults to localhost)' },
{ type: 'text', name: 'server_port', placeholder: 6659,
help: 'Irker daemon port (defaults to 6659)' },
{ type: 'text', name: 'default_irc_uri', title: 'Default IRC URI',
help: 'A default IRC URI to prepend before each recipient (optional)',
placeholder: 'irc://irc.network.net:6697/' },
{ type: 'textarea', name: 'recipients',
placeholder: 'Recipients/channels separated by whitespaces' },
placeholder: 'Recipients/channels separated by whitespaces',
help: 'Recipients have to be specified with a full URI: '\
'irc[s]://irc.network.net[:port]/#channel. Special cases: if '\
'you want the channel to be a nickname instead, append ",isnick" to ' \
'the channel name; if the channel is protected by a secret password, ' \
' append "?key=secretpassword" to the URI. Note that if you specify a ' \
' default IRC URI to prepend before each recipient, you can just give ' \
' a channel name.' },
{ type: 'checkbox', name: 'colorize_messages' },
]
end
private
def check_recipients_count
return true if recipients.nil? || recipients.empty?
if recipients.split(/\s+/).count > max_chans
errors.add(:recipients, "are limited to #{max_chans}")
end
def help
' NOTE: Irker does NOT have built-in authentication, which makes it' \
' vulnerable to spamming IRC channels if it is hosted outside of a ' \
' firewall. Please make sure you run the daemon within a secured network ' \
' to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.'
end
def max_chans
@settings[:max_channels]
end
private
def get_channels
return true unless :activated?
......@@ -114,40 +100,35 @@ class IrkerService < Service
def map_recipients
self.channels = recipients.split(/\s+/).map do |recipient|
format_channel default_irc_uri, recipient
format_channel(recipient)
end
channels.reject! &:nil?
end
def default_irc_uri
default_irc = @settings[:default_irc_uri]
if !(default_irc.nil? || default_irc[-1] == '/')
default_irc += '/'
end
default_irc
end
def format_channel(default_irc, recipient)
cnt = 0
url = nil
def format_channel(recipient)
uri = nil
# Try to parse the chan as a full URI
begin
uri = URI.parse(recipient)
raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0
uri = consider_uri(URI.parse(recipient))
rescue URI::InvalidURIError
unless default_irc.nil?
cnt += 1
recipient = "#{default_irc}#{recipient}"
retry if cnt == 1
end
unless uri.present? and default_irc_uri.nil?
begin
new_recipient = URI.join(default_irc_uri, '/', recipient).to_s
uri = consider_uri(URI.parse(new_recipient))
rescue
Rails.logger.error("Unable to create a valid URL from #{default_irc_uri} and #{recipient}")
end
else
url = consider_uri uri
end
url
uri
end
def consider_uri(uri)
return nil if uri.scheme.nil?
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
# Do not authorize irc://domain.com/
......
......@@ -431,6 +431,40 @@ class Repository
end
end
def search_files(query, ref)
offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
def parse_search_result(result)
ref = nil
filename = nil
startline = 0
lines = result.lines
lines.each_with_index do |line, index|
if line =~ /^.*:.*:\d+:/
ref, filename, startline = line.split(':')
startline = startline.to_i - index
break
end
end
data = lines.map do |line|
line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
end
data = data.join("")
OpenStruct.new(
filename: filename,
ref: ref,
startline: startline,
data: data
)
end
private
def cache
......
......@@ -178,6 +178,10 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars]
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity]
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
......@@ -350,6 +354,16 @@ class User < ActiveRecord::Base
@reset_token
end
def disable_two_factor!
update_attributes(
two_factor_enabled: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
otp_backup_codes: nil
)
end
def namespace_uniq
namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name)
......
......@@ -64,6 +64,17 @@ class AuditEventService
self
end
def for_authentication
@details = {
with: @details[:with],
target_id: @author.id,
target_type: "User",
target_details: @author.name,
}
self
end
def security_event
SecurityEvent.create(
author_id: @author.id,
......
module Issues
class UpdateService < Issues::BaseService
def execute(issue)
state = params[:state_event]
case state
case params.delete(:state_event)
when 'reopen'
Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue)
when 'task_check'
issue.update_nth_task(params[:task_num].to_i, true)
when 'task_uncheck'
issue.update_nth_task(params[:task_num].to_i, false)
end
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
......@@ -20,8 +14,7 @@ module Issues
filter_params
old_labels = issue.labels.to_a
if params.present? && issue.update_attributes(params.except(:state_event,
:task_num))
if params.present? && issue.update_attributes(params)
issue.reset_events_cache
if issue.labels != old_labels
......@@ -42,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first)
end
issue.notice_added_references(issue.project, current_user)
issue.create_new_cross_references!(issue.project, current_user)
execute_hooks(issue, 'update')
end
......
......@@ -11,17 +11,11 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
state = params[:state_event]
case state
case params.delete(:state_event)
when 'reopen'
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
when 'task_check'
merge_request.update_nth_task(params[:task_num].to_i, true)
when 'task_uncheck'
merge_request.update_nth_task(params[:task_num].to_i, false)
end
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
......@@ -30,9 +24,7 @@ module MergeRequests
filter_params
old_labels = merge_request.labels.to_a
if params.present? && merge_request.update_attributes(
params.except(:state_event, :task_num)
)
if params.present? && merge_request.update_attributes(params)
merge_request.reset_events_cache
if merge_request.labels != old_labels
......@@ -67,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked
end
merge_request.notice_added_references(merge_request.project, current_user)
merge_request.create_new_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request, 'update')
end
......
......@@ -11,19 +11,16 @@ module Projects
include Gitlab::ShellAdapter
class TransferError < StandardError; end
def execute
namespace_id = params[:new_namespace_id]
namespace = Namespace.find_by(id: namespace_id)
if allowed_transfer?(current_user, project, namespace)
transfer(project, namespace)
def execute(new_namespace)
if allowed_transfer?(current_user, project, new_namespace)
transfer(project, new_namespace)
else
project.errors.add(:namespace, 'is invalid')
project.errors.add(:new_namespace, 'is invalid')
false
end
rescue Projects::TransferService::TransferError => ex
project.reload
project.errors.add(:namespace_id, ex.message)
project.errors.add(:new_namespace, ex.message)
false
end
......
......@@ -43,6 +43,7 @@
%strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'}
- if @user.two_factor_enabled?
Enabled
= link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-xs btn-remove pull-right', title: 'Disable Two-factor Authentication'
- else
Disabled
......
......@@ -5,6 +5,7 @@
- if event.created_project?
= cache [event, current_user] do
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
= render "events/event/created_project", event: event
- else
= cache event do
......
......@@ -79,6 +79,12 @@
%td.shortcut
.key g
.key p
%td
Go to the project's home page
%tr
%td.shortcut
.key g
.key e
%td
Go to the project's activity feed
%tr
......
......@@ -7,14 +7,29 @@
%title= page_title
= favicon_link_tag 'favicon.ico'
= stylesheet_link_tag "application", :media => "all"
= stylesheet_link_tag "print", :media => "print"
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
= javascript_include_tag "application"
= csrf_meta_tags
= include_gon
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
-# Apple Safari/iOS home screen icons
= favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon'
= favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76'
= favicon_link_tag 'touch-icon-iphone-retina.png', rel: 'apple-touch-icon', sizes: '120x120'
= favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152'
-# Windows 8 pinned site tile
%meta{name: 'msapplication-TileImage', content: image_url('msapplication-tile.png')}
%meta{name: 'msapplication-TileColor', content: '#30353E'}
= yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
......
.page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper
.sidebar-wrapper.nicescroll
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
......
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to Dashboard
%li.separate-item
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Activity
Group
- if current_user
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
......
%ul.nav.nav-sidebar
= nav_link do
= link_to group_path(@group), title: 'Back to group', data: {placement: 'right'} do
= link_to group_path(@group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to group
......
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to Dashboard
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw')
......@@ -44,8 +52,8 @@
= icon('image fw')
%span
Preferences
= nav_link(path: 'profiles#history') do
= link_to history_profile_path, title: 'History', data: {placement: 'right'} do
= nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
= icon('history fw')
%span
History
Audit Log
%ul.nav.nav-sidebar
- if @project.group
= nav_link do
= link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to Group
- else
= nav_link do
= link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to Dashboard
%li.separate-item
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
= icon('dashboard fw')
= icon('home fw')
%span
Project
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
......
%ul.nav.nav-sidebar
= nav_link do
= link_to project_path(@project), title: 'Back to project', data: {placement: 'right'} do
= link_to project_path(@project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to project
......
%table.table#audits
%thead
%tr
%th Action
%th When
%tbody
- events.each do |event|
%tr
%td
%span
Signed in with
%b= event.details[:with]
authentication
%td #{time_ago_in_words event.created_at} ago
= paginate events, theme: "gitlab"
......@@ -66,4 +66,4 @@
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
%p.light You dont have any authorized applications
%p.light You don't have any authorized applications
- page_title "Audit Log"
%h3.page-title Audit Log
%p.light History of authentications
= render 'event_table', events: @events
\ No newline at end of file
- page_title "History"
%h3.page-title
Your Account History
%p.light
All events created by your account are listed below.
%hr
.profile_history
= render @events
%hr
= paginate @events, theme: "gitlab"
......@@ -38,5 +38,13 @@
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
.col-sm-10
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
.form-group
= f.label :project_view, class: 'control-label' do
Project view
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
.col-sm-10
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
Choose what content you want to see when visit project page
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
= render 'projects/last_push'
.hidden-xs
- if current_user
%ul.nav.nav-pills.event_filter.pull-right
%li
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter'
%hr
.content_list{:"data-href" => activity_project_path(@project)}
= spinner
:coffeescript
new Activities()
.clearfix
- unless @project.empty_repo?
.panel.panel-default
.panel-heading
= visibility_level_icon(@project.visibility_level)
= "#{visibility_level_label(@project.visibility_level).capitalize} project"
.panel-body
- if @repository.changelog || @repository.license || @repository.contribution_guide
%ul.nav.nav-pills
- if @repository.changelog
%li.hidden-xs
= link_to changelog_url(@project) do
Changelog
- if @repository.license
%li
= link_to license_url(@project) do
License
- if @repository.contribution_guide
%li
= link_to contribution_guide_url(@project) do
Contribution guide
.actions
- if can? current_user, :create_issue, @project
= link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do
New Issue
- if can? current_user, :create_merge_request, @project
= link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do
New Merge Request
- if forked_from_project = @project.forked_from_project
.panel-footer
= icon("code-fork fw")
Forked from
.pull-right
= link_to forked_from_project.namespace.try(:name), project_path(forked_from_project)
- @project.ci_services.each do |ci_service|
- if ci_service.active? && ci_service.respond_to?(:builds_path)
.panel-footer
= icon("check fw")
= ci_service.title
.pull-right
- if ci_service.respond_to?(:status_img_path)
= link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
= image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image'
- else
= link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
- unless @project.empty_repo?
.panel.panel-default
.panel-heading
= icon("folder-o fw")
Repository
.panel-body
%ul.nav.nav-pills
%li
= link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
= pluralize(number_with_delimiter(@repository.commit_count), 'commit')
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
.actions
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' do
%i.fa.fa-exchange
Compare code
- if can?(current_user, :download_code, @project)
= render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm'
- if version = @repository.version
.panel-footer
= icon("clock-o fw")
Version
.pull-right
= link_to version_url(@project) do
= @repository.blob_by_oid(version.id).data
= render "shared/clone_panel"
- if @project.archived?
%br
.alert.alert-warning
%h4
= icon("exclamation-triangle fw")
Archived project!
%p Repository is read-only
- if current_user
- access = user_max_access_in_project(current_user, @project)
- if access
.light-well.light.prepend-top-20
%small
You have #{access} access to this project.
- if @project.project_member_by_id(current_user)
%br
= link_to leave_namespace_project_project_members_path(@project.namespace, @project),
data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
Leave this project
- empty_repo = @project.empty_repo?
.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
= project_icon(@project, alt: '', class: 'avatar project-avatar')
.project-home-row.project-home-row-top
.project-home-desc
- if @project.description.present?
= markdown(@project.description, pipeline: :description)
- if can?(current_user, :admin_project, @project)
&ndash;
= link_to 'Edit', edit_namespace_project_path
- elsif !empty_repo && @repository.readme
- readme = @repository.readme
&ndash;
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
= readme.name
.project-repo-buttons
.inline.star.js-toggler-container{class: @show_star ? 'on' : ''}
- if current_user
= link_to_toggle_star('Star this project.', false)
= link_to_toggle_star('Unstar this project.', true)
- else
= link_to new_user_session_path, class: 'btn star-btn has_tooltip', title: 'You must sign in to star a project' do
%span
= icon('star')
Star
%span.count
= @project.star_count
- unless empty_repo
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
.inline.fork-buttons.prepend-left-10
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-sm btn-default' do
= link_to_toggle_fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do
= link_to_toggle_fork
= project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc.lead
- if @project.description.present?
= markdown(@project.description, pipeline: :description)
.project-repo-buttons
= render 'projects/buttons/star'
- unless empty_repo
= render 'projects/buttons/fork'
- if forked_from_project = @project.forked_from_project
= link_to project_path(forked_from_project), class: 'btn' do
= icon("code-fork fw")
Forked from
= forked_from_project.namespace.try(:name)
- if can? current_user, :download_code, @project
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
= render 'projects/buttons/dropdown'
= render "shared/clone_panel"
- if event = last_push_event
- if show_last_push_widget?(event)
.hidden-xs.center
.slead
%span You pushed to
= link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
%strong= event.ref_name
branch
#{time_ago_with_tooltip(event.created_at)}
%div
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
Create Merge Request
%hr
- if readme = @repository.readme
%article.readme-holder#README
.clearfix
.pull-right
&nbsp;
- if can?(current_user, :push_code, @project)
= link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
%i.fa.fa-pencil
.wiki
= cache(readme_cache_key) do
= render_readme(readme)
- else
%h3.page-title
This project does not have README yet
- if can?(current_user, :push_code, @project)
%p.slead
A
%code README
file contains information about other files in a repository and is commonly
distributed with computer software, forming part of its documentation.
%br
We recommend you to
= link_to "add README", new_readme_path, class: 'underlined-link'
file to the repository and GitLab will render it here instead of this message.
%ul.nav.nav-tabs
%li.active
= link_to '#tab-activity', 'data-toggle' => 'tab' do
= icon("tachometer")
Activity
- if @repository.readme
%li
= link_to '#tab-readme', 'data-toggle' => 'tab' do
= icon("file-text-o")
Readme
.tab-content
.tab-pane.active#tab-activity
.hidden-xs
= render "events/event_last_push", event: @last_push
- if current_user
%ul.nav.nav-pills.event_filter.pull-right
%li
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter'
%hr
.content_list
= spinner
- if readme = @repository.readme
.tab-pane#tab-readme
%article.readme-holder#README
.clearfix
%small.pull-right
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
%i.fa.fa-file
= readme.name
.wiki
= render_readme(readme)
......@@ -2,7 +2,7 @@
%input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: random_markdown_tip
= f.text_area attr, class: classes, placeholder: ''
= link_to nil, class: 'zen-enter-link', tabindex: '-1' do
%i.fa.fa-expand
Edit in fullscreen
......
= render 'projects/activity'
- page_title @blob.path, @ref
= render 'projects/last_push'
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
......
- if current_user
%span.dropdown
%a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-plus
%ul.dropdown-menu
- if @project.issues_enabled && can?(current_user, :create_issue, @project)
%li
= link_to url_for_new_issue, title: "New Issue" do
New issue
- if @project.merge_requests_enabled && can?(current_user, :create_merge_request, @project)
%li
= link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do
New merge request
- if @project.snippets_enabled && can?(current_user, :create_snippet, @project)
%li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
New snippet
- if can?(current_user, :admin_project_member, @project)
%li
= link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
New project member
- if can? current_user, :push_code, @project
%li.divider
%li
= link_to new_namespace_project_branch_path(@project.namespace, @project) do
New git branch
%li
= link_to new_namespace_project_tag_path(@project.namespace, @project) do
New git tag
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn' do
= icon('code-fork')
Fork
%span.count
= @project.forks_count
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
= icon('code-fork')
Fork
%span.count
= @project.forks_count
- if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do
= icon('star')
- if current_user.starred?(@project)
Unstar
- else
Star
%span.count
= @project.star_count
:coffeescript
$('.project-home-panel .toggle-star').on 'ajax:success', (e, data, status, xhr) ->
$(@).replaceWith(data.html)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
- else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star')
Star
%span.count
= @project.star_count
......@@ -20,6 +20,3 @@
Failed to collect changes
%p
Maybe diff is really big and operation failed with timeout. Try to get diff locally
:coffeescript
$('.files .diff-header').stick_in_parent(offset_top: $('.navbar').height())
.alert.alert-warning
%h4
Too many changes.
Too many changes to show.
.pull-right
- unless diff_hard_limit_enabled?
= link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning"
......
......@@ -29,7 +29,7 @@
.col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
= render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project), form_model: @project
= render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
.form-group
= f.label :tag_list, "Tags", class: 'control-label'
......@@ -178,7 +178,7 @@
.form-group
= label_tag :new_namespace_id, nil, class: 'control-label' do
%span Namespace
.col-sm-10
.col-sm-9
.form-group
= select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
%ul
......
......@@ -4,30 +4,30 @@
= render "home_panel"
.center.well
%h3
.center.light-well
%h3.page-title
The repository for this project is empty
%h4
You can
= link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do
add a file
&nbsp;or do a push via the command line.
%p
If you already have files you can push them using command line instructions below.
%br
Otherwise you can start with
= link_to "adding README", new_readme_path, class: 'underlined-link'
file to this project.
.well
= render "shared/clone_panel"
%h4
%strong Command line instructions
.prepend-top-20
%h3.page-title
Command line instructions
%div.git-empty
%fieldset
%legend Git global setup
%pre.dark
%h5 Git global setup
%pre.light-well
:preserve
git config --global user.name "#{git_user_name}"
git config --global user.email "#{git_user_email}"
%fieldset
%legend Create a new repository
%pre.dark
%h5 Create a new repository
%pre.light-well
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
cd #{@project.path}
......@@ -37,8 +37,8 @@
git push -u origin master
%fieldset
%legend Existing folder or Git repository
%pre.dark
%h5 Existing folder or Git repository
%pre.light-well
:preserve
cd existing_folder
git init
......
- page_title "Commit statistics"
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs_commits'
= render 'head'
%p.lead
Commit statistics for
%strong #{@repository.root_ref}
%strong #{@ref}
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
.row
......
- page_title "Contributor statistics"
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs'
= render 'head'
.loading-graph
.center
%h3.page-title
......@@ -11,7 +14,7 @@
.header.clearfix
%h3#date_header.page-title
%p.light
Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits
Commits to #{@ref}, excluding merge commits. Limited by 6,000 commits
%input#brush_change{:type => "hidden"}
.graphs
#contributors-master
......@@ -35,4 +38,3 @@
$(".stat-graph").fadeIn();
$(".loading-graph").hide();
dataType: "json"
......@@ -6,5 +6,5 @@
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn'
= link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
= link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
- page_title "Merge Requests"
= render 'projects/last_push'
.append-bottom-10
.pull-right
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
......
......@@ -85,7 +85,7 @@
%li
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}.
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
%hr.prepend-botton-10
......
......@@ -5,8 +5,8 @@
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
.comment-hints.clearfix
.pull-left Comments are parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
.pull-right Attach files by dragging &amp; dropping or #{link_to 'selecting them', '#', class: 'markdown-selector', tabindex: -1 }.
.pull-left #{link_to 'Markdown ', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
.pull-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 }
.note-form-actions
.buttons
......
......@@ -12,8 +12,14 @@
classes: 'note_text js-note-text'
.comment-hints.clearfix
.pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
.pull-right Attach files by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
.pull-left
= link_to "Markdown ", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }
tip:
= random_markdown_tip
.pull-right
= link_to '#', class: 'markdown-selector', tabindex: -1 do
Attach a file
= icon('paperclip')
.error-alert
.note-form-actions
......
......@@ -6,11 +6,54 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
= render 'projects/last_push'
= render "home_panel"
= render 'shared/show_aside'
.row
%section.col-md-8
= render 'section'
%aside.col-md-4.project-side
= render 'aside'
.project-stats
%ul.nav.nav-pills
%li
= link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
= pluralize(number_with_delimiter(@repository.commit_count), 'commit')
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
- if @repository.changelog
%li
= link_to changelog_url(@project) do
Changelog
- if @repository.license
%li
= link_to license_url(@project) do
License
- if @repository.contribution_guide
%li
= link_to contribution_guide_url(@project) do
Contribution guide
- if @project.archived?
.text-warning.center.prepend-top-20
%p
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
%hr
%section
- if prefer_readme?
= render 'projects/readme'
- else
= render 'projects/activity'
- if current_user
- access = user_max_access_in_project(current_user, @project)
- if access
%hr
%p.light
You have #{access} access to this project.
- if @project.project_member_by_id(current_user)
= link_to leave_namespace_project_project_members_path(@project.namespace, @project),
data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
Leave this project
......@@ -2,7 +2,9 @@
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render 'projects/last_push'
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
......
- blob = @project.repository.parse_search_result(blob)
.blob-result
.file-holder
.file-title
......
- wiki_blob = @project.repository.parse_search_result(wiki_blob)
.blob-result
.file-holder
.file-title
......
......@@ -20,7 +20,7 @@
:"data-container" => "body"}
= gitlab_config.protocol.upcase
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true
- if project.kind_of?(Project) && project.empty_repo?
- if project.kind_of?(Project)
.input-group-addon
.visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
= visibility_level_icon(project.visibility_level)
......
- name = field[:name]
- title = field[:title] || name.humanize
- value = service_field_value(field[:type], @service.send(name))
- value = @service.send(name)
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
......@@ -19,6 +19,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
= form.password_field name, placeholder: value, class: 'form-control'
= form.password_field name, value: value, class: 'form-control'
- if help
%span.help-block= help
- Gitlab::VisibilityLevel.values.each do |level|
- next if skip_level?(form_model, level)
.radio
- restricted = restricted_visibility_levels.include?(level)
= form.label "#{model_method}_#{level}" do
......
......@@ -43,11 +43,15 @@
placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true)
.filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone')
= select_tag('milestone_title', projects_milestones_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'})
- if @project
.filter-item.inline.labels-filter
= select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label')
= select_tag('label_name', project_labels_options(@project),
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Label'})
.pull-right
= render 'shared/sort_dropdown'
......
......@@ -19,7 +19,7 @@ class IrkerWorker
branch = "\x0305#{branch}\x0f"
end
# Firsts messages are for branch creation/deletion
# First messages are for branch creation/deletion
send_branch_updates push_data, project, repo_name, committer, branch
# Next messages are for commits
......@@ -34,7 +34,7 @@ class IrkerWorker
def init_perform(set, chans, colors)
@colors = colors
@channels = chans
start_connection set['server_ip'], set['server_port']
start_connection set['server_host'], set['server_port']
end
def start_connection(irker_server, irker_port)
......
......@@ -164,6 +164,7 @@ Gitlab::Application.routes.draw do
put :block
put :unblock
put :unlock
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
......@@ -228,7 +229,7 @@ Gitlab::Application.routes.draw do
#
resource :profile, only: [:show, :update] do
member do
get :history
get :audit_log
get :applications
put :reset_private_token
......@@ -351,6 +352,7 @@ Gitlab::Application.routes.draw do
post :toggle_star
post :markdown_preview
get :autocomplete_sources
get :activity
end
scope module: :projects do
......
class AddProjectViewToUsers < ActiveRecord::Migration
def change
add_column :users, :project_view, :integer, default: 0
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150709134649) do
ActiveRecord::Schema.define(version: 20150713160110) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -588,6 +588,7 @@ ActiveRecord::Schema.define(version: 20150709134649) do
t.text "otp_backup_codes"
t.string "public_email", default: "", null: false
t.integer "dashboard", default: 0
t.integer "project_view", default: 0
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
......@@ -4,7 +4,7 @@
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
- [GitLab Basics](gitlab_basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
......
......@@ -4,7 +4,7 @@
- [Users](users.md)
- [Session](session.md)
- [Projects](projects.md)
- [Projects](projects.md) including setting Webhooks
- [Project Snippets](project_snippets.md)
- [Services](services.md)
- [Repositories](repositories.md)
......@@ -20,6 +20,7 @@
- [System Hooks](system_hooks.md)
- [Groups](groups.md)
- [Namespaces](namespaces.md)
- [Settings](settings.md)
## Clients
......
......@@ -4,16 +4,16 @@
### Project visibility level
Project in GitLab has be either private, internal or public.
You can determine it by `visibility_level` field in project.
You can determine it by `visibility_level` field in project.
Constants for project visibility levels are next:
* Private. `visibility_level` is `0`.
* Private. `visibility_level` is `0`.
Project access must be granted explicitly for each user.
* Internal. `visibility_level` is `10`.
The project can be cloned by any logged in user.
* Public. `visibility_level` is `20`.
The project can be cloned without any authentication.
......@@ -362,7 +362,7 @@ Parameters:
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
On success, method returns 200 with the updated project. If parameters are
On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
### Fork project
......@@ -479,6 +479,9 @@ rely on the returned JSON structure.
## Hooks
Also called Project Hooks and Webhooks.
These are different for [System Hooks](system_hooks.md) that are system wide.
### List project hooks
Get a list of project hooks.
......
# Application settings
This API allows you to read and modify GitLab instance application settings.
## Get current application settings:
```
GET /application/settings
```
```json
{
"id": 1,
"default_projects_limit": 10,
"signup_enabled": true,
"signin_enabled": true,
"gravatar_enabled": true,
"sign_in_text": "",
"created_at": "2015-06-12T15:51:55.432Z",
"updated_at": "2015-06-30T13:22:42.210Z",
"home_page_url": "",
"default_branch_protection": 2,
"twitter_sharing_enabled": true,
"restricted_visibility_levels": [],
"max_attachment_size": 10,
"session_expire_delay": 10080,
"default_project_visibility": 0,
"default_snippet_visibility": 0,
"restricted_signup_domains": [],
"user_oauth_applications": true,
"after_sign_out_path": ""
}
```
## Change application settings:
```
PUT /application/settings
```
Parameters:
- `default_projects_limit` - project limit per user
- `signup_enabled` - enable registration
- `signin_enabled` - enable login via GitLab account
- `gravatar_enabled` - enable gravatar
- `sign_in_text` - text on login page
- `home_page_url` - redirect to this URL when not logged in
- `default_branch_protection` - determine if developers can push to master
- `twitter_sharing_enabled` - allow users to share project creation in twitter
- `restricted_visibility_levels` - restrict certain visibility levels
- `max_attachment_size` - limit attachment size
- `session_expire_delay` - session lifetime
- `default_project_visibility` - what visibility level new project receives
- `default_snippet_visibility` - what visibility level new snippet receives
- `restricted_signup_domains` - force people to use only corporate emails for signup
- `user_oauth_applications` - allow users to create oauth applicaitons
- `after_sign_out_path` - where redirect user after logout
All parameters are optional. You can send only one that you want to change.
```json
{
"id": 1,
"default_projects_limit": 10,
"signup_enabled": true,
"signin_enabled": true,
"gravatar_enabled": true,
"sign_in_text": "",
"created_at": "2015-06-12T15:51:55.432Z",
"updated_at": "2015-06-30T13:22:42.210Z",
"home_page_url": "",
"default_branch_protection": 2,
"twitter_sharing_enabled": true,
"restricted_visibility_levels": [],
"max_attachment_size": 10,
"session_expire_delay": 10080,
"default_project_visibility": 0,
"default_snippet_visibility": 0,
"restricted_signup_domains": [],
"user_oauth_applications": true,
"after_sign_out_path": ""
}
```
......@@ -397,4 +397,32 @@ Parameters:
- `uid` (required) - id of specified user
- `id` (required) - SSH key ID
Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
\ No newline at end of file
Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
## Block user
Blocks the specified user. Available only for admin.
```
PUT /users/:uid/block
```
Parameters:
- `uid` (required) - id of specified user
Will return `200 OK` on success, or `404 User Not Found` is user cannot be found.
## Unblock user
Unblocks the specified user. Available only for admin.
```
PUT /users/:uid/unblock
```
Parameters:
- `uid` (required) - id of specified user
Will return `200 OK` on success, or `404 User Not Found` is user cannot be found.
......@@ -11,3 +11,5 @@ Step-by-step guides on the basics of working with Git and GitLab.
* [Basic Git commands](basic-git-commands.md)
* [Create a project](create-project.md)
* [Create a group](create-group.md)
......@@ -2,7 +2,7 @@
## Start working on your project
* In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, start by signing in at GitLab.com.. To do it, go to your [GitLab.com](https://gitlab.com) account
* In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com)
* When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen
......
# How to create a group in GitLab
## Create a group
Your projects in [GitLab.com](https://gitlab.com) can be organized in 2 different ways:
under your own namespace for single projects, such as ´your-name/project-1'; or under groups.
If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects.
To create a group, follow the instructions below:
Sign in to [GitLab.com](https://gitlab.com).
When you are on your Dashboard, click on "Groups" on the left menu of your screen:
![Go to groups](basicsimages/select-group2.png)
Click on "New group" on the top right side of your screen:
![New group](basicsimages/click-on-new-group.png)
Fill out the information required:
1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores)
1. Add details or a group description
1. You can choose a group avatar if you'd like
1. Click on "create group"
![Group information](basicsimages/group_info.png)
## Add a project to a group
There are 2 different ways to add a new project to a group:
* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md)
![New project](basicsimages/new_project.png)
* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen
![Create a group](basicsimages/create_group.png)
......@@ -57,6 +57,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
- 16 cores supports up to 10,000 users
- 32 cores supports up to 20,000 users
- 64 cores supports up to 40,000 users
- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
### Memory
......@@ -64,15 +65,17 @@ You need at least 2GB of addressable memory (RAM + swap) to install and use GitL
With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
- 8GB RAM supports up to 5,000 users
- 16GB RAM supports up to 10,000 users
- 32GB RAM supports up to 20,000 users
- 64GB RAM supports up to 40,000 users
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application.
- 1GB RAM + 1GB swap supports up to 100 users but it will be slow
- **2GB RAM** is the **recommended** memory size and supports up to 100 users
- 4GB RAM supports up to 1,000 users
- 8GB RAM supports up to 2,000 users
- 16GB RAM supports up to 4,000 users
- 32GB RAM supports up to 8,000 users
- 64GB RAM supports up to 16,000 users
- 128GB RAM supports up to 32,000 users
- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
## Unicorn Workers
......@@ -106,4 +109,8 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- IE 10+
\ No newline at end of file
- IE 10+
### Common UI problems with IE
If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file
......@@ -343,6 +343,36 @@ Strikethrough uses two tildes. ~~Scratch this.~~
- Or minuses
+ Or pluses
If a list item contains multiple paragraphs,
each subsequent paragraph should be indented with four spaces.
```no-highlight
1. First ordered list item
Second paragraph of first item.
2. Another item
```
1. First ordered list item
Second paragraph of first item.
2. Another item
If the second paragraph isn't indented with four spaces,
the second list item will be incorrectly labeled as `1`.
```no-highlight
1. First ordered list item
Second paragraph of first item.
2. Another item
```
1. First ordered list item
Second paragraph of first item.
2. Another item
## Links
There are two ways to create links, inline-style and reference-style.
......
......@@ -30,3 +30,9 @@ will be. Setting it to **Starred Projects** will make that Dashboard view the
default when signing in or clicking the application logo in the upper left.
The default is **Your Projects**.
### Default Project view
It allows user to choose what content he or she want to see on project page.
The default is **Readme**.
......@@ -9,38 +9,43 @@ See the project homepage for further info: https://gitlab.com/esr/irker
## Needed setup
You will first need an Irker daemon. You can download the Irker code from its
gitorious repository on https://gitorious.org/irker: `git clone
git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can
run the python script named `irkerd`. This script is the gateway script, it acts
both as an IRC client, for sending messages to an IRC server obviously, and as a
TCP server, for receiving messages from the GitLab service.
repository on https://gitlab.com/esr/irker:
If the Irker server runs on the same machine, you are done. If not, you will
need to follow the firsts steps of the next section.
```
git clone https://gitlab.com/esr/irker.git
```
## Optional setup
Once you have downloaded the code, you can run the python script named `irkerd`.
This script is the gateway script, it acts both as an IRC client, for sending
messages to an IRC server obviously, and as a TCP server, for receiving messages
from the GitLab service.
In the `app/models/project_services/irker_service.rb` file, you can modify some
options in the `initialize_settings` method:
- **server_ip** (defaults to `localhost`): the server IP address where the
`irkerd` daemon runs;
- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon;
- **max_channels** (defaults to `3`): the maximum number of recipients the
client is authorized to join, per project;
- **default_irc_uri** (no default) : if this option is set, it has to be in the
format `irc[s]://domain.name` and will be prepend to each and every channel
provided by the user which is not a full URI.
If the Irker server runs on the same machine, you are done. If not, you will
need to follow the firsts steps of the next section.
If the Irker server and the GitLab application do not run on the same host, you
will **need** to setup at least the **server_ip** option.
## Complete these steps in GitLab:
1. Navigate to the project you want to configure for notifications.
1. Select "Settings" in the top navigation.
1. Select "Services" in the left navigation.
1. Click "Irker".
1. Select the "Active" checkbox.
1. Enter the server host address where `irkerd` runs (defaults to `localhost`)
in the `Server host` field on the Web page
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
`Server port` field on the Web page.
1. Optional: if `Default IRC URI` is set, it has to be in the format
`irc[s]://domain.name` and will be prepend to each and every channel provided
by the user which is not a full URI.
1. Specify the recipients (e.g. #channel1, user1, etc.)
1. Save or optionally click "Test Settings".
## Note on Irker recipients
Irker accepts channel names of the form `chan` and `#chan`, both for the
`#chan` channel. If you want to send messages in query, you will need to add
`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter
`,isnick` after the channel name, in this form: `Aorimn,isnick`. In this latter
case, `Aorimn` is treated as a nick and no more as a channel name.
Irker can also join password-protected channels. Users need to append
`?key=thesecretpassword` to the chan name.
......@@ -66,10 +66,11 @@ Xth: (2 working days before the 22nd)
Xth: (1 working day before the 22nd)
- [ ] Create CE, EE, CI stable versions (#LINK)
- [ ] Create Omnibus tags and build packages
- [ ] Update GitLab.com with the stable version (#LINK)
- [ ] Update ci.gitLab.com with the stable version (#LINK)
- [ ] Merge CE stable into EE stable
- [ ] Create (hopefully final) CE, EE, CI release candidates (#LINK)
- [ ] Create Omnibus tags and build packages for the latest release candidates
- [ ] Update GitLab.com with the latest RC (#LINK)
- [ ] Update ci.gitLab.com with the latest RC (#LINK)
22nd before 12AM CET:
......@@ -77,7 +78,11 @@ Release before 12AM CET / 3AM PST, to make sure the majority of our users
get the new version on the 22nd and there is sufficient time in the European
workday to quickly fix any issues.
- [ ] Release CE, EE and CI (#LINK)
- [ ] Merge CE stable into EE stable (#LINK)
- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK)
- [ ] BEFORE 11AM CET Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
- [ ] BEFORE 12AM CET Publish the release blog post (#LINK)
- [ ] Tweet about the release (blog post) (#LINK)
- [ ] Schedule a second tweet of the release announcement at 6PM CET / 9AM PST
```
......
# From 6.x or 7.x to 7.12
*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.12.md) for the most up to date instructions.*
# From 6.x or 7.x to 7.13
*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.13.md) for the most up to date instructions.*
This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.12.
This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.13.
## Global issue numbers
......@@ -71,7 +71,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 7-12-stable
sudo -u git -H git checkout 7-13-stable
```
OR
......@@ -79,7 +79,7 @@ OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 7-12-stable-ee
sudo -u git -H git checkout 7-13-stable-ee
```
## 4. Install additional packages
......@@ -164,11 +164,11 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
git diff 6-0-stable:config/gitlab.yml.example 7-12-stable-ee:config/gitlab.yml.example
git diff 6-0-stable:config/gitlab.yml.example 7-13-stable-ee:config/gitlab.yml.example
```
* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-12-stable-ee/config/gitlab.yml.example but with your settings.
* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-12-stable-ee/config/unicorn.rb.example but with your settings.
* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-13-stable-ee/config/gitlab.yml.example but with your settings.
* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-13-stable-ee/config/unicorn.rb.example but with your settings.
* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings.
* Copy rack attack middleware config
......@@ -184,14 +184,14 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### Change Nginx settings
* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-12-stable-ee/lib/support/nginx/gitlab but with your settings.
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-12-stable-ee/lib/support/nginx/gitlab-ssl but with your settings.
* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-13-stable-ee/lib/support/nginx/gitlab but with your settings.
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ee/blob/7-13-stable-ee/lib/support/nginx/gitlab-ssl but with your settings.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
### Check the version of /usr/local/bin/git
If you installed Git from source into /usr/local/bin/git then please [check
your version](7.11-to-7.12.md).
your version](7.12-to-7.13.md).
## 9. Start application
......
# From 7.12 to 7.13
### 0. Double-check your Git version
**This notice applies only to /usr/local/bin/git**
If you compiled Git from source on your GitLab server then please double-check
that you are using a version that protects against CVE-2014-9390. For six
months after this vulnerability became known the GitLab installation guide
still contained instructions that would install the outdated, 'vulnerable' Git
version 2.1.2.
Run the following command to get your current Git version.
```
/usr/local/bin/git --version
```
If you see 'No such file or directory' then you did not install Git according
to the outdated instructions from the GitLab installation guide and you can go
to the next step 'Stop server' below.
If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
v2.2.1 or newer. You can use the [instructions in the GitLab source
installation
guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
to install a newer version of Git.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 7-13-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 7-13-stable-ee
```
### 4. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.3
```
### 5. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 6. Update config files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-12-stable:config/gitlab.yml.example origin/7-13-stable:config/gitlab.yml.example
``````
### 7. Start application
sudo service gitlab start
sudo service nginx restart
### 8. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (7.12)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.11 to 7.12](7.11-to-7.12.md), except for the database migration
(The backup is already migrated to the previous version)
### 2. Restore from the backup:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
......@@ -16,6 +16,7 @@ git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter
mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
python db_converter.py gitlabhq_production.mysql gitlabhq_production.psql
ed -s gitlabhq_production.psql < move_drop_indexes.ed
# Import the database dump as the application database user
sudo -u git psql -f gitlabhq_production.psql -d gitlabhq_production
......@@ -56,13 +57,17 @@ sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.
# Convert gitlabhq_production.mysql
sudo -u git -H mkdir db
sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_production.mysql db/database.sql
sudo -u git -H ed -s db/database.sql < mysql-postgresql-converter/move_drop_indexes.ed
# Compress database backup
sudo -u git -H gzip db/database.sql
# Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar.
# Warning: if you forget to replace TIMESTAMP below, tar will create a new file
# 'TIMESTAMP_gitlab_backup.tar' without giving an error.
sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql
sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz
# Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab
# installation. Remember to recreate the indexes after the import.
......
......@@ -7,7 +7,9 @@ RUN apt-get update -q \
ca-certificates \
openssh-server \
wget \
apt-transport-https
apt-transport-https \
vim \
nano
# Download & Install GitLab
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
......@@ -23,12 +25,21 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
# Prepare default configuration
RUN ( \
echo "" && \
echo "# Docker options" && \
echo "# Prevent Postgres from trying to allocate 25% of total memory" && \
echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb
# Expose web & ssh
EXPOSE 80 22
EXPOSE 443 80 22
# Define data volumes
VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"]
# Copy assets
COPY assets/wrapper /usr/local/bin/
COPY assets/gitlab.rb /etc/gitlab/
# Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"]
# GitLab Docker images
## What is GitLab?
GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster.
Learn more on [https://about.gitlab.com](https://about.gitlab.com)
The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
## After starting a container
......@@ -11,152 +8,160 @@ After starting a container you can go to [http://localhost:8080/](http://localho
It might take a while before the docker container is responding to queries.
You can check the status with something like `sudo docker logs -f 7c10172d7705`.
You can check the status with something like `sudo docker logs -f gitlab`.
You can login to the web interface with username `root` and password `password`.
Next time, you can just use docker start and stop to run the container.
## How to build the docker images
## Run the image
This guide will also let you know how to build docker images yourself.
Please run all the commands from the GitLab repo root directory.
People using boot2docker should run all the commands without sudo.
Run the image:
```bash
sudo docker run --detach \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
## Choosing between the single and the app and data images
This will download and start GitLab CE container and publish ports needed to access SSH, HTTP and HTTPS.
All GitLab data will be stored as subdirectories of `/srv/gitlab/`.
The container will automatically `restart` after system reboot.
Normally docker uses a single image for one applications.
But GitLab stores repositories and uploads in the filesystem.
This means that upgrades of a single image are hard.
That is why we recommend using separate app and data images.
We'll first describe how to use a single image.
After that we'll describe how to use the app and data images.
After this you can login to the web interface as explained above in 'After starting a container'.
## Single image
## Where is the data stored?
Get a published image from Dockerhub:
The GitLab container uses host mounted volumes to store persistent data:
- `/srv/gitlab/data` mounted as `/var/opt/gitlab` in the container is used for storing *application data*
- `/srv/gitlab/logs` mounted as `/var/log/gitlab` in the container is used for storing *logs*
- `/srv/gitlab/config` mounted as `/etc/gitlab` in the container is used for storing *configuration*
```bash
sudo docker pull sytse/gitlab-ce:7.10.1
```
You can fine tune these directories to meet your requirements.
Run the image:
### Configure GitLab
This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
To access GitLab configuration, you can start an bash in a new the context of running container, you will be able to browse all directories and use your favorite text editor:
```bash
sudo docker run --detach --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1
sudo docker exec -it gitlab /bin/bash
```
After this you can login to the web interface as explained above in 'After starting a container'.
Build the image:
You can also edit just `/etc/gitlab/gitlab.rb`:
```bash
sudo docker build --tag sytse/gitlab-ce:7.10.1 docker/single/
sudo docker exec -it gitlab vi /etc/gitlab/gitlab.rb
```
Publish the image to Dockerhub:
**You should set the `external_url` to point to a valid URL.**
```bash
sudo docker push sytse/gitlab-ce
```
**You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).**
**To receive e-mails from GitLab you have to configure the [SMTP settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md),
because Docker image doesn't have a SMTP server.**
Diagnosing commands:
**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab:
```bash
sudo docker run -i -t sytse/gitlab-ce:7.10.1
sudo docker run -ti -e TERM=linux --name gitlab-ce-troubleshoot --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1 bash /usr/local/bin/wrapper
sudo docker restart gitlab
```
## App and data images
For more options for configuring the container please check [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
### Get published images from Dockerhub
## Diagnose potential problems
Read container logs:
```bash
sudo docker pull sytse/gitlab-data
sudo docker pull sytse/gitlab-app:7.10.1
sudo docker logs gitlab
```
### Run the images
Enter running container:
```bash
sudo docker run --name gitlab-data sytse/gitlab-data /bin/true
sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data sytse/gitlab-app:7.10.1
sudo docker exec -it gitlab /bin/bash
```
After this you can login to the web interface as explained above in 'After starting a container'.
### Build images
From within container you can administrer GitLab container as you would normally administer Omnibus installation: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md.
Build your own based on the Omnibus packages with the following commands.
### Upgrade GitLab to newer version
To upgrade GitLab to new version you have to do:
1. pull new image,
```bash
sudo docker build --tag gitlab-data docker/data/
sudo docker build --tag gitlab-app:7.10.1 docker/app/
sudo docker stop gitlab
```
After this run the images:
1. stop running container,
```bash
sudo docker run --name gitlab-data gitlab-data /bin/true
sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
sudo docker rm gitlab
```
We assume using a data volume container, this will simplify migrations and backups.
This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
The directories on data container are:
- `/var/opt/gitlab` for application data
- `/var/log/gitlab` for logs
- `/etc/gitlab` for configuration
### Configure GitLab
This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
1. remove existing container,
```bash
sudo docker pull gitlab/gitlab-ce:latest
```
1. create the container once again with previously specified options.
```bash
sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab-data ubuntu
vi /etc/gitlab/gitlab.rb
sudo docker run --detach \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
On the first run GitLab will reconfigure and update itself.
You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
### Run GitLab CE on public IP address
### Upgrade GitLab with app and data images
You can make Docker to use your IP address and forward all traffic to the GitLab CE container.
You can do that by modifying the `--publish` ([Binding container ports to the host](https://docs.docker.com/articles/networking/#binding-ports)):
To upgrade GitLab to new versions, stop running container, create new docker image and container from that image.
> --publish=[] : Publish a container᾿s port or a range of ports to the host format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory:
To expose GitLab CE on IP 1.1.1.1:
```bash
sudo docker stop gitlab-app
sudo docker rm gitlab-app
sudo docker build --tag gitlab-app:7.10.1 docker/app/
sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
sudo docker run --detach \
--publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup the app image:
You can then access GitLab instance at http://1.1.1.1/ and https://1.1.1.1/.
### Build the image
This guide will also let you know how to build docker image yourself.
Please run the command from the GitLab repo root directory.
People using boot2docker should run all the commands without sudo.
```bash
sudo docker rmi gitlab-app:7.8.1
sudo docker build --tag gitlab/gitlab-ce:latest docker/
```
### Publish images to Dockerhub
### Publish the image to Dockerhub
- Ensure the containers are running
- Login to Dockerhub with `sudo docker login`
- Run the following (replace '7.10.1' with the version you're using and 'Sytse Sijbrandij' with your name):
```bash
sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1
sudo docker push sytse/gitlab-app:7.10.1
sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-data sytse/gitlab-data
sudo docker push sytse/gitlab-data
sudo docker login
sudo docker push gitlab/gitlab-ce:latest
```
## Troubleshooting
Please see the [troubleshooting](troubleshooting.md) file in this directory.
Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it.
FROM ubuntu:14.04
# Install required packages
RUN apt-get update -q \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \
ca-certificates \
openssh-server \
wget \
apt-transport-https
# Download & Install GitLab
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
# Manage SSHD through runit
RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& mkfifo /opt/gitlab/sv/sshd/supervise/ok \
&& printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
&& chmod a+x /opt/gitlab/sv/sshd/run \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
# Expose web & ssh
EXPOSE 80 22
# Copy assets
COPY assets/wrapper /usr/local/bin/
# Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"]
\ No newline at end of file
#!/bin/bash
function sigterm_handler() {
echo "SIGTERM signal received, try to gracefully shutdown all services..."
gitlab-ctl stop
}
trap "sigterm_handler; exit" TERM
function entrypoint() {
# Default is to run runit and reconfigure GitLab
gitlab-ctl reconfigure &
/opt/gitlab/embedded/bin/runsvdir-start &
wait
}
entrypoint
FROM busybox
# Declare volumes
VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
# Copy assets
COPY assets/gitlab.rb /etc/gitlab/
CMD /bin/sh
# External URL should be your Docker instance.
# By default, this example is the "standard" boot2docker IP.
# Always use port 80 here to force the internal nginx to bind port 80,
# even if you intend to use another port in Docker.
external_url "http://192.168.59.103/"
# Prevent Postgres from trying to allocate 25% of total memory
postgresql['shared_buffers'] = '1MB'
# Configure GitLab to redirect PostgreSQL logs to the data volume
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Some configuration of GitLab
# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_support_email'] = 'support@example.com'
gitlab_rails['time_zone'] = 'Europe/Paris'
# SMTP settings
# You must use an external server, the Docker container does not install an SMTP server
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.example.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "user"
gitlab_rails['smtp_password'] = "password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "plain"
gitlab_rails['smtp_enable_starttls_auto'] = true
# Enable LDAP authentication
# gitlab_rails['ldap_enabled'] = true
# gitlab_rails['ldap_host'] = 'ldap.example.com'
# gitlab_rails['ldap_port'] = 389
# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
# gitlab_rails['ldap_allow_username_or_email_login'] = false
# gitlab_rails['ldap_uid'] = 'uid'
# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
app:
build: .
{
"id": "/gitlab",
"ports": [0,0],
"cpus": 2,
"mem": 2048.0,
"disk": 10240.0,
"container": {
"type": "DOCKER",
"docker": {
"network": "HOST",
"image": "gitlab/gitlab-ce:latest"
},
"volumes": [
{
"containerPath": "/etc/gitlab",
"hostPath": "/var/data/etc/gitlab",
"mode": "RW"
},
{
"containerPath": "/var/opt/gitlab",
"hostPath": "/var/data/opt/gitlab",
"mode": "RW"
},
{
"containerPath": "/var/log/gitlab",
"hostPath": "/var/data/log/gitlab",
"mode": "RW"
}
]
}
}
\ No newline at end of file
# External URL should be your Docker instance.
# By default, GitLab will use the Docker container hostname.
# Always use port 80 here to force the internal nginx to bind port 80,
# even if you intend to use another port in Docker.
# external_url "http://192.168.59.103/"
# Prevent Postgres from trying to allocate 25% of total memory
postgresql['shared_buffers'] = '1MB'
# Configure GitLab to redirect PostgreSQL logs to the data volume
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Some configuration of GitLab
# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_support_email'] = 'support@example.com'
gitlab_rails['time_zone'] = 'Europe/Paris'
# SMTP settings
# You must use an external server, the Docker container does not install an SMTP server
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.example.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "user"
gitlab_rails['smtp_password'] = "password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "plain"
gitlab_rails['smtp_enable_starttls_auto'] = true
# Enable LDAP authentication
# gitlab_rails['ldap_enabled'] = true
# gitlab_rails['ldap_host'] = 'ldap.example.com'
# gitlab_rails['ldap_port'] = 389
# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
# gitlab_rails['ldap_allow_username_or_email_login'] = false
# gitlab_rails['ldap_uid'] = 'uid'
# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
{
"id": "/gitlab",
"ports": [0,0],
"cpus": 2,
"mem": 2048.0,
"disk": 10240.0,
"container": {
"type": "DOCKER",
"docker": {
"network": "HOST",
"image": "sytse/gitlab-ce:7.10.1"
}
}
}
\ No newline at end of file
......@@ -9,24 +9,19 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands
```bash
sudo docker build --tag gitlab_image docker/
sudo docker build --tag gitlab/gitlab-ce:latest docker/
sudo docker rm -f gitlab_app
sudo docker rm -f gitlab_data
sudo docker rm -f gitlab
sudo docker run --name gitlab_data gitlab_image /bin/true
sudo docker exec -it gitlab vim /etc/gitlab/gitlab.rb
sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
sudo docker exec gitlab tail -f /var/log/gitlab/reconfigure.log
sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
sudo docker exec gitlab tail -f /var/log/gitlab/postgresql/current
sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
sudo docker exec gitlab cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
sudo docker exec gitlab cat /etc/gitlab/gitlab.rb
```
# Interactively
......@@ -37,7 +32,16 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab
# - we run interactively (-t -i)
# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
# - we choose another startup command (bash)
sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
sudo docker run --ti \
-e TERM=linux
--publish 80443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest \
bash
# Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
......@@ -64,10 +68,17 @@ free -m
# Cleanup
Remove ALL docker containers and images (also non GitLab ones):
Remove ALL docker containers and images (also non GitLab ones).
**Be careful, because the `-v` also removes volumes attached to the images.**
```
docker rm $(docker ps -a -q)
```bash
# Remove all containers with attached volumes
docker rm -v $(docker ps -a -q)
# Remove all images
docker rmi $(docker images -q)
# Remove GitLab persistent data
rm -rf /srv/gitlab
```
......@@ -4,6 +4,10 @@ Feature: Groups
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
Scenario: I should have back to group button
When I visit group "Owned" page
Then I should see back to dashboard button
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
......
......@@ -23,7 +23,7 @@ Feature: Profile Active Tab
Then the active main tab should be Preferences
And no other main tabs should be active
Scenario: On Profile History
Given I visit profile history page
Then the active main tab should be History
Scenario: On Profile Audit Log
Given I visit Audit Log page
Then the active main tab should be Audit Log
And no other main tabs should be active
......@@ -63,7 +63,7 @@ Feature: Profile
Scenario: I visit history tab
Given I have activity
When I visit profile history page
When I visit Audit Log page
Then I should see my activity
Scenario: I visit my user page
......
......@@ -188,6 +188,7 @@ Feature: Project Issues
Scenario: I submit new unassigned issue as guest
Given public project "Community"
When I visit project "Community" page
And I visit project "Community" issues page
And I click link "New Issue"
And I should not see assignee field
And I should not see milestone field
......
......@@ -18,9 +18,22 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
Scenario: I should have back to group button
And project "Shop" belongs to group
And I visit project "Shop" page
Then I should see back to group button
Scenario: I should have back to group button
And I visit project "Shop" page
Then I should see back to dashboard button
Scenario: I should have readme on page
And I visit project "Shop" page
Then I should see project "Shop" README
@javascript
Scenario: I should see project activity
When I visit project "Shop" page
When I visit project "Shop" activity page
Then I should see project "Shop" activity feed
Scenario: I visit edit project
......@@ -45,24 +58,12 @@ Feature: Project
And I save project
Then I should see project with merge request template saved
Scenario: I should see project readme and version
When I visit project "Shop" page
And I should see project "Shop" version
Scenario: I should change project default branch
When I visit edit project "Shop" page
And change project default branch
And I save project
Then I should see project default branch changed
@javascript
Scenario: I should have default tab per my preference
And I own project "Forum"
When I select project "Forum" README tab
Then I should see project "Forum" README
And I visit project "Shop" page
Then I should see project "Shop" README
Scenario: I tag a project
When I visit edit project "Shop" page
Then I should see project settings
......
......@@ -3,7 +3,7 @@ Feature: Project Shortcuts
Background:
Given I sign in as a user
And I own a project
And I visit my project's home page
And I visit my project's commits page
@javascript
Scenario: Navigate to files tab
......@@ -12,6 +12,7 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to commits tab
Given I visit my project's files page
Given I press "g" and "c"
Then the active main tab should be Commits
......@@ -46,7 +47,11 @@ Feature: Project Shortcuts
Then the active main tab should be Wiki
@javascript
Scenario: Navigate to project feed
Given I visit my project's files page
Scenario: Navigate to project home
Given I press "g" and "p"
Then the active main tab should be Home
@javascript
Scenario: Navigate to project feed
Given I press "g" and "e"
Then the active main tab should be Activity
......@@ -5,6 +5,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedUser
include Select2Helper
step 'I should see back to dashboard button' do
expect(page).to have_content 'Back to Dashboard'
end
step 'gitlab user "Mike"' do
create(:user, name: "Mike")
end
......
......@@ -19,7 +19,7 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('Preferences')
end
step 'the active main tab should be History' do
ensure_active_main_tab('History')
step 'the active main tab should be Audit Log' do
ensure_active_main_tab('Audit Log')
end
end
......@@ -115,7 +115,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see my activity' do
expect(page).to have_content "#{current_user.name} closed issue"
expect(page).to have_content "Signed in with standard authentication"
end
step 'my password is expired' do
......
......@@ -194,6 +194,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
When "I visit project \"Community\" issues page" do
project = Project.find_by(name: 'Community')
visit namespace_project_issues_path(project.namespace, project)
end
When "I visit empty project's issues page" do
project = Project.find_by(name: 'Empty Project')
visit namespace_project_issues_path(project.namespace, project)
......
......@@ -103,13 +103,15 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
expect(page).to have_link 'README.md'
expect(page).to have_content 'Sample repo for testing gitlab features'
page.within('#README') do
expect(page).to have_content 'Sample repo for testing gitlab features'
end
end
step 'I should see project "Shop" README' do
expect(page).to have_link 'README.md'
expect(page).to have_content 'testme'
page.within('#README') do
expect(page).to have_content 'testme'
end
end
step 'I visit project "Shop" settings page' do
......@@ -139,4 +141,18 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should not see "Snippets" button' do
expect(page).not_to have_link 'Snippets'
end
step 'project "Shop" belongs to group' do
group = create(:group)
@project.namespace = group
@project.save!
end
step 'I should see back to dashboard button' do
expect(page).to have_content 'Back to Dashboard'
end
step 'I should see back to group button' do
expect(page).to have_content 'Back to Group'
end
end
......@@ -33,4 +33,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
find('body').native.send_key('g')
find('body').native.send_key('w')
end
step 'I press "g" and "e"' do
find('body').native.send_key('g')
find('body').native.send_key('e')
end
end
......@@ -200,7 +200,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I should see jira service settings saved' do
expect(find_field('Project url').value).to eq 'http://jira.example'
expect(find_field('Username').value).to eq 'gitlab'
expect(find_field('Password').value).not_to eq 'gitlab'
expect(find_field('Password').value).to eq 'gitlab'
expect(find_field('Api version').value).to eq '2'
end
......
......@@ -187,7 +187,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click on "add a file" link' do
click_link 'add a file'
click_link 'adding README'
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
......
......@@ -5,7 +5,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
include SharedUser
step "The project has no stars" do
expect(page).not_to have_content '.star-buttons'
expect(page).not_to have_content '.toggle-star'
end
step "The project has 0 stars" do
......
......@@ -139,8 +139,8 @@ module SharedPaths
visit profile_preferences_path
end
step 'I visit profile history page' do
visit history_profile_path
step 'I visit Audit Log page' do
visit audit_log_profile_path
end
# ----------------------------------------
......@@ -287,6 +287,10 @@ module SharedPaths
visit namespace_project_path(project.namespace, project)
end
step 'I visit project "Shop" activity page' do
visit activity_namespace_project_path(project.namespace, project)
end
step 'I visit project "Forked Shop" merge requests page' do
visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
end
......
......@@ -49,4 +49,8 @@ module SharedProjectTab
expect(page).to have_content('Back to project')
end
end
step 'the active main tab should be Activity' do
ensure_active_main_tab('Activity')
end
end
......@@ -52,5 +52,6 @@ module API
mount Namespaces
mount Branches
mount Labels
mount Settings
end
end
......@@ -295,5 +295,27 @@ module API
class BroadcastMessage < Grape::Entity
expose :message, :starts_at, :ends_at, :color, :font
end
class ApplicationSetting < Grape::Entity
expose :id
expose :default_projects_limit
expose :signup_enabled
expose :signin_enabled
expose :gravatar_enabled
expose :sign_in_text
expose :created_at
expose :updated_at
expose :home_page_url
expose :default_branch_protection
expose :twitter_sharing_enabled
expose :restricted_visibility_levels
expose :max_attachment_size
expose :session_expire_delay
expose :default_project_visibility
expose :default_snippet_visibility
expose :restricted_signup_domains
expose :user_oauth_applications
expose :after_sign_out_path
end
end
end
......@@ -83,9 +83,9 @@ module API
# POST /groups/:id/projects/:project_id
post ":id/projects/:project_id" do
authenticated_as_admin!
group = Group.find(params[:id])
group = Group.find_by(id: params[:id])
project = Project.find(params[:project_id])
result = ::Projects::TransferService.new(project, current_user, namespace_id: group.id).execute
result = ::Projects::TransferService.new(project, current_user).execute(group)
if result
present group
......
module API
class Settings < Grape::API
before { authenticated_as_admin! }
helpers do
def current_settings
@current_setting ||=
(ApplicationSetting.current || ApplicationSetting.create_from_defaults)
end
end
# Get current applicaiton settings
#
# Example Request:
# GET /application/settings
get "application/settings" do
present current_settings, with: Entities::ApplicationSetting
end
# Modify applicaiton settings
#
# Example Request:
# PUT /application/settings
put "application/settings" do
attributes = current_settings.attributes.keys - ["id"]
attrs = attributes_for_keys(attributes)
if current_settings.update_attributes(attrs)
present current_settings, with: Entities::ApplicationSetting
else
render_validation_error!(current_settings)
end
end
end
end
......@@ -202,6 +202,36 @@ module API
not_found!('User')
end
end
# Block user. Available only for admin
#
# Example Request:
# PUT /users/:id/block
put ':id/block' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
if user
user.block
else
not_found!('User')
end
end
# Unblock user. Available only for admin
#
# Example Request:
# PUT /users/:id/unblock
put ':id/unblock' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
if user
user.activate
else
not_found!('User')
end
end
end
resource :user do
......
......@@ -18,23 +18,30 @@ module Backup
when "postgresql" then
$progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
system('pg_dump', config['database'], out: db_file_name)
# Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
system('pg_dump', '--clean', config['database'], out: db_file_name)
end
report_success(success)
abort 'Backup failed' unless success
$progress.print 'Compressing database ... '
success = system('gzip', db_file_name)
report_success(success)
abort 'Backup failed: compress error' unless success
end
def restore
$progress.print 'Decompressing database ... '
success = system('gzip', '-d', db_file_name_gz)
report_success(success)
abort 'Restore failed: decompress error' unless success
success = case config["adapter"]
when /^mysql/ then
$progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
$progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke
Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env
system('psql', config['database'], '-f', db_file_name)
end
......@@ -48,6 +55,10 @@ module Backup
File.join(db_dir, 'database.sql')
end
def db_file_name_gz
File.join(db_dir, 'database.sql.gz')
end
def mysql_args
args = {
'host' => '--host',
......
......@@ -33,6 +33,16 @@ module Gitlab
filename.downcase.end_with?(*%w(.adoc .ad .asciidoc))
end
# Public: Determines if the given filename is plain text.
#
# filename - Filename string to check
#
# Returns boolean
def plain?(filename)
filename.downcase.end_with?('.txt') ||
filename.downcase == 'readme'
end
def previewable?(filename)
markup?(filename)
end
......
......@@ -47,6 +47,10 @@ module Gitlab
def valid_level?(level)
options.has_value?(level)
end
def allowed_fork_levels(origin_level)
[PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
end
end
def private?
......
namespace :gitlab do
namespace :db do
task drop_all_postgres_sequences: :environment do
connection = ActiveRecord::Base.connection
connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence|
connection.execute("DROP SEQUENCE #{sequence['relname']}")
end
end
end
end
namespace :gitlab do
namespace :db do
task drop_all_tables: :environment do
connection = ActiveRecord::Base.connection
connection.tables.each do |table|
connection.drop_table(table)
end
end
end
end
......@@ -6,7 +6,7 @@ namespace :gitlab do
%W(rake rubocop),
%W(rake spinach),
%W(rake spec),
%W(rake jasmine:ci)
%W(rake teaspoon)
]
cmds.each do |cmd|
......
......@@ -9,5 +9,5 @@ unless Rails.env.production?
require 'coveralls/rake/task'
Coveralls::RakeTask.new
desc "GitLab | Run all tests on CI with simplecov"
task :test_ci => [:rubocop, :brakeman, 'jasmine:ci', :spinach, :spec, 'coveralls:push']
task :test_ci => [:rubocop, :brakeman, 'teaspoon', :spinach, :spec, 'coveralls:push']
end
......@@ -36,4 +36,32 @@ describe Admin::UsersController do
expect(user.access_locked?).to be_falsey
end
end
describe 'PATCH disable_two_factor' do
let(:user) { create(:user) }
it 'disables 2FA for the user' do
expect(user).to receive(:disable_two_factor!)
allow(subject).to receive(:user).and_return(user)
go
end
it 'redirects back' do
go
expect(response).to redirect_to(admin_user_path(user))
end
it 'displays an alert' do
go
expect(flash[:notice]).
to eq 'Two-factor Authentication has been disabled for this user'
end
def go
patch :disable_two_factor, id: user.to_param
end
end
end
......@@ -9,15 +9,27 @@ describe AutocompleteController do
before do
sign_in(user)
project.team << [user, :master]
get(:users, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username }
describe 'GET #users with project ID' do
before do
get(:users, project_id: project.id)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username }
end
describe 'GET #users with unknown project' do
before do
get(:users, project_id: 'unknown')
end
it { expect(response.status).to eq(404) }
end
end
context 'group members' do
......@@ -26,15 +38,27 @@ describe AutocompleteController do
before do
sign_in(user)
group.add_owner(user)
get(:users, group_id: group.id)
end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username }
describe 'GET #users with group ID' do
before do
get(:users, group_id: group.id)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username }
end
describe 'GET #users with unknown group ID' do
before do
get(:users, group_id: 'unknown')
end
it { expect(response.status).to eq(404) }
end
end
context 'all users' do
......@@ -48,4 +72,52 @@ describe AutocompleteController do
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq User.count }
end
context 'unauthenticated user' do
let(:public_project) { create(:project, :public) }
let(:body) { JSON.parse(response.body) }
describe 'GET #users with public project' do
before do
public_project.team << [user, :guest]
get(:users, project_id: public_project.id)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
end
describe 'GET #users with project' do
before do
get(:users, project_id: project.id)
end
it { expect(response.status).to eq(302) }
end
describe 'GET #users with unknown project' do
before do
get(:users, project_id: 'unknown')
end
it { expect(response.status).to eq(302) }
end
describe 'GET #users with inaccessible group' do
before do
project.team << [user, :guest]
get(:users, group_id: user.namespace.id)
end
it { expect(response.status).to eq(302) }
end
describe 'GET #users with no project' do
before do
get(:users)
end
it { expect(response.status).to eq(302) }
end
end
end
......@@ -55,4 +55,30 @@ describe Projects::BranchesController do
it { is_expected.to render_template('new') }
end
end
describe "POST destroy" do
render_views
before do
post :destroy,
format: :js,
id: branch,
namespace_id: project.namespace.to_param,
project_id: project.to_param
end
context "valid branch name, valid source" do
let(:branch) { "feature" }
it { expect(response.status).to eq(200) }
it { expect(subject).to render_template('destroy') }
end
context "invalid branch name, valid ref" do
let(:branch) { "no-branch" }
it { expect(response.status).to eq(404) }
it { expect(subject).to render_template('destroy') }
end
end
end
......@@ -105,19 +105,12 @@ describe Profiles::TwoFactorAuthsController do
end
describe 'DELETE destroy' do
let(:user) { create(:user, :two_factor) }
let!(:codes) { user.generate_otp_backup_codes! }
let(:user) { create(:user, :two_factor) }
it 'clears all 2FA-related fields' do
expect(user).to be_two_factor_enabled
expect(user.otp_backup_codes).not_to be_nil
expect(user.encrypted_otp_secret).not_to be_nil
it 'disables two factor' do
expect(user).to receive(:disable_two_factor!)
delete :destroy
expect(user).not_to be_two_factor_enabled
expect(user.otp_backup_codes).to be_nil
expect(user.encrypted_otp_secret).to be_nil
end
it 'redirects to profile_account_path' do
......
......@@ -32,6 +32,7 @@ FactoryGirl.define do
before(:create) do |user|
user.two_factor_enabled = true
user.otp_secret = User.generate_otp_secret(32)
user.generate_otp_backup_codes!
end
end
......
require 'rails_helper'
feature 'Admin disables 2FA for a user', feature: true do
scenario 'successfully', js: true do
login_as(:admin)
user = create(:user, :two_factor)
edit_user(user)
page.within('.two-factor-status') do
click_link 'Disable'
end
page.within('.two-factor-status') do
expect(page).to have_content 'Disabled'
expect(page).not_to have_button 'Disable'
end
end
scenario 'for a user without 2FA enabled' do
login_as(:admin)
user = create(:user)
edit_user(user)
page.within('.two-factor-status') do
expect(page).not_to have_button 'Disable'
end
end
def edit_user(user)
visit admin_user_path(user)
end
end
require 'spec_helper'
feature 'Group' do
feature 'Group', feature: true do
describe 'description' do
let(:group) { create(:group) }
let(:path) { group_path(group) }
......
require 'rails_helper'
feature 'Issue filtering by Milestone', feature: true do
include Select2Helper
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
scenario 'filters by no Milestone', js: true do
create(:issue, project: project)
create(:issue, project: project, milestone: milestone)
visit_issues(project)
filter_by_milestone(Milestone::None.title)
expect(page).to have_css('.issue-title', count: 1)
end
scenario 'filters by a specific Milestone', js: true do
create(:issue, project: project, milestone: milestone)
create(:issue, project: project)
visit_issues(project)
filter_by_milestone(milestone.title)
expect(page).to have_css('.issue-title', count: 1)
end
def visit_issues(project)
visit namespace_project_issues_path(project.namespace, project)
end
def filter_by_milestone(title)
select2(title, from: '#milestone_title')
end
end
......@@ -92,22 +92,6 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
it 'should allow filtering by issues with no specified milestone' do
visit namespace_project_issues_path(project.namespace, project, milestone_title: IssuableFinder::NONE)
expect(page).not_to have_content 'foobar'
expect(page).to have_content 'barbaz'
expect(page).to have_content 'gitlab'
end
it 'should allow filtering by a specified milestone' do
visit namespace_project_issues_path(project.namespace, project, milestone_title: issue.milestone.title)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
expect(page).not_to have_content 'gitlab'
end
it 'should allow filtering by issues with no specified assignee' do
visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
......
require 'spec_helper'
feature 'Login' do
feature 'Login', feature: true do
describe 'with two-factor authentication' do
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
......
......@@ -32,7 +32,7 @@ require 'erb'
#
# See the MarkdownFeature class for setup details.
describe 'GitLab Markdown' do
describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers
......
require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do
include Select2Helper
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
scenario 'filters by no Milestone', js: true do
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
filter_by_milestone(Milestone::None.title)
expect(page).to have_css('.merge-request-title', count: 1)
end
scenario 'filters by a specific Milestone', js: true do
create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
create(:merge_request, :simple, source_project: project)
visit_merge_requests(project)
filter_by_milestone(milestone.title)
expect(page).to have_css('.merge-request-title', count: 1)
end
def visit_merge_requests(project)
visit namespace_project_merge_requests_path(project.namespace, project)
end
def filter_by_milestone(title)
select2(title, from: '#milestone_title')
end
end
require 'spec_helper'
describe 'Comments' do
describe 'Comments', feature: true do
include RepoHelpers
describe 'On a merge request', js: true, feature: true do
......
require 'spec_helper'
feature 'Password reset' do
feature 'Password reset', feature: true do
def forgot_password
click_on 'Forgot your password?'
fill_in 'Email', with: user.email
......
......@@ -9,8 +9,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is enabled' do
before do
allow_any_instance_of(ApplicationSetting).
to receive(:signup_enabled?).and_return(true)
stub_application_setting(signup_enabled: true)
visit profile_account_path
end
......@@ -24,8 +23,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is disabled' do
before do
allow_any_instance_of(ApplicationSetting).
to receive(:signup_enabled?).and_return(false)
stub_application_setting(signup_enabled: false)
visit profile_account_path
end
......
require 'spec_helper'
describe 'Profile > Preferences' do
describe 'Profile > Preferences', feature: true do
let(:user) { create(:user) }
before do
......
require 'spec_helper'
feature 'Project' do
feature 'Project', feature: true do
describe 'description' do
let(:project) { create(:project) }
let(:path) { namespace_project_path(project.namespace, project) }
......
......@@ -45,8 +45,8 @@ describe "Profile access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
describe "GET /profile/history" do
subject { history_profile_path }
describe "GET /profile/audit_log" do
subject { audit_log_profile_path }
it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
......
require 'spec_helper'
feature 'Users' do
feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
fill_in 'user_name', with: 'Name Surname'
......
......@@ -137,7 +137,7 @@ describe GitlabMarkdownHelper do
describe 'random_markdown_tip' do
it 'returns a random Markdown tip' do
stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip'])
expect(random_markdown_tip).to eq 'Tip: Random tip'
expect(random_markdown_tip).to eq 'Random tip'
end
end
end
......@@ -8,4 +8,48 @@ describe ProjectsHelper do
expect(project_status_css_class("finished")).to eq("success")
end
end
describe "can_change_visibility_level?" do
let(:project) { create(:project) }
let(:fork_project) do
fork_project = create(:forked_project_with_submodules)
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
fork_project
end
let(:user) { create(:user) }
it "returns false if there are no approipriate permissions" do
allow(helper).to receive(:can?) { false }
expect(helper.can_change_visibility_level?(project, user)).to be_falsey
end
it "returns true if there are permissions and it is not fork" do
allow(helper).to receive(:can?) { true }
expect(helper.can_change_visibility_level?(project, user)).to be_truthy
end
context "forks" do
it "returns false if there are permissions and origin project is PRIVATE" do
allow(helper).to receive(:can?) { true }
project.update visibility_level: Gitlab::VisibilityLevel::PRIVATE
expect(helper.can_change_visibility_level?(fork_project, user)).to be_falsey
end
it "returns true if there are permissions and origin project is INTERNAL" do
allow(helper).to receive(:can?) { true }
project.update visibility_level: Gitlab::VisibilityLevel::INTERNAL
expect(helper.can_change_visibility_level?(fork_project, user)).to be_truthy
end
end
end
end
......@@ -72,4 +72,43 @@ describe VisibilityLevelHelper do
end
end
end
describe "skip_level?" do
describe "forks" do
let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
let(:fork_project) { create(:forked_project_with_submodules) }
before do
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
end
it "skips levels" do
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
describe "non-forked project" do
let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
it "skips levels" do
expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
expect(skip_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(skip_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
describe "Snippet" do
let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
it "skips levels" do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
expect(skip_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
end
end
......@@ -41,4 +41,53 @@ describe Issue, "Mentionable" do
issue.create_cross_references!(project, author, [commit2])
end
end
describe '#create_new_cross_references!' do
let(:project) { create(:project) }
let(:issues) { create_list(:issue, 2, project: project) }
context 'before changes are persisted' do
it 'ignores pre-existing references' do
issue = create_issue(description: issues[0].to_reference)
expect(SystemNoteService).not_to receive(:cross_reference)
issue.description = 'New description'
issue.create_new_cross_references!
end
it 'notifies new references' do
issue = create_issue(description: issues[0].to_reference)
expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
issue.description = issues[1].to_reference
issue.create_new_cross_references!
end
end
context 'after changes are persisted' do
it 'ignores pre-existing references' do
issue = create_issue(description: issues[0].to_reference)
expect(SystemNoteService).not_to receive(:cross_reference)
issue.update_attributes(description: 'New description')
issue.create_new_cross_references!
end
it 'notifies new references' do
issue = create_issue(description: issues[0].to_reference)
expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
issue.update_attributes(description: issues[1].to_reference)
issue.create_new_cross_references!
end
end
def create_issue(description:)
create(:issue, project: project, description: description)
end
end
end
......@@ -63,7 +63,7 @@ describe Key do
key = build(:key)
# Not always the middle, but close enough
key.key = key.key[0..100] + ' ' + key.key[100..-1]
key.key = key.key[0..100] + ' ' + key.key[101..-1]
expect(key).not_to be_valid
end
......@@ -71,6 +71,12 @@ describe Key do
it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
it 'rejects the multiple line key' do
key = build(:key)
key.key.gsub!(' ', "\n")
expect(key).not_to be_valid
end
end
context 'callbacks' do
......
......@@ -38,22 +38,6 @@ describe IrkerService do
let(:_recipients) { nil }
it { should validate_presence_of :recipients }
end
context 'too many recipients' do
let(:_recipients) { 'a b c d' }
it 'should add an error if there is too many recipients' do
subject.send :check_recipients_count
expect(subject.errors).not_to be_blank
end
end
context '3 recipients' do
let(:_recipients) { 'a b c' }
it 'should not add an error if there is 3 recipients' do
subject.send :check_recipients_count
expect(subject.errors).to be_blank
end
end
end
describe 'Execute' do
......@@ -62,7 +46,7 @@ describe IrkerService do
let(:project) { create(:project) }
let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
let(:recipients) { '#commits' }
let(:recipients) { '#commits irc://test.net/#test ftp://bad' }
let(:colorize_messages) { '1' }
before do
......@@ -71,17 +55,12 @@ describe IrkerService do
project: project,
project_id: project.id,
service_hook: true,
properties: {
'recipients' => recipients,
'colorize_messages' => colorize_messages
}
)
irker.settings = {
server_ip: 'localhost',
server_host: 'localhost',
server_port: 6659,
max_channels: 3,
default_irc_uri: 'irc://chat.freenode.net/'
}
default_irc_uri: 'irc://chat.freenode.net/',
recipients: recipients,
colorize_messages: colorize_messages)
irker.valid?
@irker_server = TCPServer.new 'localhost', 6659
end
......@@ -97,11 +76,8 @@ describe IrkerService do
conn.readlines.each do |line|
msg = JSON.load(line.chomp("\n"))
expect(msg.keys).to match_array(['to', 'privmsg'])
if msg['to'].is_a?(String)
expect(msg['to']).to eq 'irc://chat.freenode.net/#commits'
else
expect(msg['to']).to match_array(['irc://chat.freenode.net/#commits'])
end
expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits",
"irc://test.net/#test"])
end
conn.close
end
......
......@@ -47,4 +47,28 @@ describe Repository do
it { is_expected.to be_falsey }
end
end
describe "search_files" do
let(:results) { repository.search_files('feature', 'master') }
subject { results }
it { is_expected.to be_an Array }
describe 'result' do
subject { results.first }
it { is_expected.to be_an String }
it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") }
end
describe 'parsing result' do
subject { repository.parse_search_result(results.first) }
it { is_expected.to be_an OpenStruct }
it { expect(subject.filename).to eq('CHANGELOG') }
it { expect(subject.ref).to eq('master') }
it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
end
end
end
......@@ -230,6 +230,24 @@ describe User do
end
end
describe '#disable_two_factor!' do
it 'clears all 2FA-related fields' do
user = create(:user, :two_factor)
expect(user).to be_two_factor_enabled
expect(user.encrypted_otp_secret).not_to be_nil
expect(user.otp_backup_codes).not_to be_nil
user.disable_two_factor!
expect(user).not_to be_two_factor_enabled
expect(user.encrypted_otp_secret).to be_nil
expect(user.encrypted_otp_secret_iv).to be_nil
expect(user.encrypted_otp_secret_salt).to be_nil
expect(user.otp_backup_codes).to be_nil
end
end
describe 'projects' do
before do
@user = create :user
......
......@@ -220,9 +220,7 @@ describe API::API, api: true do
context 'when a visibility level is restricted' do
before do
@project = attributes_for(:project, { public: true })
allow_any_instance_of(ApplicationSetting).to(
receive(:restricted_visibility_levels).and_return([20])
)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it 'should not allow a non-admin to use a restricted visibility level' do
......
require 'spec_helper'
describe API::API, 'Settings', api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:admin) { create(:admin) }
describe "GET /application/settings" do
it "should return application settings" do
get api("/application/settings", admin)
expect(response.status).to eq(200)
expect(json_response).to be_an Hash
expect(json_response['default_projects_limit']).to eq(42)
expect(json_response['signin_enabled']).to be_truthy
end
end
describe "PUT /application/settings" do
it "should update application settings" do
put api("/application/settings", admin),
default_projects_limit: 3, signin_enabled: false
expect(response.status).to eq(200)
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['signin_enabled']).to be_falsey
end
end
end
......@@ -539,4 +539,55 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
describe 'PUT /user/:id/block' do
before { admin }
it 'should block existing user' do
put api("/users/#{user.id}/block", admin)
expect(response.status).to eq(200)
expect(user.reload.state).to eq('blocked')
end
it 'should not be available for non admin users' do
put api("/users/#{user.id}/block", user)
expect(response.status).to eq(403)
expect(user.reload.state).to eq('active')
end
it 'should return a 404 error if user id not found' do
put api('/users/9999/block', admin)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 User Not Found')
end
end
describe 'PUT /user/:id/unblock' do
before { admin }
it 'should unblock existing user' do
put api("/users/#{user.id}/unblock", admin)
expect(response.status).to eq(200)
expect(user.reload.state).to eq('active')
end
it 'should unblock a blocked user' do
put api("/users/#{user.id}/block", admin)
expect(response.status).to eq(200)
expect(user.reload.state).to eq('blocked')
put api("/users/#{user.id}/unblock", admin)
expect(response.status).to eq(200)
expect(user.reload.state).to eq('active')
end
it 'should not be available for non admin users' do
put api("/users/#{user.id}/unblock", user)
expect(response.status).to eq(403)
expect(user.reload.state).to eq('active')
end
it 'should return a 404 error if user id not found' do
put api('/users/9999/block', admin)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 User Not Found')
end
end
end
......@@ -108,8 +108,8 @@ describe ProfilesController, "routing" do
expect(get("/profile/account")).to route_to('profiles/accounts#show')
end
it "to #history" do
expect(get("/profile/history")).to route_to('profiles#history')
it "to #audit_log" do
expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
end
it "to #reset_private_token" do
......
......@@ -14,11 +14,7 @@ describe CreateSnippetService do
context 'When public visibility is restricted' do
before do
allow_any_instance_of(ApplicationSetting).to(
receive(:restricted_visibility_levels).and_return(
[Gitlab::VisibilityLevel::PUBLIC]
)
)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
......
......@@ -124,9 +124,7 @@ describe GitPushService do
end
it "when pushing a branch for the first time with default branch protection disabled" do
allow(ApplicationSetting.current_application_settings).
to receive(:default_branch_protection).
and_return(Gitlab::Access::PROTECTION_NONE)
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
......@@ -135,9 +133,7 @@ describe GitPushService do
end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
allow(ApplicationSetting.current_application_settings).
to receive(:default_branch_protection).
and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
......
......@@ -58,9 +58,7 @@ describe Projects::CreateService do
context 'restricted visibility level' do
before do
allow_any_instance_of(ApplicationSetting).to(
receive(:restricted_visibility_levels).and_return([20])
)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@opts.merge!(
visibility_level: Gitlab::VisibilityLevel.options['Public']
......
......@@ -8,7 +8,7 @@ describe Projects::TransferService do
context 'namespace -> namespace' do
before do
group.add_owner(user)
@result = transfer_project(project, user, new_namespace_id: group.id)
@result = transfer_project(project, user, group)
end
it { expect(@result).to be_truthy }
......@@ -17,7 +17,7 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do
before do
@result = transfer_project(project, user, new_namespace_id: nil)
@result = transfer_project(project, user, nil)
end
it { expect(@result).to eq false }
......@@ -26,14 +26,14 @@ describe Projects::TransferService do
context 'namespace -> not allowed namespace' do
before do
@result = transfer_project(project, user, new_namespace_id: group.id)
@result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
end
def transfer_project(project, user, params)
Projects::TransferService.new(project, user, params).execute
def transfer_project(project, user, new_namespace)
Projects::TransferService.new(project, user).execute(new_namespace)
end
end
......@@ -47,9 +47,7 @@ describe Projects::UpdateService do
context 'respect configured visibility restrictions setting' do
before(:each) do
allow_any_instance_of(ApplicationSetting).to(
receive(:restricted_visibility_levels).and_return([20])
)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
context 'should be private when updated to private' do
......
......@@ -14,11 +14,7 @@ describe UpdateSnippetService do
context 'When public visibility is restricted' do
before do
allow_any_instance_of(ApplicationSetting).to(
receive(:restricted_visibility_levels).and_return(
[Gitlab::VisibilityLevel::PUBLIC]
)
)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@snippet = create_snippet(@project, @user, @opts)
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
......
if ENV['SIMPLECOV']
require 'simplecov'
SimpleCov.start :rails
end
if ENV['COVERALLS']
require 'coveralls'
Coveralls.wear_merged!
end
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
......
if ENV['SIMPLECOV']
require 'simplecov'
end
if ENV['COVERALLS']
require 'coveralls'
Coveralls.wear_merged!
end
......@@ -143,6 +143,6 @@ shared_examples 'an editable mentionable' do
end
set_mentionable_text.call(new_text)
subject.notice_added_references(project, author)
subject.create_new_cross_references!(project, author)
end
end
module StubConfiguration
def stub_application_setting(messages)
add_predicates(messages)
# Stubbing both of these because we're not yet consistent with how we access
# current application settings
allow_any_instance_of(ApplicationSetting).to receive_messages(messages)
allow(Gitlab::CurrentSettings.current_application_settings).
to receive_messages(messages)
end
......@@ -11,4 +16,25 @@ module StubConfiguration
def stub_gravatar_setting(messages)
allow(Gitlab.config.gravatar).to receive_messages(messages)
end
private
# Modifies stubbed messages to also stub possible predicate versions
#
# Examples:
#
# add_predicates(foo: true)
# # => {foo: true, foo?: true}
#
# add_predicates(signup_enabled?: false)
# # => {signup_enabled? false}
def add_predicates(messages)
# Only modify keys that aren't already predicates
keys = messages.keys.map(&:to_s).reject { |k| k.end_with?('?') }
keys.each do |key|
predicate = key + '?'
messages[predicate.to_sym] = messages[key.to_sym]
end
end
end
/* jquery.nicescroll 3.6.0 InuYaksa*2014 MIT http://nicescroll.areaaperta.com */(function(f){"function"===typeof define&&define.amd?define(["jquery"],f):f(jQuery)})(function(f){var y=!1,D=!1,N=0,O=2E3,x=0,H=["webkit","ms","moz","o"],s=window.requestAnimationFrame||!1,t=window.cancelAnimationFrame||!1;if(!s)for(var P in H){var E=H[P];s||(s=window[E+"RequestAnimationFrame"]);t||(t=window[E+"CancelAnimationFrame"]||window[E+"CancelRequestAnimationFrame"])}var v=window.MutationObserver||window.WebKitMutationObserver||!1,I={zindex:"auto",cursoropacitymin:0,cursoropacitymax:1,cursorcolor:"#424242",
cursorwidth:"5px",cursorborder:"1px solid #fff",cursorborderradius:"5px",scrollspeed:60,mousescrollstep:24,touchbehavior:!1,hwacceleration:!0,usetransition:!0,boxzoom:!1,dblclickzoom:!0,gesturezoom:!0,grabcursorenabled:!0,autohidemode:!0,background:"",iframeautoresize:!0,cursorminheight:32,preservenativescrolling:!0,railoffset:!1,railhoffset:!1,bouncescroll:!0,spacebarenabled:!0,railpadding:{top:0,right:0,left:0,bottom:0},disableoutline:!0,horizrailenabled:!0,railalign:"right",railvalign:"bottom",
enabletranslate3d:!0,enablemousewheel:!0,enablekeyboard:!0,smoothscroll:!0,sensitiverail:!0,enablemouselockapi:!0,cursorfixedheight:!1,directionlockdeadzone:6,hidecursordelay:400,nativeparentscrolling:!0,enablescrollonselection:!0,overflowx:!0,overflowy:!0,cursordragspeed:.3,rtlmode:"auto",cursordragontouch:!1,oneaxismousemode:"auto",scriptpath:function(){var f=document.getElementsByTagName("script"),f=f[f.length-1].src.split("?")[0];return 0<f.split("/").length?f.split("/").slice(0,-1).join("/")+
"/":""}(),preventmultitouchscrolling:!0},F=!1,Q=function(){if(F)return F;var f=document.createElement("DIV"),c=f.style,h=navigator.userAgent,m=navigator.platform,d={haspointerlock:"pointerLockElement"in document||"webkitPointerLockElement"in document||"mozPointerLockElement"in document};d.isopera="opera"in window;d.isopera12=d.isopera&&"getUserMedia"in navigator;d.isoperamini="[object OperaMini]"===Object.prototype.toString.call(window.operamini);d.isie="all"in document&&"attachEvent"in f&&!d.isopera;
d.isieold=d.isie&&!("msInterpolationMode"in c);d.isie7=d.isie&&!d.isieold&&(!("documentMode"in document)||7==document.documentMode);d.isie8=d.isie&&"documentMode"in document&&8==document.documentMode;d.isie9=d.isie&&"performance"in window&&9<=document.documentMode;d.isie10=d.isie&&"performance"in window&&10==document.documentMode;d.isie11="msRequestFullscreen"in f&&11<=document.documentMode;d.isie9mobile=/iemobile.9/i.test(h);d.isie9mobile&&(d.isie9=!1);d.isie7mobile=!d.isie9mobile&&d.isie7&&/iemobile/i.test(h);
d.ismozilla="MozAppearance"in c;d.iswebkit="WebkitAppearance"in c;d.ischrome="chrome"in window;d.ischrome22=d.ischrome&&d.haspointerlock;d.ischrome26=d.ischrome&&"transition"in c;d.cantouch="ontouchstart"in document.documentElement||"ontouchstart"in window;d.hasmstouch=window.MSPointerEvent||!1;d.hasw3ctouch=window.PointerEvent||!1;d.ismac=/^mac$/i.test(m);d.isios=d.cantouch&&/iphone|ipad|ipod/i.test(m);d.isios4=d.isios&&!("seal"in Object);d.isios7=d.isios&&"webkitHidden"in document;d.isandroid=/android/i.test(h);
d.haseventlistener="addEventListener"in f;d.trstyle=!1;d.hastransform=!1;d.hastranslate3d=!1;d.transitionstyle=!1;d.hastransition=!1;d.transitionend=!1;m=["transform","msTransform","webkitTransform","MozTransform","OTransform"];for(h=0;h<m.length;h++)if("undefined"!=typeof c[m[h]]){d.trstyle=m[h];break}d.hastransform=!!d.trstyle;d.hastransform&&(c[d.trstyle]="translate3d(1px,2px,3px)",d.hastranslate3d=/translate3d/.test(c[d.trstyle]));d.transitionstyle=!1;d.prefixstyle="";d.transitionend=!1;for(var m=
"transition webkitTransition msTransition MozTransition OTransition OTransition KhtmlTransition".split(" "),n=" -webkit- -ms- -moz- -o- -o -khtml-".split(" "),p="transitionend webkitTransitionEnd msTransitionEnd transitionend otransitionend oTransitionEnd KhtmlTransitionEnd".split(" "),h=0;h<m.length;h++)if(m[h]in c){d.transitionstyle=m[h];d.prefixstyle=n[h];d.transitionend=p[h];break}d.ischrome26&&(d.prefixstyle=n[1]);d.hastransition=d.transitionstyle;a:{h=["-webkit-grab","-moz-grab","grab"];if(d.ischrome&&
!d.ischrome22||d.isie)h=[];for(m=0;m<h.length;m++)if(n=h[m],c.cursor=n,c.cursor==n){c=n;break a}c="url(//mail.google.com/mail/images/2/openhand.cur),n-resize"}d.cursorgrabvalue=c;d.hasmousecapture="setCapture"in f;d.hasMutationObserver=!1!==v;return F=d},R=function(k,c){function h(){var b=a.doc.css(e.trstyle);return b&&"matrix"==b.substr(0,6)?b.replace(/^.*\((.*)\)$/g,"$1").replace(/px/g,"").split(/, +/):!1}function m(){var b=a.win;if("zIndex"in b)return b.zIndex();for(;0<b.length&&9!=b[0].nodeType;){var g=
b.css("zIndex");if(!isNaN(g)&&0!=g)return parseInt(g);b=b.parent()}return!1}function d(b,g,q){g=b.css(g);b=parseFloat(g);return isNaN(b)?(b=w[g]||0,q=3==b?q?a.win.outerHeight()-a.win.innerHeight():a.win.outerWidth()-a.win.innerWidth():1,a.isie8&&b&&(b+=1),q?b:0):b}function n(b,g,q,c){a._bind(b,g,function(a){a=a?a:window.event;var c={original:a,target:a.target||a.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==a.type?0:1,deltaX:0,deltaZ:0,preventDefault:function(){a.preventDefault?a.preventDefault():
a.returnValue=!1;return!1},stopImmediatePropagation:function(){a.stopImmediatePropagation?a.stopImmediatePropagation():a.cancelBubble=!0}};"mousewheel"==g?(c.deltaY=-.025*a.wheelDelta,a.wheelDeltaX&&(c.deltaX=-.025*a.wheelDeltaX)):c.deltaY=a.detail;return q.call(b,c)},c)}function p(b,g,c){var d,e;0==b.deltaMode?(d=-Math.floor(a.opt.mousescrollstep/54*b.deltaX),e=-Math.floor(a.opt.mousescrollstep/54*b.deltaY)):1==b.deltaMode&&(d=-Math.floor(b.deltaX*a.opt.mousescrollstep),e=-Math.floor(b.deltaY*a.opt.mousescrollstep));
g&&a.opt.oneaxismousemode&&0==d&&e&&(d=e,e=0,c&&(0>d?a.getScrollLeft()>=a.page.maxw:0>=a.getScrollLeft())&&(e=d,d=0));d&&(a.scrollmom&&a.scrollmom.stop(),a.lastdeltax+=d,a.debounced("mousewheelx",function(){var b=a.lastdeltax;a.lastdeltax=0;a.rail.drag||a.doScrollLeftBy(b)},15));if(e){if(a.opt.nativeparentscrolling&&c&&!a.ispage&&!a.zoomactive)if(0>e){if(a.getScrollTop()>=a.page.maxh)return!0}else if(0>=a.getScrollTop())return!0;a.scrollmom&&a.scrollmom.stop();a.lastdeltay+=e;a.debounced("mousewheely",
function(){var b=a.lastdeltay;a.lastdeltay=0;a.rail.drag||a.doScrollBy(b)},15)}b.stopImmediatePropagation();return b.preventDefault()}var a=this;this.version="3.6.0";this.name="nicescroll";this.me=c;this.opt={doc:f("body"),win:!1};f.extend(this.opt,I);this.opt.snapbackspeed=80;if(k)for(var G in a.opt)"undefined"!=typeof k[G]&&(a.opt[G]=k[G]);this.iddoc=(this.doc=a.opt.doc)&&this.doc[0]?this.doc[0].id||"":"";this.ispage=/^BODY|HTML/.test(a.opt.win?a.opt.win[0].nodeName:this.doc[0].nodeName);this.haswrapper=
!1!==a.opt.win;this.win=a.opt.win||(this.ispage?f(window):this.doc);this.docscroll=this.ispage&&!this.haswrapper?f(window):this.win;this.body=f("body");this.iframe=this.isfixed=this.viewport=!1;this.isiframe="IFRAME"==this.doc[0].nodeName&&"IFRAME"==this.win[0].nodeName;this.istextarea="TEXTAREA"==this.win[0].nodeName;this.forcescreen=!1;this.canshowonmouseevent="scroll"!=a.opt.autohidemode;this.page=this.view=this.onzoomout=this.onzoomin=this.onscrollcancel=this.onscrollend=this.onscrollstart=this.onclick=
this.ongesturezoom=this.onkeypress=this.onmousewheel=this.onmousemove=this.onmouseup=this.onmousedown=!1;this.scroll={x:0,y:0};this.scrollratio={x:0,y:0};this.cursorheight=20;this.scrollvaluemax=0;this.isrtlmode="auto"==this.opt.rtlmode?"rtl"==(this.win[0]==window?this.body:this.win).css("direction"):!0===this.opt.rtlmode;this.observerbody=this.observerremover=this.observer=this.scrollmom=this.scrollrunning=!1;do this.id="ascrail"+O++;while(document.getElementById(this.id));this.hasmousefocus=this.hasfocus=
this.zoomactive=this.zoom=this.selectiondrag=this.cursorfreezed=this.cursor=this.rail=!1;this.visibility=!0;this.hidden=this.locked=this.railslocked=!1;this.cursoractive=!0;this.wheelprevented=!1;this.overflowx=a.opt.overflowx;this.overflowy=a.opt.overflowy;this.nativescrollingarea=!1;this.checkarea=0;this.events=[];this.saved={};this.delaylist={};this.synclist={};this.lastdeltay=this.lastdeltax=0;this.detected=Q();var e=f.extend({},this.detected);this.ishwscroll=(this.canhwscroll=e.hastransform&&
a.opt.hwacceleration)&&a.haswrapper;this.hasreversehr=this.isrtlmode&&!e.iswebkit;this.istouchcapable=!1;!e.cantouch||e.isios||e.isandroid||!e.iswebkit&&!e.ismozilla||(this.istouchcapable=!0,e.cantouch=!1);a.opt.enablemouselockapi||(e.hasmousecapture=!1,e.haspointerlock=!1);this.debounced=function(b,g,c){var d=a.delaylist[b];a.delaylist[b]=g;d||setTimeout(function(){var g=a.delaylist[b];a.delaylist[b]=!1;g.call(a)},c)};var r=!1;this.synched=function(b,g){a.synclist[b]=g;(function(){r||(s(function(){r=
!1;for(var b in a.synclist){var g=a.synclist[b];g&&g.call(a);a.synclist[b]=!1}}),r=!0)})();return b};this.unsynched=function(b){a.synclist[b]&&(a.synclist[b]=!1)};this.css=function(b,g){for(var c in g)a.saved.css.push([b,c,b.css(c)]),b.css(c,g[c])};this.scrollTop=function(b){return"undefined"==typeof b?a.getScrollTop():a.setScrollTop(b)};this.scrollLeft=function(b){return"undefined"==typeof b?a.getScrollLeft():a.setScrollLeft(b)};var A=function(a,g,c,d,e,f,h){this.st=a;this.ed=g;this.spd=c;this.p1=
d||0;this.p2=e||1;this.p3=f||0;this.p4=h||1;this.ts=(new Date).getTime();this.df=this.ed-this.st};A.prototype={B2:function(a){return 3*a*a*(1-a)},B3:function(a){return 3*a*(1-a)*(1-a)},B4:function(a){return(1-a)*(1-a)*(1-a)},getNow:function(){var a=1-((new Date).getTime()-this.ts)/this.spd,g=this.B2(a)+this.B3(a)+this.B4(a);return 0>a?this.ed:this.st+Math.round(this.df*g)},update:function(a,g){this.st=this.getNow();this.ed=a;this.spd=g;this.ts=(new Date).getTime();this.df=this.ed-this.st;return this}};
if(this.ishwscroll){this.doc.translate={x:0,y:0,tx:"0px",ty:"0px"};e.hastranslate3d&&e.isios&&this.doc.css("-webkit-backface-visibility","hidden");this.getScrollTop=function(b){if(!b){if(b=h())return 16==b.length?-b[13]:-b[5];if(a.timerscroll&&a.timerscroll.bz)return a.timerscroll.bz.getNow()}return a.doc.translate.y};this.getScrollLeft=function(b){if(!b){if(b=h())return 16==b.length?-b[12]:-b[4];if(a.timerscroll&&a.timerscroll.bh)return a.timerscroll.bh.getNow()}return a.doc.translate.x};this.notifyScrollEvent=
function(a){var g=document.createEvent("UIEvents");g.initUIEvent("scroll",!1,!0,window,1);g.niceevent=!0;a.dispatchEvent(g)};var K=this.isrtlmode?1:-1;e.hastranslate3d&&a.opt.enabletranslate3d?(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate3d("+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate3d("+
a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])}):(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])})}else this.getScrollTop=
function(){return a.docscroll.scrollTop()},this.setScrollTop=function(b){return a.docscroll.scrollTop(b)},this.getScrollLeft=function(){return a.detected.ismozilla&&a.isrtlmode?Math.abs(a.docscroll.scrollLeft()):a.docscroll.scrollLeft()},this.setScrollLeft=function(b){return a.docscroll.scrollLeft(a.detected.ismozilla&&a.isrtlmode?-b:b)};this.getTarget=function(a){return a?a.target?a.target:a.srcElement?a.srcElement:!1:!1};this.hasParent=function(a,g){if(!a)return!1;for(var c=a.target||a.srcElement||
a||!1;c&&c.id!=g;)c=c.parentNode||!1;return!1!==c};var w={thin:1,medium:3,thick:5};this.getDocumentScrollOffset=function(){return{top:window.pageYOffset||document.documentElement.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft}};this.getOffset=function(){if(a.isfixed){var b=a.win.offset(),g=a.getDocumentScrollOffset();b.top-=g.top;b.left-=g.left;return b}b=a.win.offset();if(!a.viewport)return b;g=a.viewport.offset();return{top:b.top-g.top,left:b.left-g.left}};this.updateScrollBar=
function(b){if(a.ishwscroll)a.rail.css({height:a.win.innerHeight()-(a.opt.railpadding.top+a.opt.railpadding.bottom)}),a.railh&&a.railh.css({width:a.win.innerWidth()-(a.opt.railpadding.left+a.opt.railpadding.right)});else{var g=a.getOffset(),c=g.top,e=g.left-(a.opt.railpadding.left+a.opt.railpadding.right),c=c+d(a.win,"border-top-width",!0),e=e+(a.rail.align?a.win.outerWidth()-d(a.win,"border-right-width")-a.rail.width:d(a.win,"border-left-width")),f=a.opt.railoffset;f&&(f.top&&(c+=f.top),a.rail.align&&
f.left&&(e+=f.left));a.railslocked||a.rail.css({top:c,left:e,height:(b?b.h:a.win.innerHeight())-(a.opt.railpadding.top+a.opt.railpadding.bottom)});a.zoom&&a.zoom.css({top:c+1,left:1==a.rail.align?e-20:e+a.rail.width+4});if(a.railh&&!a.railslocked){c=g.top;e=g.left;if(f=a.opt.railhoffset)f.top&&(c+=f.top),f.left&&(e+=f.left);b=a.railh.align?c+d(a.win,"border-top-width",!0)+a.win.innerHeight()-a.railh.height:c+d(a.win,"border-top-width",!0);e+=d(a.win,"border-left-width");a.railh.css({top:b-(a.opt.railpadding.top+
a.opt.railpadding.bottom),left:e,width:a.railh.width})}}};this.doRailClick=function(b,g,c){var e;a.railslocked||(a.cancelEvent(b),g?(g=c?a.doScrollLeft:a.doScrollTop,e=c?(b.pageX-a.railh.offset().left-a.cursorwidth/2)*a.scrollratio.x:(b.pageY-a.rail.offset().top-a.cursorheight/2)*a.scrollratio.y,g(e)):(g=c?a.doScrollLeftBy:a.doScrollBy,e=c?a.scroll.x:a.scroll.y,b=c?b.pageX-a.railh.offset().left:b.pageY-a.rail.offset().top,c=c?a.view.w:a.view.h,g(e>=b?c:-c)))};a.hasanimationframe=s;a.hascancelanimationframe=
t;a.hasanimationframe?a.hascancelanimationframe||(t=function(){a.cancelAnimationFrame=!0}):(s=function(a){return setTimeout(a,15-Math.floor(+new Date/1E3)%16)},t=clearInterval);this.init=function(){a.saved.css=[];if(e.isie7mobile||e.isoperamini)return!0;e.hasmstouch&&a.css(a.ispage?f("html"):a.win,{"-ms-touch-action":"none"});a.zindex="auto";a.zindex=a.ispage||"auto"!=a.opt.zindex?a.opt.zindex:m()||"auto";!a.ispage&&"auto"!=a.zindex&&a.zindex>x&&(x=a.zindex);a.isie&&0==a.zindex&&"auto"==a.opt.zindex&&
(a.zindex="auto");if(!a.ispage||!e.cantouch&&!e.isieold&&!e.isie9mobile){var b=a.docscroll;a.ispage&&(b=a.haswrapper?a.win:a.doc);e.isie9mobile||a.css(b,{"overflow-y":"hidden"});a.ispage&&e.isie7&&("BODY"==a.doc[0].nodeName?a.css(f("html"),{"overflow-y":"hidden"}):"HTML"==a.doc[0].nodeName&&a.css(f("body"),{"overflow-y":"hidden"}));!e.isios||a.ispage||a.haswrapper||a.css(f("body"),{"-webkit-overflow-scrolling":"touch"});var g=f(document.createElement("div"));g.css({position:"relative",top:0,"float":"right",
width:a.opt.cursorwidth,height:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius});g.hborder=parseFloat(g.outerHeight()-g.innerHeight());g.addClass("nicescroll-cursors");a.cursor=g;var c=f(document.createElement("div"));c.attr("id",a.id);c.addClass("nicescroll-rails nicescroll-rails-vr");var d,h,k=["left","right",
"top","bottom"],J;for(J in k)h=k[J],(d=a.opt.railpadding[h])?c.css("padding-"+h,d+"px"):a.opt.railpadding[h]=0;c.append(g);c.width=Math.max(parseFloat(a.opt.cursorwidth),g.outerWidth());c.css({width:c.width+"px",zIndex:a.zindex,background:a.opt.background,cursor:"default"});c.visibility=!0;c.scrollable=!0;c.align="left"==a.opt.railalign?0:1;a.rail=c;g=a.rail.drag=!1;!a.opt.boxzoom||a.ispage||e.isieold||(g=document.createElement("div"),a.bind(g,"click",a.doZoom),a.bind(g,"mouseenter",function(){a.zoom.css("opacity",
a.opt.cursoropacitymax)}),a.bind(g,"mouseleave",function(){a.zoom.css("opacity",a.opt.cursoropacitymin)}),a.zoom=f(g),a.zoom.css({cursor:"pointer","z-index":a.zindex,backgroundImage:"url("+a.opt.scriptpath+"zoomico.png)",height:18,width:18,backgroundPosition:"0px 0px"}),a.opt.dblclickzoom&&a.bind(a.win,"dblclick",a.doZoom),e.cantouch&&a.opt.gesturezoom&&(a.ongesturezoom=function(b){1.5<b.scale&&a.doZoomIn(b);.8>b.scale&&a.doZoomOut(b);return a.cancelEvent(b)},a.bind(a.win,"gestureend",a.ongesturezoom)));
a.railh=!1;var l;a.opt.horizrailenabled&&(a.css(b,{"overflow-x":"hidden"}),g=f(document.createElement("div")),g.css({position:"absolute",top:0,height:a.opt.cursorwidth,width:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius}),e.isieold&&g.css({overflow:"hidden"}),g.wborder=parseFloat(g.outerWidth()-g.innerWidth()),
g.addClass("nicescroll-cursors"),a.cursorh=g,l=f(document.createElement("div")),l.attr("id",a.id+"-hr"),l.addClass("nicescroll-rails nicescroll-rails-hr"),l.height=Math.max(parseFloat(a.opt.cursorwidth),g.outerHeight()),l.css({height:l.height+"px",zIndex:a.zindex,background:a.opt.background}),l.append(g),l.visibility=!0,l.scrollable=!0,l.align="top"==a.opt.railvalign?0:1,a.railh=l,a.railh.drag=!1);a.ispage?(c.css({position:"fixed",top:"0px",height:"100%"}),c.align?c.css({right:"0px"}):c.css({left:"0px"}),
a.body.append(c),a.railh&&(l.css({position:"fixed",left:"0px",width:"100%"}),l.align?l.css({bottom:"0px"}):l.css({top:"0px"}),a.body.append(l))):(a.ishwscroll?("static"==a.win.css("position")&&a.css(a.win,{position:"relative"}),b="HTML"==a.win[0].nodeName?a.body:a.win,f(b).scrollTop(0).scrollLeft(0),a.zoom&&(a.zoom.css({position:"absolute",top:1,right:0,"margin-right":c.width+4}),b.append(a.zoom)),c.css({position:"absolute",top:0}),c.align?c.css({right:0}):c.css({left:0}),b.append(c),l&&(l.css({position:"absolute",
left:0,bottom:0}),l.align?l.css({bottom:0}):l.css({top:0}),b.append(l))):(a.isfixed="fixed"==a.win.css("position"),b=a.isfixed?"fixed":"absolute",a.isfixed||(a.viewport=a.getViewport(a.win[0])),a.viewport&&(a.body=a.viewport,0==/fixed|absolute/.test(a.viewport.css("position"))&&a.css(a.viewport,{position:"relative"})),c.css({position:b}),a.zoom&&a.zoom.css({position:b}),a.updateScrollBar(),a.body.append(c),a.zoom&&a.body.append(a.zoom),a.railh&&(l.css({position:b}),a.body.append(l))),e.isios&&a.css(a.win,
{"-webkit-tap-highlight-color":"rgba(0,0,0,0)","-webkit-touch-callout":"none"}),e.isie&&a.opt.disableoutline&&a.win.attr("hideFocus","true"),e.iswebkit&&a.opt.disableoutline&&a.win.css({outline:"none"}));!1===a.opt.autohidemode?(a.autohidedom=!1,a.rail.css({opacity:a.opt.cursoropacitymax}),a.railh&&a.railh.css({opacity:a.opt.cursoropacitymax})):!0===a.opt.autohidemode||"leave"===a.opt.autohidemode?(a.autohidedom=f().add(a.rail),e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursor)),a.railh&&(a.autohidedom=
a.autohidedom.add(a.railh)),a.railh&&e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"scroll"==a.opt.autohidemode?(a.autohidedom=f().add(a.rail),a.railh&&(a.autohidedom=a.autohidedom.add(a.railh))):"cursor"==a.opt.autohidemode?(a.autohidedom=f().add(a.cursor),a.railh&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"hidden"==a.opt.autohidemode&&(a.autohidedom=!1,a.hide(),a.railslocked=!1);if(e.isie9mobile)a.scrollmom=new L(a),a.onmangotouch=function(){var b=a.getScrollTop(),c=a.getScrollLeft();
if(b==a.scrollmom.lastscrolly&&c==a.scrollmom.lastscrollx)return!0;var g=b-a.mangotouch.sy,e=c-a.mangotouch.sx;if(0!=Math.round(Math.sqrt(Math.pow(e,2)+Math.pow(g,2)))){var d=0>g?-1:1,f=0>e?-1:1,q=+new Date;a.mangotouch.lazy&&clearTimeout(a.mangotouch.lazy);80<q-a.mangotouch.tm||a.mangotouch.dry!=d||a.mangotouch.drx!=f?(a.scrollmom.stop(),a.scrollmom.reset(c,b),a.mangotouch.sy=b,a.mangotouch.ly=b,a.mangotouch.sx=c,a.mangotouch.lx=c,a.mangotouch.dry=d,a.mangotouch.drx=f,a.mangotouch.tm=q):(a.scrollmom.stop(),
a.scrollmom.update(a.mangotouch.sx-e,a.mangotouch.sy-g),a.mangotouch.tm=q,g=Math.max(Math.abs(a.mangotouch.ly-b),Math.abs(a.mangotouch.lx-c)),a.mangotouch.ly=b,a.mangotouch.lx=c,2<g&&(a.mangotouch.lazy=setTimeout(function(){a.mangotouch.lazy=!1;a.mangotouch.dry=0;a.mangotouch.drx=0;a.mangotouch.tm=0;a.scrollmom.doMomentum(30)},100)))}},c=a.getScrollTop(),l=a.getScrollLeft(),a.mangotouch={sy:c,ly:c,dry:0,sx:l,lx:l,drx:0,lazy:!1,tm:0},a.bind(a.docscroll,"scroll",a.onmangotouch);else{if(e.cantouch||
a.istouchcapable||a.opt.touchbehavior||e.hasmstouch){a.scrollmom=new L(a);a.ontouchstart=function(b){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;a.hasmoving=!1;if(!a.railslocked){var c;if(e.hasmstouch)for(c=b.target?b.target:!1;c;){var g=f(c).getNiceScroll();if(0<g.length&&g[0].me==a.me)break;if(0<g.length)return!1;if("DIV"==c.nodeName&&c.id==a.id)break;c=c.parentNode?c.parentNode:!1}a.cancelScroll();if((c=a.getTarget(b))&&/INPUT/i.test(c.nodeName)&&/range/i.test(c.type))return a.stopPropagation(b);
!("clientX"in b)&&"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);a.forcescreen&&(g=b,b={original:b.original?b.original:b},b.clientX=g.screenX,b.clientY=g.screenY);a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,st:a.getScrollTop(),sl:a.getScrollLeft(),pt:2,dl:!1};if(a.ispage||!a.opt.directionlockdeadzone)a.rail.drag.dl="f";else{var g=f(window).width(),d=f(window).height(),q=Math.max(document.body.scrollWidth,document.documentElement.scrollWidth),
h=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight),d=Math.max(0,h-d),g=Math.max(0,q-g);a.rail.drag.ck=!a.rail.scrollable&&a.railh.scrollable?0<d?"v":!1:a.rail.scrollable&&!a.railh.scrollable?0<g?"h":!1:!1;a.rail.drag.ck||(a.rail.drag.dl="f")}a.opt.touchbehavior&&a.isiframe&&e.isie&&(g=a.win.position(),a.rail.drag.x+=g.left,a.rail.drag.y+=g.top);a.hasmoving=!1;a.lastmouseup=!1;a.scrollmom.reset(b.clientX,b.clientY);if(!e.cantouch&&!this.istouchcapable&&!b.pointerType){if(!c||
!/INPUT|SELECT|TEXTAREA/i.test(c.nodeName))return!a.ispage&&e.hasmousecapture&&c.setCapture(),a.opt.touchbehavior?(c.onclick&&!c._onclick&&(c._onclick=c.onclick,c.onclick=function(b){if(a.hasmoving)return!1;c._onclick.call(this,b)}),a.cancelEvent(b)):a.stopPropagation(b);/SUBMIT|CANCEL|BUTTON/i.test(f(c).attr("type"))&&(pc={tg:c,click:!1},a.preventclick=pc)}}};a.ontouchend=function(b){if(!a.rail.drag)return!0;if(2==a.rail.drag.pt){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;
a.scrollmom.doMomentum();a.rail.drag=!1;if(a.hasmoving&&(a.lastmouseup=!0,a.hideCursor(),e.hasmousecapture&&document.releaseCapture(),!e.cantouch))return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmouseup(b)};var n=a.opt.touchbehavior&&a.isiframe&&!e.hasmousecapture;a.ontouchmove=function(b,c){if(!a.rail.drag||b.targetTouches&&a.opt.preventmultitouchscrolling&&1<b.targetTouches.length||b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;if(2==a.rail.drag.pt){if(e.cantouch&&
e.isios&&"undefined"==typeof b.original)return!0;a.hasmoving=!0;a.preventclick&&!a.preventclick.click&&(a.preventclick.click=a.preventclick.tg.onclick||!1,a.preventclick.tg.onclick=a.onpreventclick);b=f.extend({original:b},b);"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);if(a.forcescreen){var g=b;b={original:b.original?b.original:b};b.clientX=g.screenX;b.clientY=g.screenY}var d,g=d=0;n&&!c&&(d=a.win.position(),g=-d.left,d=-d.top);var q=b.clientY+
d;d=q-a.rail.drag.y;var h=b.clientX+g,u=h-a.rail.drag.x,k=a.rail.drag.st-d;a.ishwscroll&&a.opt.bouncescroll?0>k?k=Math.round(k/2):k>a.page.maxh&&(k=a.page.maxh+Math.round((k-a.page.maxh)/2)):(0>k&&(q=k=0),k>a.page.maxh&&(k=a.page.maxh,q=0));var l;a.railh&&a.railh.scrollable&&(l=a.isrtlmode?u-a.rail.drag.sl:a.rail.drag.sl-u,a.ishwscroll&&a.opt.bouncescroll?0>l?l=Math.round(l/2):l>a.page.maxw&&(l=a.page.maxw+Math.round((l-a.page.maxw)/2)):(0>l&&(h=l=0),l>a.page.maxw&&(l=a.page.maxw,h=0)));g=!1;if(a.rail.drag.dl)g=
!0,"v"==a.rail.drag.dl?l=a.rail.drag.sl:"h"==a.rail.drag.dl&&(k=a.rail.drag.st);else{d=Math.abs(d);var u=Math.abs(u),z=a.opt.directionlockdeadzone;if("v"==a.rail.drag.ck){if(d>z&&u<=.3*d)return a.rail.drag=!1,!0;u>z&&(a.rail.drag.dl="f",f("body").scrollTop(f("body").scrollTop()))}else if("h"==a.rail.drag.ck){if(u>z&&d<=.3*u)return a.rail.drag=!1,!0;d>z&&(a.rail.drag.dl="f",f("body").scrollLeft(f("body").scrollLeft()))}}a.synched("touchmove",function(){a.rail.drag&&2==a.rail.drag.pt&&(a.prepareTransition&&
a.prepareTransition(0),a.rail.scrollable&&a.setScrollTop(k),a.scrollmom.update(h,q),a.railh&&a.railh.scrollable?(a.setScrollLeft(l),a.showCursor(k,l)):a.showCursor(k),e.isie10&&document.selection.clear())});e.ischrome&&a.istouchcapable&&(g=!1);if(g)return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmousemove(b)}}a.onmousedown=function(b,c){if(!a.rail.drag||1==a.rail.drag.pt){if(a.railslocked)return a.cancelEvent(b);a.cancelScroll();a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,
pt:1,hr:!!c};var g=a.getTarget(b);!a.ispage&&e.hasmousecapture&&g.setCapture();a.isiframe&&!e.hasmousecapture&&(a.saved.csspointerevents=a.doc.css("pointer-events"),a.css(a.doc,{"pointer-events":"none"}));a.hasmoving=!1;return a.cancelEvent(b)}};a.onmouseup=function(b){if(a.rail.drag){if(1!=a.rail.drag.pt)return!0;e.hasmousecapture&&document.releaseCapture();a.isiframe&&!e.hasmousecapture&&a.doc.css("pointer-events",a.saved.csspointerevents);a.rail.drag=!1;a.hasmoving&&a.triggerScrollEnd();return a.cancelEvent(b)}};
a.onmousemove=function(b){if(a.rail.drag&&1==a.rail.drag.pt){if(e.ischrome&&0==b.which)return a.onmouseup(b);a.cursorfreezed=!0;a.hasmoving=!0;if(a.rail.drag.hr){a.scroll.x=a.rail.drag.sx+(b.clientX-a.rail.drag.x);0>a.scroll.x&&(a.scroll.x=0);var c=a.scrollvaluemaxw;a.scroll.x>c&&(a.scroll.x=c)}else a.scroll.y=a.rail.drag.sy+(b.clientY-a.rail.drag.y),0>a.scroll.y&&(a.scroll.y=0),c=a.scrollvaluemax,a.scroll.y>c&&(a.scroll.y=c);a.synched("mousemove",function(){a.rail.drag&&1==a.rail.drag.pt&&(a.showCursor(),
a.rail.drag.hr?a.hasreversehr?a.doScrollLeft(a.scrollvaluemaxw-Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollLeft(Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollTop(Math.round(a.scroll.y*a.scrollratio.y),a.opt.cursordragspeed))});return a.cancelEvent(b)}};if(e.cantouch||a.opt.touchbehavior)a.onpreventclick=function(b){if(a.preventclick)return a.preventclick.tg.onclick=a.preventclick.click,a.preventclick=!1,a.cancelEvent(b)},a.bind(a.win,"mousedown",
a.ontouchstart),a.onclick=e.isios?!1:function(b){return a.lastmouseup?(a.lastmouseup=!1,a.cancelEvent(b)):!0},a.opt.grabcursorenabled&&e.cursorgrabvalue&&(a.css(a.ispage?a.doc:a.win,{cursor:e.cursorgrabvalue}),a.css(a.rail,{cursor:e.cursorgrabvalue}));else{var p=function(b){if(a.selectiondrag){if(b){var c=a.win.outerHeight();b=b.pageY-a.selectiondrag.top;0<b&&b<c&&(b=0);b>=c&&(b-=c);a.selectiondrag.df=b}0!=a.selectiondrag.df&&(a.doScrollBy(2*-Math.floor(a.selectiondrag.df/6)),a.debounced("doselectionscroll",
function(){p()},50))}};a.hasTextSelected="getSelection"in document?function(){return 0<document.getSelection().rangeCount}:"selection"in document?function(){return"None"!=document.selection.type}:function(){return!1};a.onselectionstart=function(b){a.ispage||(a.selectiondrag=a.win.offset())};a.onselectionend=function(b){a.selectiondrag=!1};a.onselectiondrag=function(b){a.selectiondrag&&a.hasTextSelected()&&a.debounced("selectionscroll",function(){p(b)},250)}}e.hasw3ctouch?(a.css(a.rail,{"touch-action":"none"}),
a.css(a.cursor,{"touch-action":"none"}),a.bind(a.win,"pointerdown",a.ontouchstart),a.bind(document,"pointerup",a.ontouchend),a.bind(document,"pointermove",a.ontouchmove)):e.hasmstouch?(a.css(a.rail,{"-ms-touch-action":"none"}),a.css(a.cursor,{"-ms-touch-action":"none"}),a.bind(a.win,"MSPointerDown",a.ontouchstart),a.bind(document,"MSPointerUp",a.ontouchend),a.bind(document,"MSPointerMove",a.ontouchmove),a.bind(a.cursor,"MSGestureHold",function(a){a.preventDefault()}),a.bind(a.cursor,"contextmenu",
function(a){a.preventDefault()})):this.istouchcapable&&(a.bind(a.win,"touchstart",a.ontouchstart),a.bind(document,"touchend",a.ontouchend),a.bind(document,"touchcancel",a.ontouchend),a.bind(document,"touchmove",a.ontouchmove));if(a.opt.cursordragontouch||!e.cantouch&&!a.opt.touchbehavior)a.rail.css({cursor:"default"}),a.railh&&a.railh.css({cursor:"default"}),a.jqbind(a.rail,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),
a.jqbind(a.rail,"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.rail,"click",function(b){a.doRailClick(b,!1,!1)}),a.bind(a.rail,"dblclick",function(b){a.doRailClick(b,!0,!1)}),a.bind(a.cursor,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursor,"dblclick",function(b){a.cancelEvent(b)})),a.railh&&(a.jqbind(a.railh,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.railh,
"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.railh,"click",function(b){a.doRailClick(b,!1,!0)}),a.bind(a.railh,"dblclick",function(b){a.doRailClick(b,!0,!0)}),a.bind(a.cursorh,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursorh,"dblclick",function(b){a.cancelEvent(b)})));e.cantouch||a.opt.touchbehavior?(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.ontouchend),a.bind(document,"mousemove",a.ontouchmove),a.onclick&&a.bind(document,"click",
a.onclick),a.opt.cursordragontouch&&(a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.cursorh&&a.bind(a.cursorh,"mousedown",function(b){a.onmousedown(b,!0)}),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onmouseup))):(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.onmouseup),a.bind(document,"mousemove",a.onmousemove),a.onclick&&a.bind(document,"click",a.onclick),a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.railh&&(a.bind(a.cursorh,
"mousedown",function(b){a.onmousedown(b,!0)}),a.bind(a.cursorh,"mouseup",a.onmouseup)),!a.ispage&&a.opt.enablescrollonselection&&(a.bind(a.win[0],"mousedown",a.onselectionstart),a.bind(document,"mouseup",a.onselectionend),a.bind(a.cursor,"mouseup",a.onselectionend),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onselectionend),a.bind(document,"mousemove",a.onselectiondrag)),a.zoom&&(a.jqbind(a.zoom,"mouseenter",function(){a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.zoom,"mouseleave",
function(){a.rail.active=!1;a.rail.drag||a.hideCursor()})));a.opt.enablemousewheel&&(a.isiframe||a.bind(e.isie&&a.ispage?document:a.win,"mousewheel",a.onmousewheel),a.bind(a.rail,"mousewheel",a.onmousewheel),a.railh&&a.bind(a.railh,"mousewheel",a.onmousewheelhr));a.ispage||e.cantouch||/HTML|^BODY/.test(a.win[0].nodeName)||(a.win.attr("tabindex")||a.win.attr({tabindex:N++}),a.jqbind(a.win,"focus",function(b){y=a.getTarget(b).id||!0;a.hasfocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,
"blur",function(b){y=!1;a.hasfocus=!1}),a.jqbind(a.win,"mouseenter",function(b){D=a.getTarget(b).id||!0;a.hasmousefocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,"mouseleave",function(){D=!1;a.hasmousefocus=!1;a.rail.drag||a.hideCursor()}))}a.onkeypress=function(b){if(a.railslocked&&0==a.page.maxh)return!0;b=b?b:window.e;var c=a.getTarget(b);if(c&&/INPUT|TEXTAREA|SELECT|OPTION/.test(c.nodeName)&&(!c.getAttribute("type")&&!c.type||!/submit|button|cancel/i.tp)||f(c).attr("contenteditable"))return!0;
if(a.hasfocus||a.hasmousefocus&&!y||a.ispage&&!y&&!D){c=b.keyCode;if(a.railslocked&&27!=c)return a.cancelEvent(b);var g=b.ctrlKey||!1,d=b.shiftKey||!1,e=!1;switch(c){case 38:case 63233:a.doScrollBy(72);e=!0;break;case 40:case 63235:a.doScrollBy(-72);e=!0;break;case 37:case 63232:a.railh&&(g?a.doScrollLeft(0):a.doScrollLeftBy(72),e=!0);break;case 39:case 63234:a.railh&&(g?a.doScrollLeft(a.page.maxw):a.doScrollLeftBy(-72),e=!0);break;case 33:case 63276:a.doScrollBy(a.view.h);e=!0;break;case 34:case 63277:a.doScrollBy(-a.view.h);
e=!0;break;case 36:case 63273:a.railh&&g?a.doScrollPos(0,0):a.doScrollTo(0);e=!0;break;case 35:case 63275:a.railh&&g?a.doScrollPos(a.page.maxw,a.page.maxh):a.doScrollTo(a.page.maxh);e=!0;break;case 32:a.opt.spacebarenabled&&(d?a.doScrollBy(a.view.h):a.doScrollBy(-a.view.h),e=!0);break;case 27:a.zoomactive&&(a.doZoom(),e=!0)}if(e)return a.cancelEvent(b)}};a.opt.enablekeyboard&&a.bind(document,e.isopera&&!e.isopera12?"keypress":"keydown",a.onkeypress);a.bind(document,"keydown",function(b){b.ctrlKey&&
(a.wheelprevented=!0)});a.bind(document,"keyup",function(b){b.ctrlKey||(a.wheelprevented=!1)});a.bind(window,"blur",function(b){a.wheelprevented=!1});a.bind(window,"resize",a.lazyResize);a.bind(window,"orientationchange",a.lazyResize);a.bind(window,"load",a.lazyResize);if(e.ischrome&&!a.ispage&&!a.haswrapper){var r=a.win.attr("style"),c=parseFloat(a.win.css("width"))+1;a.win.css("width",c);a.synched("chromefix",function(){a.win.attr("style",r)})}a.onAttributeChange=function(b){a.lazyResize(a.isieold?
250:30)};!1!==v&&(a.observerbody=new v(function(b){b.forEach(function(b){if("attributes"==b.type)return f("body").hasClass("modal-open")?a.hide():a.show()});if(document.body.scrollHeight!=a.page.maxh)return a.lazyResize(30)}),a.observerbody.observe(document.body,{childList:!0,subtree:!0,characterData:!1,attributes:!0,attributeFilter:["class"]}));a.ispage||a.haswrapper||(!1!==v?(a.observer=new v(function(b){b.forEach(a.onAttributeChange)}),a.observer.observe(a.win[0],{childList:!0,characterData:!1,
attributes:!0,subtree:!1}),a.observerremover=new v(function(b){b.forEach(function(b){if(0<b.removedNodes.length)for(var c in b.removedNodes)if(a&&b.removedNodes[c]==a.win[0])return a.remove()})}),a.observerremover.observe(a.win[0].parentNode,{childList:!0,characterData:!1,attributes:!1,subtree:!1})):(a.bind(a.win,e.isie&&!e.isie9?"propertychange":"DOMAttrModified",a.onAttributeChange),e.isie9&&a.win[0].attachEvent("onpropertychange",a.onAttributeChange),a.bind(a.win,"DOMNodeRemoved",function(b){b.target==
a.win[0]&&a.remove()})));!a.ispage&&a.opt.boxzoom&&a.bind(window,"resize",a.resizeZoom);a.istextarea&&a.bind(a.win,"mouseup",a.lazyResize);a.lazyResize(30)}if("IFRAME"==this.doc[0].nodeName){var M=function(){a.iframexd=!1;var b;try{b="contentDocument"in this?this.contentDocument:this.contentWindow.document}catch(c){a.iframexd=!0,b=!1}if(a.iframexd)return"console"in window&&console.log("NiceScroll error: policy restriced iframe"),!0;a.forcescreen=!0;a.isiframe&&(a.iframe={doc:f(b),html:a.doc.contents().find("html")[0],
body:a.doc.contents().find("body")[0]},a.getContentSize=function(){return{w:Math.max(a.iframe.html.scrollWidth,a.iframe.body.scrollWidth),h:Math.max(a.iframe.html.scrollHeight,a.iframe.body.scrollHeight)}},a.docscroll=f(a.iframe.body));if(!e.isios&&a.opt.iframeautoresize&&!a.isiframe){a.win.scrollTop(0);a.doc.height("");var g=Math.max(b.getElementsByTagName("html")[0].scrollHeight,b.body.scrollHeight);a.doc.height(g)}a.lazyResize(30);e.isie7&&a.css(f(a.iframe.html),{"overflow-y":"hidden"});a.css(f(a.iframe.body),
{"overflow-y":"hidden"});e.isios&&a.haswrapper&&a.css(f(b.body),{"-webkit-transform":"translate3d(0,0,0)"});"contentWindow"in this?a.bind(this.contentWindow,"scroll",a.onscroll):a.bind(b,"scroll",a.onscroll);a.opt.enablemousewheel&&a.bind(b,"mousewheel",a.onmousewheel);a.opt.enablekeyboard&&a.bind(b,e.isopera?"keypress":"keydown",a.onkeypress);if(e.cantouch||a.opt.touchbehavior)a.bind(b,"mousedown",a.ontouchstart),a.bind(b,"mousemove",function(b){return a.ontouchmove(b,!0)}),a.opt.grabcursorenabled&&
e.cursorgrabvalue&&a.css(f(b.body),{cursor:e.cursorgrabvalue});a.bind(b,"mouseup",a.ontouchend);a.zoom&&(a.opt.dblclickzoom&&a.bind(b,"dblclick",a.doZoom),a.ongesturezoom&&a.bind(b,"gestureend",a.ongesturezoom))};this.doc[0].readyState&&"complete"==this.doc[0].readyState&&setTimeout(function(){M.call(a.doc[0],!1)},500);a.bind(this.doc,"load",M)}};this.showCursor=function(b,c){a.cursortimeout&&(clearTimeout(a.cursortimeout),a.cursortimeout=0);if(a.rail){a.autohidedom&&(a.autohidedom.stop().css({opacity:a.opt.cursoropacitymax}),
a.cursoractive=!0);a.rail.drag&&1==a.rail.drag.pt||("undefined"!=typeof b&&!1!==b&&(a.scroll.y=Math.round(1*b/a.scrollratio.y)),"undefined"!=typeof c&&(a.scroll.x=Math.round(1*c/a.scrollratio.x)));a.cursor.css({height:a.cursorheight,top:a.scroll.y});if(a.cursorh){var d=a.hasreversehr?a.scrollvaluemaxw-a.scroll.x:a.scroll.x;!a.rail.align&&a.rail.visibility?a.cursorh.css({width:a.cursorwidth,left:d+a.rail.width}):a.cursorh.css({width:a.cursorwidth,left:d});a.cursoractive=!0}a.zoom&&a.zoom.stop().css({opacity:a.opt.cursoropacitymax})}};
this.hideCursor=function(b){a.cursortimeout||!a.rail||!a.autohidedom||a.hasmousefocus&&"leave"==a.opt.autohidemode||(a.cursortimeout=setTimeout(function(){a.rail.active&&a.showonmouseevent||(a.autohidedom.stop().animate({opacity:a.opt.cursoropacitymin}),a.zoom&&a.zoom.stop().animate({opacity:a.opt.cursoropacitymin}),a.cursoractive=!1);a.cursortimeout=0},b||a.opt.hidecursordelay))};this.noticeCursor=function(b,c,d){a.showCursor(c,d);a.rail.active||a.hideCursor(b)};this.getContentSize=a.ispage?function(){return{w:Math.max(document.body.scrollWidth,
document.documentElement.scrollWidth),h:Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}}:a.haswrapper?function(){return{w:a.doc.outerWidth()+parseInt(a.win.css("paddingLeft"))+parseInt(a.win.css("paddingRight")),h:a.doc.outerHeight()+parseInt(a.win.css("paddingTop"))+parseInt(a.win.css("paddingBottom"))}}:function(){return{w:a.docscroll[0].scrollWidth,h:a.docscroll[0].scrollHeight}};this.onResize=function(b,c){if(!a||!a.win)return!1;if(!a.haswrapper&&!a.ispage){if("none"==
a.win.css("display"))return a.visibility&&a.hideRail().hideRailHr(),!1;a.hidden||a.visibility||a.showRail().showRailHr()}var d=a.page.maxh,e=a.page.maxw,f=a.view.h,h=a.view.w;a.view={w:a.ispage?a.win.width():parseInt(a.win[0].clientWidth),h:a.ispage?a.win.height():parseInt(a.win[0].clientHeight)};a.page=c?c:a.getContentSize();a.page.maxh=Math.max(0,a.page.h-a.view.h);a.page.maxw=Math.max(0,a.page.w-a.view.w);if(a.page.maxh==d&&a.page.maxw==e&&a.view.w==h&&a.view.h==f){if(a.ispage)return a;d=a.win.offset();
if(a.lastposition&&(e=a.lastposition,e.top==d.top&&e.left==d.left))return a;a.lastposition=d}0==a.page.maxh?(a.hideRail(),a.scrollvaluemax=0,a.scroll.y=0,a.scrollratio.y=0,a.cursorheight=0,a.setScrollTop(0),a.rail.scrollable=!1):(a.page.maxh-=a.opt.railpadding.top+a.opt.railpadding.bottom,a.rail.scrollable=!0);0==a.page.maxw?(a.hideRailHr(),a.scrollvaluemaxw=0,a.scroll.x=0,a.scrollratio.x=0,a.cursorwidth=0,a.setScrollLeft(0),a.railh.scrollable=!1):(a.page.maxw-=a.opt.railpadding.left+a.opt.railpadding.right,
a.railh.scrollable=!0);a.railslocked=a.locked||0==a.page.maxh&&0==a.page.maxw;if(a.railslocked)return a.ispage||a.updateScrollBar(a.view),!1;a.hidden||a.visibility?a.hidden||a.railh.visibility||a.showRailHr():a.showRail().showRailHr();a.istextarea&&a.win.css("resize")&&"none"!=a.win.css("resize")&&(a.view.h-=20);a.cursorheight=Math.min(a.view.h,Math.round(a.view.h/a.page.h*a.view.h));a.cursorheight=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorheight);a.cursorwidth=
Math.min(a.view.w,Math.round(a.view.w/a.page.w*a.view.w));a.cursorwidth=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorwidth);a.scrollvaluemax=a.view.h-a.cursorheight-a.cursor.hborder-(a.opt.railpadding.top+a.opt.railpadding.bottom);a.railh&&(a.railh.width=0<a.page.maxh?a.view.w-a.rail.width:a.view.w,a.scrollvaluemaxw=a.railh.width-a.cursorwidth-a.cursorh.wborder-(a.opt.railpadding.left+a.opt.railpadding.right));a.ispage||a.updateScrollBar(a.view);a.scrollratio=
{x:a.page.maxw/a.scrollvaluemaxw,y:a.page.maxh/a.scrollvaluemax};a.getScrollTop()>a.page.maxh?a.doScrollTop(a.page.maxh):(a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y)),a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)),a.cursoractive&&a.noticeCursor());a.scroll.y&&0==a.getScrollTop()&&a.doScrollTo(Math.floor(a.scroll.y*a.scrollratio.y));return a};this.resize=a.onResize;this.lazyResize=function(b){b=isNaN(b)?30:b;a.debounced("resize",a.resize,b);return a};this.jqbind=function(b,
c,d){a.events.push({e:b,n:c,f:d,q:!0});f(b).bind(c,d)};this.bind=function(b,c,d,f){var h="jquery"in b?b[0]:b;"mousewheel"==c?window.addEventListener||"onwheel"in document?a._bind(h,"wheel",d,f||!1):(b="undefined"!=typeof document.onmousewheel?"mousewheel":"DOMMouseScroll",n(h,b,d,f||!1),"DOMMouseScroll"==b&&n(h,"MozMousePixelScroll",d,f||!1)):h.addEventListener?(e.cantouch&&/mouseup|mousedown|mousemove/.test(c)&&a._bind(h,"mousedown"==c?"touchstart":"mouseup"==c?"touchend":"touchmove",function(a){if(a.touches){if(2>
a.touches.length){var b=a.touches.length?a.touches[0]:a;b.original=a;d.call(this,b)}}else a.changedTouches&&(b=a.changedTouches[0],b.original=a,d.call(this,b))},f||!1),a._bind(h,c,d,f||!1),e.cantouch&&"mouseup"==c&&a._bind(h,"touchcancel",d,f||!1)):a._bind(h,c,function(b){(b=b||window.event||!1)&&b.srcElement&&(b.target=b.srcElement);"pageY"in b||(b.pageX=b.clientX+document.documentElement.scrollLeft,b.pageY=b.clientY+document.documentElement.scrollTop);return!1===d.call(h,b)||!1===f?a.cancelEvent(b):
!0})};e.haseventlistener?(this._bind=function(b,c,d,e){a.events.push({e:b,n:c,f:d,b:e,q:!1});b.addEventListener(c,d,e||!1)},this.cancelEvent=function(a){if(!a)return!1;a=a.original?a.original:a;a.preventDefault();a.stopPropagation();a.preventManipulation&&a.preventManipulation();return!1},this.stopPropagation=function(a){if(!a)return!1;a=a.original?a.original:a;a.stopPropagation();return!1},this._unbind=function(a,c,d,e){a.removeEventListener(c,d,e)}):(this._bind=function(b,c,d,e){a.events.push({e:b,
n:c,f:d,b:e,q:!1});b.attachEvent?b.attachEvent("on"+c,d):b["on"+c]=d},this.cancelEvent=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;a.cancel=!0;return a.returnValue=!1},this.stopPropagation=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;return!1},this._unbind=function(a,c,d,e){a.detachEvent?a.detachEvent("on"+c,d):a["on"+c]=!1});this.unbindAll=function(){for(var b=0;b<a.events.length;b++){var c=a.events[b];c.q?c.e.unbind(c.n,c.f):a._unbind(c.e,c.n,c.f,c.b)}};this.showRail=
function(){0==a.page.maxh||!a.ispage&&"none"==a.win.css("display")||(a.visibility=!0,a.rail.visibility=!0,a.rail.css("display","block"));return a};this.showRailHr=function(){if(!a.railh)return a;0==a.page.maxw||!a.ispage&&"none"==a.win.css("display")||(a.railh.visibility=!0,a.railh.css("display","block"));return a};this.hideRail=function(){a.visibility=!1;a.rail.visibility=!1;a.rail.css("display","none");return a};this.hideRailHr=function(){if(!a.railh)return a;a.railh.visibility=!1;a.railh.css("display",
"none");return a};this.show=function(){a.hidden=!1;a.railslocked=!1;return a.showRail().showRailHr()};this.hide=function(){a.hidden=!0;a.railslocked=!0;return a.hideRail().hideRailHr()};this.toggle=function(){return a.hidden?a.show():a.hide()};this.remove=function(){a.stop();a.cursortimeout&&clearTimeout(a.cursortimeout);a.doZoomOut();a.unbindAll();e.isie9&&a.win[0].detachEvent("onpropertychange",a.onAttributeChange);!1!==a.observer&&a.observer.disconnect();!1!==a.observerremover&&a.observerremover.disconnect();
!1!==a.observerbody&&a.observerbody.disconnect();a.events=null;a.cursor&&a.cursor.remove();a.cursorh&&a.cursorh.remove();a.rail&&a.rail.remove();a.railh&&a.railh.remove();a.zoom&&a.zoom.remove();for(var b=0;b<a.saved.css.length;b++){var c=a.saved.css[b];c[0].css(c[1],"undefined"==typeof c[2]?"":c[2])}a.saved=!1;a.me.data("__nicescroll","");var d=f.nicescroll;d.each(function(b){if(this&&this.id===a.id){delete d[b];for(var c=++b;c<d.length;c++,b++)d[b]=d[c];d.length--;d.length&&delete d[d.length]}});
for(var h in a)a[h]=null,delete a[h];a=null};this.scrollstart=function(b){this.onscrollstart=b;return a};this.scrollend=function(b){this.onscrollend=b;return a};this.scrollcancel=function(b){this.onscrollcancel=b;return a};this.zoomin=function(b){this.onzoomin=b;return a};this.zoomout=function(b){this.onzoomout=b;return a};this.isScrollable=function(a){a=a.target?a.target:a;if("OPTION"==a.nodeName)return!0;for(;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a),c=c.css("overflowY")||c.css("overflowX")||
c.css("overflow")||"";if(/scroll|auto/.test(c))return a.clientHeight!=a.scrollHeight;a=a.parentNode?a.parentNode:!1}return!1};this.getViewport=function(a){for(a=a&&a.parentNode?a.parentNode:!1;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a);if(/fixed|absolute/.test(c.css("position")))return c;var d=c.css("overflowY")||c.css("overflowX")||c.css("overflow")||"";if(/scroll|auto/.test(d)&&a.clientHeight!=a.scrollHeight||0<c.getNiceScroll().length)return c;a=a.parentNode?a.parentNode:!1}return!1};
this.triggerScrollEnd=function(){if(a.onscrollend){var b=a.getScrollLeft(),c=a.getScrollTop();a.onscrollend.call(a,{type:"scrollend",current:{x:b,y:c},end:{x:b,y:c}})}};this.onmousewheel=function(b){if(!a.wheelprevented){if(a.railslocked)return a.debounced("checkunlock",a.resize,250),!0;if(a.rail.drag)return a.cancelEvent(b);"auto"==a.opt.oneaxismousemode&&0!=b.deltaX&&(a.opt.oneaxismousemode=!1);if(a.opt.oneaxismousemode&&0==b.deltaX&&!a.rail.scrollable)return a.railh&&a.railh.scrollable?a.onmousewheelhr(b):
!0;var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;if(a.nativescrollingarea)return!0;if(b=p(b,!1,d))a.checkarea=0;return b}};this.onmousewheelhr=function(b){if(!a.wheelprevented){if(a.railslocked||!a.railh.scrollable)return!0;if(a.rail.drag)return a.cancelEvent(b);var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;return a.nativescrollingarea?
!0:a.railslocked?a.cancelEvent(b):p(b,!0,d)}};this.stop=function(){a.cancelScroll();a.scrollmon&&a.scrollmon.stop();a.cursorfreezed=!1;a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.noticeCursor();return a};this.getTransitionSpeed=function(b){var c=Math.round(10*a.opt.scrollspeed);b=Math.min(c,Math.round(b/20*a.opt.scrollspeed));return 20<b?b:0};a.opt.smoothscroll?a.ishwscroll&&e.hastransition&&a.opt.usetransition&&a.opt.smoothscroll?(this.prepareTransition=function(b,c){var d=c?20<
b?b:0:a.getTransitionSpeed(b),f=d?e.prefixstyle+"transform "+d+"ms ease-out":"";a.lasttransitionstyle&&a.lasttransitionstyle==f||(a.lasttransitionstyle=f,a.doc.css(e.transitionstyle,f));return d},this.doScrollLeft=function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-
f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();0==a.opt.bouncescroll&&(0>c?c=0:c>a.page.maxh&&(c=a.page.maxh),0>b?b=0:b>a.page.maxw&&(b=a.page.maxw));if(a.scrollrunning&&b==a.newscrollx&&c==a.newscrolly)return!1;a.newscrolly=c;a.newscrollx=b;a.newscrollspeed=d||!1;if(a.timer)return!1;a.timer=setTimeout(function(){var d=a.getScrollTop(),f=a.getScrollLeft(),h,k;h=b-f;k=c-d;h=Math.round(Math.sqrt(Math.pow(h,2)+Math.pow(k,2)));h=a.newscrollspeed&&1<a.newscrollspeed?a.newscrollspeed:a.getTransitionSpeed(h);
a.newscrollspeed&&1>=a.newscrollspeed&&(h*=a.newscrollspeed);a.prepareTransition(h,!0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);0<h&&(!a.scrollrunning&&a.onscrollstart&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:f,y:d},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:h}),e.transitionend?a.scrollendtrapped||(a.scrollendtrapped=!0,a.bind(a.doc,e.transitionend,a.onScrollTransitionEnd,!1)):(a.scrollendtrapped&&clearTimeout(a.scrollendtrapped),a.scrollendtrapped=
setTimeout(a.onScrollTransitionEnd,h)),a.timerscroll={bz:new A(d,a.newscrolly,h,0,0,.58,1),bh:new A(f,a.newscrollx,h,0,0,.58,1)},a.cursorfreezed||(a.timerscroll.tm=setInterval(function(){a.showCursor(a.getScrollTop(),a.getScrollLeft())},60)));a.synched("doScroll-set",function(){a.timer=0;a.scrollendtrapped&&(a.scrollrunning=!0);a.setScrollTop(a.newscrolly);a.setScrollLeft(a.newscrollx);if(!a.scrollendtrapped)a.onScrollTransitionEnd()})},50)},this.cancelScroll=function(){if(!a.scrollendtrapped)return!0;
var b=a.getScrollTop(),c=a.getScrollLeft();a.scrollrunning=!1;e.transitionend||clearTimeout(e.transitionend);a.scrollendtrapped=!1;a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);a.prepareTransition(0);a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;a.cursorfreezed=!1;a.showCursor(b,c);return a},this.onScrollTransitionEnd=function(){a.scrollendtrapped&&a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);
a.scrollendtrapped=!1;a.prepareTransition(0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;var b=a.getScrollTop(),c=a.getScrollLeft();a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.noticeCursor(!1,b,c);a.cursorfreezed=!1;0>b?b=0:b>a.page.maxh&&(b=a.page.maxh);0>c?c=0:c>a.page.maxw&&(c=a.page.maxw);if(b!=a.newscrolly||c!=a.newscrollx)return a.doScrollPos(c,b,a.opt.snapbackspeed);a.onscrollend&&a.scrollrunning&&a.triggerScrollEnd();a.scrollrunning=!1}):(this.doScrollLeft=
function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){function e(){if(a.cancelAnimationFrame)return!0;a.scrollrunning=!0;if(n=1-n)return a.timer=s(e)||1;var b=0,c,d,g=d=a.getScrollTop();if(a.dst.ay){g=a.bzscroll?a.dst.py+a.bzscroll.getNow()*a.dst.ay:a.newscrolly;c=g-d;if(0>c&&g<a.newscrolly||0<c&&g>a.newscrolly)g=a.newscrolly;
a.setScrollTop(g);g==a.newscrolly&&(b=1)}else b=1;d=c=a.getScrollLeft();if(a.dst.ax){d=a.bzscroll?a.dst.px+a.bzscroll.getNow()*a.dst.ax:a.newscrollx;c=d-c;if(0>c&&d<a.newscrollx||0<c&&d>a.newscrollx)d=a.newscrollx;a.setScrollLeft(d);d==a.newscrollx&&(b+=1)}else b+=1;2==b?(a.timer=0,a.cursorfreezed=!1,a.bzscroll=!1,a.scrollrunning=!1,0>g?g=0:g>a.page.maxh&&(g=a.page.maxh),0>d?d=0:d>a.page.maxw&&(d=a.page.maxw),d!=a.newscrollx||g!=a.newscrolly?a.doScrollPos(d,g):a.onscrollend&&a.triggerScrollEnd()):
a.timer=s(e)||1}c="undefined"==typeof c||!1===c?a.getScrollTop(!0):c;if(a.timer&&a.newscrolly==c&&a.newscrollx==b)return!0;a.timer&&t(a.timer);a.timer=0;var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();a.newscrolly=c;a.newscrollx=b;a.bouncescroll&&a.rail.visibility||(0>a.newscrolly?a.newscrolly=0:a.newscrolly>a.page.maxh&&(a.newscrolly=a.page.maxh));a.bouncescroll&&a.railh.visibility||(0>a.newscrollx?a.newscrollx=0:a.newscrollx>a.page.maxw&&
(a.newscrollx=a.page.maxw));a.dst={};a.dst.x=b-h;a.dst.y=c-f;a.dst.px=h;a.dst.py=f;var k=Math.round(Math.sqrt(Math.pow(a.dst.x,2)+Math.pow(a.dst.y,2)));a.dst.ax=a.dst.x/k;a.dst.ay=a.dst.y/k;var l=0,m=k;0==a.dst.x?(l=f,m=c,a.dst.ay=1,a.dst.py=0):0==a.dst.y&&(l=h,m=b,a.dst.ax=1,a.dst.px=0);k=a.getTransitionSpeed(k);d&&1>=d&&(k*=d);a.bzscroll=0<k?a.bzscroll?a.bzscroll.update(m,k):new A(l,m,k,0,1,0,1):!1;if(!a.timer){(f==a.page.maxh&&c>=a.page.maxh||h==a.page.maxw&&b>=a.page.maxw)&&a.checkContentSize();
var n=1;a.cancelAnimationFrame=!1;a.timer=1;a.onscrollstart&&!a.scrollrunning&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:h,y:f},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:k});e();(f==a.page.maxh&&c>=f||h==a.page.maxw&&b>=h)&&a.checkContentSize();a.noticeCursor()}},this.cancelScroll=function(){a.timer&&t(a.timer);a.timer=0;a.bzscroll=!1;a.scrollrunning=!1;return a}):(this.doScrollLeft=function(b,c){var d=a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,
c){var d=a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var e=b>a.page.maxw?a.page.maxw:b;0>e&&(e=0);var f=c>a.page.maxh?a.page.maxh:c;0>f&&(f=0);a.synched("scroll",function(){a.setScrollTop(f);a.setScrollLeft(e)})},this.cancelScroll=function(){});this.doScrollBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.y-b)*a.scrollratio.y):(a.timer?a.newscrolly:a.getScrollTop(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.h/2);d<-e?d=-e:d>a.page.maxh+e&&(d=a.page.maxh+e)}a.cursorfreezed=
!1;e=a.getScrollTop(!0);if(0>d&&0>=e)return a.noticeCursor();if(d>a.page.maxh&&e>=a.page.maxh)return a.checkContentSize(),a.noticeCursor();a.doScrollTop(d)};this.doScrollLeftBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.x-b)*a.scrollratio.x):(a.timer?a.newscrollx:a.getScrollLeft(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.w/2);d<-e?d=-e:d>a.page.maxw+e&&(d=a.page.maxw+e)}a.cursorfreezed=!1;e=a.getScrollLeft(!0);if(0>d&&0>=e||d>a.page.maxw&&e>=a.page.maxw)return a.noticeCursor();a.doScrollLeft(d)};
this.doScrollTo=function(b,c){c&&Math.round(b*a.scrollratio.y);a.cursorfreezed=!1;a.doScrollTop(b)};this.checkContentSize=function(){var b=a.getContentSize();b.h==a.page.h&&b.w==a.page.w||a.resize(!1,b)};a.onscroll=function(b){a.rail.drag||a.cursorfreezed||a.synched("scroll",function(){a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.railh&&(a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)));a.noticeCursor()})};a.bind(a.docscroll,"scroll",a.onscroll);this.doZoomIn=function(b){if(!a.zoomactive){a.zoomactive=
!0;a.zoomrestore={style:{}};var c="position top left zIndex backgroundColor marginTop marginBottom marginLeft marginRight".split(" "),d=a.win[0].style,h;for(h in c){var k=c[h];a.zoomrestore.style[k]="undefined"!=typeof d[k]?d[k]:""}a.zoomrestore.style.width=a.win.css("width");a.zoomrestore.style.height=a.win.css("height");a.zoomrestore.padding={w:a.win.outerWidth()-a.win.width(),h:a.win.outerHeight()-a.win.height()};e.isios4&&(a.zoomrestore.scrollTop=f(window).scrollTop(),f(window).scrollTop(0));
a.win.css({position:e.isios4?"absolute":"fixed",top:0,left:0,"z-index":x+100,margin:"0px"});c=a.win.css("backgroundColor");(""==c||/transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(c))&&a.win.css("backgroundColor","#fff");a.rail.css({"z-index":x+101});a.zoom.css({"z-index":x+102});a.zoom.css("backgroundPosition","0px -18px");a.resizeZoom();a.onzoomin&&a.onzoomin.call(a);return a.cancelEvent(b)}};this.doZoomOut=function(b){if(a.zoomactive)return a.zoomactive=!1,a.win.css("margin",""),a.win.css(a.zoomrestore.style),
e.isios4&&f(window).scrollTop(a.zoomrestore.scrollTop),a.rail.css({"z-index":a.zindex}),a.zoom.css({"z-index":a.zindex}),a.zoomrestore=!1,a.zoom.css("backgroundPosition","0px 0px"),a.onResize(),a.onzoomout&&a.onzoomout.call(a),a.cancelEvent(b)};this.doZoom=function(b){return a.zoomactive?a.doZoomOut(b):a.doZoomIn(b)};this.resizeZoom=function(){if(a.zoomactive){var b=a.getScrollTop();a.win.css({width:f(window).width()-a.zoomrestore.padding.w+"px",height:f(window).height()-a.zoomrestore.padding.h+"px"});
a.onResize();a.setScrollTop(Math.min(a.page.maxh,b))}};this.init();f.nicescroll.push(this)},L=function(f){var c=this;this.nc=f;this.steptime=this.lasttime=this.speedy=this.speedx=this.lasty=this.lastx=0;this.snapy=this.snapx=!1;this.demuly=this.demulx=0;this.lastscrolly=this.lastscrollx=-1;this.timer=this.chky=this.chkx=0;this.time=function(){return+new Date};this.reset=function(f,k){c.stop();var d=c.time();c.steptime=0;c.lasttime=d;c.speedx=0;c.speedy=0;c.lastx=f;c.lasty=k;c.lastscrollx=-1;c.lastscrolly=
-1};this.update=function(f,k){var d=c.time();c.steptime=d-c.lasttime;c.lasttime=d;var d=k-c.lasty,n=f-c.lastx,p=c.nc.getScrollTop(),a=c.nc.getScrollLeft(),p=p+d,a=a+n;c.snapx=0>a||a>c.nc.page.maxw;c.snapy=0>p||p>c.nc.page.maxh;c.speedx=n;c.speedy=d;c.lastx=f;c.lasty=k};this.stop=function(){c.nc.unsynched("domomentum2d");c.timer&&clearTimeout(c.timer);c.timer=0;c.lastscrollx=-1;c.lastscrolly=-1};this.doSnapy=function(f,k){var d=!1;0>k?(k=0,d=!0):k>c.nc.page.maxh&&(k=c.nc.page.maxh,d=!0);0>f?(f=0,d=
!0):f>c.nc.page.maxw&&(f=c.nc.page.maxw,d=!0);d?c.nc.doScrollPos(f,k,c.nc.opt.snapbackspeed):c.nc.triggerScrollEnd()};this.doMomentum=function(f){var k=c.time(),d=f?k+f:c.lasttime;f=c.nc.getScrollLeft();var n=c.nc.getScrollTop(),p=c.nc.page.maxh,a=c.nc.page.maxw;c.speedx=0<a?Math.min(60,c.speedx):0;c.speedy=0<p?Math.min(60,c.speedy):0;d=d&&60>=k-d;if(0>n||n>p||0>f||f>a)d=!1;f=c.speedx&&d?c.speedx:!1;if(c.speedy&&d&&c.speedy||f){var s=Math.max(16,c.steptime);50<s&&(f=s/50,c.speedx*=f,c.speedy*=f,s=
50);c.demulxy=0;c.lastscrollx=c.nc.getScrollLeft();c.chkx=c.lastscrollx;c.lastscrolly=c.nc.getScrollTop();c.chky=c.lastscrolly;var e=c.lastscrollx,r=c.lastscrolly,t=function(){var d=600<c.time()-k?.04:.02;c.speedx&&(e=Math.floor(c.lastscrollx-c.speedx*(1-c.demulxy)),c.lastscrollx=e,0>e||e>a)&&(d=.1);c.speedy&&(r=Math.floor(c.lastscrolly-c.speedy*(1-c.demulxy)),c.lastscrolly=r,0>r||r>p)&&(d=.1);c.demulxy=Math.min(1,c.demulxy+d);c.nc.synched("domomentum2d",function(){c.speedx&&(c.nc.getScrollLeft()!=
c.chkx&&c.stop(),c.chkx=e,c.nc.setScrollLeft(e));c.speedy&&(c.nc.getScrollTop()!=c.chky&&c.stop(),c.chky=r,c.nc.setScrollTop(r));c.timer||(c.nc.hideCursor(),c.doSnapy(e,r))});1>c.demulxy?c.timer=setTimeout(t,s):(c.stop(),c.nc.hideCursor(),c.doSnapy(e,r))};t()}else c.doSnapy(c.nc.getScrollLeft(),c.nc.getScrollTop())}},w=f.fn.scrollTop;f.cssHooks.pageYOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollTop():w.call(k)},set:function(k,c){var h=f.data(k,"__nicescroll")||
!1;h&&h.ishwscroll?h.setScrollTop(parseInt(c)):w.call(k,c);return this}};f.fn.scrollTop=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollTop():w.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollTop(parseInt(k)):w.call(f(this),k)})};var B=f.fn.scrollLeft;f.cssHooks.pageXOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollLeft():B.call(k)},
set:function(k,c){var h=f.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollLeft(parseInt(c)):B.call(k,c);return this}};f.fn.scrollLeft=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollLeft():B.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollLeft(parseInt(k)):B.call(f(this),k)})};var C=function(k){var c=this;this.length=0;this.name="nicescrollarray";this.each=function(d){for(var f=
0,h=0;f<c.length;f++)d.call(c[f],h++);return c};this.push=function(d){c[c.length]=d;c.length++};this.eq=function(d){return c[d]};if(k)for(var h=0;h<k.length;h++){var m=f.data(k[h],"__nicescroll")||!1;m&&(this[this.length]=m,this.length++)}return this};(function(f,c,h){for(var m=0;m<c.length;m++)h(f,c[m])})(C.prototype,"show hide toggle onResize resize remove stop doScrollPos".split(" "),function(f,c){f[c]=function(){var f=arguments;return this.each(function(){this[c].apply(this,f)})}});f.fn.getNiceScroll=
function(k){return"undefined"==typeof k?new C(this):this[k]&&f.data(this[k],"__nicescroll")||!1};f.extend(f.expr[":"],{nicescroll:function(k){return f.data(k,"__nicescroll")?!0:!1}});f.fn.niceScroll=function(k,c){"undefined"!=typeof c||"object"!=typeof k||"jquery"in k||(c=k,k=!1);c=f.extend({},c);var h=new C;"undefined"==typeof c&&(c={});k&&(c.doc=f(k),c.win=f(this));var m=!("doc"in c);m||"win"in c||(c.win=f(this));this.each(function(){var d=f(this).data("__nicescroll")||!1;d||(c.doc=m?f(this):c.doc,
d=new R(c,f(this)),f(this).data("__nicescroll",d));h.push(d)});return 1==h.length?h[0]:h};window.NiceScroll={getjQuery:function(){return f}};f.nicescroll||(f.nicescroll=new C,f.nicescroll.options=I)});
/*
Sticky-kit v1.1.1 | WTFPL | Leaf Corcoran 2014 | http://leafo.net
*/
(function(){var k,e;k=this.jQuery||window.jQuery;e=k(window);k.fn.stick_in_parent=function(d){var v,y,n,p,h,C,s,G,q,H;null==d&&(d={});s=d.sticky_class;y=d.inner_scrolling;C=d.recalc_every;h=d.parent;p=d.offset_top;n=d.spacer;v=d.bottoming;null==p&&(p=0);null==h&&(h=void 0);null==y&&(y=!0);null==s&&(s="is_stuck");null==v&&(v=!0);G=function(a,d,q,z,D,t,r,E){var u,F,m,A,c,f,B,w,x,g,b;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);f=a.parent();null!=h&&(f=f.closest(h));if(!f.length)throw"failed to find stick parent";
u=m=!1;(g=null!=n?n&&a.closest(n):k("<div />"))&&g.css("position",a.css("position"));B=function(){var c,e,l;if(!E&&(c=parseInt(f.css("border-top-width"),10),e=parseInt(f.css("padding-top"),10),d=parseInt(f.css("padding-bottom"),10),q=f.offset().top+c+e,z=f.height(),m&&(u=m=!1,null==n&&(a.insertAfter(g),g.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(s),l=!0),D=a.offset().top-parseInt(a.css("margin-top"),10)-p,t=a.outerHeight(!0),r=a.css("float"),g&&g.css({width:a.outerWidth(!0),
height:t,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),l))return b()};B();if(t!==z)return A=void 0,c=p,x=C,b=function(){var b,k,l,h;if(!E&&(null!=x&&(--x,0>=x&&(x=C,B())),l=e.scrollTop(),null!=A&&(k=l-A),A=l,m?(v&&(h=l+t+c>z+q,u&&!h&&(u=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),l<D&&(m=!1,c=p,null==n&&("left"!==r&&"right"!==r||a.insertAfter(g),g.detach()),b={position:"",width:"",top:""},a.css(b).removeClass(s).trigger("sticky_kit:unstick")),
y&&(b=e.height(),t+p>b&&!u&&(c-=k,c=Math.max(b-t,c),c=Math.min(p,c),m&&a.css({top:c+"px"})))):l>D&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(s),null==n&&(a.after(g),"left"!==r&&"right"!==r||g.append(a)),a.trigger("sticky_kit:stick")),m&&v&&(null==h&&(h=l+t+c>z+q),!u&&h)))return u=!0,"static"===f.css("position")&&f.css({position:"relative"}),a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")},
w=function(){B();return b()},F=function(){E=!0;e.off("touchmove",b);e.off("scroll",b);e.off("resize",w);k(document.body).off("sticky_kit:recalc",w);a.off("sticky_kit:detach",F);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});f.position("position","");if(m)return null==n&&("left"!==r&&"right"!==r||a.insertAfter(g),g.remove()),a.removeClass(s)},e.on("touchmove",b),e.on("scroll",b),e.on("resize",w),k(document.body).on("sticky_kit:recalc",w),a.on("sticky_kit:detach",F),setTimeout(b,
0)}};q=0;for(H=this.length;q<H;q++)d=this[q],G(k(d));return this}}).call(this);
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