diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-23 06:06:01 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-23 06:06:01 +0300 |
commit | 8c7eab92cd0009f55cb999bbade43e0f969c137e (patch) | |
tree | 180cac6632448a211ddbe555191574c98e8dc385 /app | |
parent | dffeff5520e861dc6e7319b690c573186bbbd22e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
41 files changed, 798 insertions, 351 deletions
diff --git a/app/assets/javascripts/pages/projects/wiki_directories/index.js b/app/assets/javascripts/pages/projects/wiki_directories/index.js new file mode 100644 index 00000000000..b055b7cc322 --- /dev/null +++ b/app/assets/javascripts/pages/projects/wiki_directories/index.js @@ -0,0 +1,2 @@ +// currently, this controller inherits all behaviors from wikis +import '../wikis/index'; diff --git a/app/assets/javascripts/pages/projects/wiki_pages/index.js b/app/assets/javascripts/pages/projects/wiki_pages/index.js new file mode 100644 index 00000000000..92549cb648c --- /dev/null +++ b/app/assets/javascripts/pages/projects/wiki_pages/index.js @@ -0,0 +1,43 @@ +import $ from 'jquery'; +import Vue from 'vue'; + +import Translate from '~/vue_shared/translate'; +import csrf from '~/lib/utils/csrf'; +import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; +import ZenMode from '~/zen_mode'; +import GLForm from '~/gl_form'; + +import deleteWikiModal from '../wikis/components/delete_wiki_modal.vue'; +import Wikis from '../wikis/wikis'; + +document.addEventListener('DOMContentLoaded', () => { + new Wikis(); // eslint-disable-line no-new + new ShortcutsWiki(); // eslint-disable-line no-new + new ZenMode(); // eslint-disable-line no-new + new GLForm($('.wiki-form')); // eslint-disable-line no-new + + const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper'); + + if (deleteWikiModalWrapperEl) { + Vue.use(Translate); + + const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el: deleteWikiModalWrapperEl, + data: { + deleteWikiUrl: '', + }, + render(createElement) { + return createElement(deleteWikiModal, { + props: { + pageTitle, + deleteWikiUrl, + csrfToken: csrf.token, + }, + }); + }, + }); + } +}); diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index f5fd84d69ac..1b0d8fb4b71 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -1,41 +1,11 @@ import $ from 'jquery'; -import Vue from 'vue'; -import Translate from '~/vue_shared/translate'; -import csrf from '~/lib/utils/csrf'; import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; +import GLForm from '~/gl_form'; + import Wikis from './wikis'; -import ZenMode from '../../../zen_mode'; -import GLForm from '../../../gl_form'; -import deleteWikiModal from './components/delete_wiki_modal.vue'; document.addEventListener('DOMContentLoaded', () => { new Wikis(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new - new ZenMode(); // eslint-disable-line no-new new GLForm($('.wiki-form')); // eslint-disable-line no-new - - const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper'); - - if (deleteWikiModalWrapperEl) { - Vue.use(Translate); - - const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset; - - // eslint-disable-next-line no-new - new Vue({ - el: deleteWikiModalWrapperEl, - data: { - deleteWikiUrl: '', - }, - render(createElement) { - return createElement(deleteWikiModal, { - props: { - pageTitle, - deleteWikiUrl, - csrfToken: csrf.token, - }, - }); - }, - }); - } }); diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js index d41199f6374..1bf799a5ee0 100644 --- a/app/assets/javascripts/pages/projects/wikis/wikis.js +++ b/app/assets/javascripts/pages/projects/wikis/wikis.js @@ -12,8 +12,8 @@ export default class Wikis { } this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page')); - this.editTitleInput = document.querySelector('form.wiki-form #wiki_title'); - this.commitMessageInput = document.querySelector('form.wiki-form #wiki_message'); + this.editTitleInput = document.querySelector('form.wiki-form #wiki_page_title'); + this.commitMessageInput = document.querySelector('form.wiki-form #wiki_page_message'); this.commitMessageI18n = this.isNewWikiPage ? s__('WikiPageCreate|Create %{pageTitle}') : s__('WikiPageEdit|Update %{pageTitle}'); diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index a53f5d85949..f73d1b41f84 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -99,3 +99,18 @@ justify-content: center; color: $gray-700; } + +.svg-icon { + display: inline-flex; + align-self: center; + + svg { + height: 1em; + width: 1em; + } + + &.svg-baseline svg { + top: 0.125em; + position: relative; + } +} diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 0b65b915abf..8f1b4cdb6d5 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -1,3 +1,31 @@ +.new-wiki-page { + .new-wiki-page-slug-tip { + display: inline-block; + max-width: 100%; + margin-top: 5px; + } +} + +.wiki-history, +.wiki-page, +.edit-wiki-page { + margin-right: $gutter-width; +} + +.edit-wiki-page { + @media only screen and (min-width: map-get($grid-breakpoints, lg) + (2 * $gutter-width)) and (max-width: map-get($grid-breakpoints, lg) + (3 * $gutter-width)) { + margin-right: $gutter-width * 1.5; + } +} + +.wiki-form { + .edit-wiki-page-slug-tip { + display: inline-block; + max-width: 100%; + margin-top: 5px; + } +} + .title .edit-wiki-header { width: 780px; margin-left: auto; @@ -5,6 +33,15 @@ padding-right: 7px; } +.container-fluid.wiki-page, +.container-fluid.edit-wiki-page { + width: initial; +} + +.wiki-history.breadcrumbs { + min-height: (2 * $gl-padding) + 22; +} + .wiki-page-header { position: relative; @@ -79,7 +116,7 @@ } .sidebar-container { - padding: $gl-padding 0; + padding-bottom: $gl-padding; width: calc(100% + 100px); padding-right: 100px; height: 100%; @@ -125,7 +162,7 @@ } .wiki-sidebar-header { - padding: 0 $gl-padding $gl-padding; + padding: $gl-padding; .gutter-toggle { margin-top: 0; diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index 2a9729b6ffd..a3938ea3652 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -9,11 +9,10 @@ module PreviewMarkdown markdown_params = case controller_name - when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } when 'snippets' then { skip_project_check: true } when 'groups' then { group: group } when 'projects' then projects_filter_params - else {} + else preview_markdown_params end render json: { @@ -25,6 +24,7 @@ module PreviewMarkdown } } end + # rubocop:enable Gitlab/ModuleWithInstanceVariables def projects_filter_params { @@ -32,5 +32,11 @@ module PreviewMarkdown suggestions_filter_enabled: params[:preview_suggestions].present? } end - # rubocop:enable Gitlab/ModuleWithInstanceVariables + + private + + # Override this method to customise the markdown for your controller + def preview_markdown_params + {} + end end diff --git a/app/controllers/concerns/project_wiki_actions.rb b/app/controllers/concerns/project_wiki_actions.rb new file mode 100644 index 00000000000..304e16a894a --- /dev/null +++ b/app/controllers/concerns/project_wiki_actions.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Controllers that include this concern must provide: +# * project +# * current_user +module ProjectWikiActions + extend ActiveSupport::Concern + + included do + before_action :authorize_read_wiki! + before_action :init_wiki_actions + + attr_accessor :project_wiki, :sidebar_page, :sidebar_wiki_entries + end + + def init_wiki_actions + load_project_wiki + load_wiki_sidebar + rescue ProjectWiki::CouldNotCreateWikiError + flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") + redirect_to project_path(project) + end + + def load_project_wiki + self.project_wiki = load_wiki + end + + def load_wiki_sidebar + self.sidebar_page = project_wiki.find_sidebar(params[:version_id]) + + return if sidebar_page.present? + + # Fallback to default sidebar + self.sidebar_wiki_entries = WikiDirectory.group_by_directory(project_wiki.list_pages(limit: 15)) + end + + def load_wiki + # Call #wiki to make sure the Wiki Repo is initialized + ProjectWiki.new(project, current_user).tap(&:wiki) + end +end diff --git a/app/controllers/projects/wiki_directories_controller.rb b/app/controllers/projects/wiki_directories_controller.rb new file mode 100644 index 00000000000..f66da60f498 --- /dev/null +++ b/app/controllers/projects/wiki_directories_controller.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Projects::WikiDirectoriesController < Projects::ApplicationController + include ProjectWikiActions + + def self.local_prefixes + [controller_path, 'shared/wiki'] + end + + def show + @wiki_dir = find_dir || WikiDirectory.new(params[:id]) + + return render('empty') if @wiki_dir.empty? + + @wiki_entries = @wiki_pages = Kaminari + .paginate_array(@wiki_dir.pages) + .page(params[:page]) + + render 'show' + end + + private + + def find_dir + dir_params = params.permit(:id, :sort, :direction).dup.tap do |h| + Gitlab::Utils.allow_hash_values(h, sort_params_config[:allowed]) + end + + project_wiki.find_dir(dir_params[:id], dir_params[:sort], dir_params[:direction]) + end +end diff --git a/app/controllers/projects/wiki_pages_controller.rb b/app/controllers/projects/wiki_pages_controller.rb new file mode 100644 index 00000000000..0ba397b08f8 --- /dev/null +++ b/app/controllers/projects/wiki_pages_controller.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +class Projects::WikiPagesController < Projects::ApplicationController + include ProjectWikiActions + include SendsBlob + include PreviewMarkdown + include Gitlab::Utils::StrongMemoize + + def self.local_prefixes + [controller_path, 'shared/wiki'] + end + + before_action :authorize_create_wiki!, only: [:edit, :create, :update] + before_action :authorize_admin_wiki!, only: :destroy + + before_action :load_page, only: [:show, :edit, :update, :history, :destroy] + before_action :valid_encoding?, + if: -> { %w[show edit update].include?(action_name) && load_page } + before_action only: [:edit, :update], unless: :valid_encoding? do + redirect_to(project_wiki_path(@project, @page)) + end + + def new + redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true) + end + + # `#show` handles a number of scenarios: + # + # - If `id` matches a WikiPage, then show the wiki page. + # - If `id` is a file in the wiki repository, then send the file. + # - If we know the user wants to create a new page with the given `id`, + # then display a create form. + # - Otherwise show the empty wiki page and invite the user to create a page. + def show + if @page + show_page + elsif file_blob + show_blob + elsif should_create_missing_page? + create_missing_page + else + render 'missing_page' + end + end + + def edit + end + + def update + @page = WikiPages::UpdateService + .new(@project, current_user, wiki_params) + .execute(@page) + + return saved(:updated) if @page.valid? + + render 'edit' + rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e + @error = e + render 'edit' + end + + def create + @page = WikiPages::CreateService + .new(@project, current_user, wiki_params) + .execute + + return saved(:created) if @page.persisted? + + render action: "edit" + rescue Gitlab::Git::Wiki::OperationError => e + @page = project_wiki.build_page(wiki_params) + @error = e + + render 'edit' + end + + def history + if @page + @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i), + total_count: @page.count_versions) + .page(params[:page]) + else + redirect_to( + project_wiki_path(@project, :home), + notice: _("Page not found") + ) + end + end + + def destroy + WikiPages::DestroyService.new(@project, current_user).execute(@page) + + redirect_to project_wiki_path(@project, :home), + status: 302, + notice: _("Page was successfully deleted") + rescue Gitlab::Git::Wiki::OperationError => e + @error = e + render 'edit' + end + + private + + # Callback for PreviewMarkdown + def preview_markdown_params + { pipeline: :wiki, project_wiki: project_wiki, page_slug: params[:id] } + end + + def show_page + set_encoding_error unless valid_encoding? + + @page_dir = @project_wiki.find_dir(@page.directory) if @page.directory.present? + @show_children = true + + render 'show' + end + + def show_blob + send_blob(@project_wiki.repository, file_blob) + end + + def should_create_missing_page? + view_param = @project_wiki.exists? ? 'create' : params[:view] + view_param == 'create' && can?(current_user, :create_wiki, @project) + end + + def create_missing_page + # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new + title = params[:id] unless params[:random_title].present? + + @page = project_wiki.build_page(title: title) + + render 'edit' + end + + def wiki_params + params.require(:wiki_page).permit(:title, :content, :format, :message, :last_commit_sha) + end + + def load_page + @page ||= @project_wiki.find_page(*page_params) + end + + def page_params + keys = [:id] + keys << :version_id if params[:action] == 'show' + + params.values_at(*keys) + end + + def valid_encoding? + strong_memoize(:valid_encoding) do + @page.content.encoding == Encoding::UTF_8 + end + end + + def set_encoding_error + flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.") + end + + def file_blob + strong_memoize(:file_blob) do + commit = @project_wiki.repository.commit(@project_wiki.default_branch) + + next unless commit + + @project_wiki.repository.blob_at(commit.id, params[:id]) + end + end + + def saved(action) + msg = case action + when :updated + _('Wiki was successfully updated') + when :created + _('Wiki was successfully created') + end + + redirect_to(project_wiki_path(@project, @page), notice: msg) + end +end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index b187fdb2723..d531d256721 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -1,120 +1,28 @@ # frozen_string_literal: true class Projects::WikisController < Projects::ApplicationController - include PreviewMarkdown - include SendsBlob - include Gitlab::Utils::StrongMemoize + include ProjectWikiActions + include WikiHelper - before_action :authorize_read_wiki! - before_action :authorize_create_wiki!, only: [:edit, :create] - before_action :authorize_admin_wiki!, only: :destroy - before_action :load_project_wiki - before_action :load_page, only: [:show, :edit, :update, :history, :destroy] - before_action :valid_encoding?, - if: -> { %w[show edit update].include?(action_name) && load_page } - before_action only: [:edit, :update], unless: :valid_encoding? do - redirect_to(project_wiki_path(@project, @page)) - end - - def new - redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true) + def self.local_prefixes + [controller_path, 'shared/wiki'] end def pages + @nesting = show_children_param + @show_children = @nesting != ProjectWiki::NESTING_CLOSED @wiki_pages = Kaminari.paginate_array( - @project_wiki.list_pages(sort: params[:sort], direction: params[:direction]) + project_wiki.list_pages(**sort_params) ).page(params[:page]) - @wiki_entries = WikiPage.group_by_directory(@wiki_pages) - end - - # `#show` handles a number of scenarios: - # - # - If `id` matches a WikiPage, then show the wiki page. - # - If `id` is a file in the wiki repository, then send the file. - # - If we know the user wants to create a new page with the given `id`, - # then display a create form. - # - Otherwise show the empty wiki page and invite the user to create a page. - def show - if @page - set_encoding_error unless valid_encoding? - - render 'show' - elsif file_blob - send_blob(@project_wiki.repository, file_blob) - elsif show_create_form? - # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new - title = params[:id] unless params[:random_title].present? - - @page = build_page(title: title) - - render 'edit' - else - render 'empty' - end - end - - def edit - end - - def update - return render('empty') unless can?(current_user, :create_wiki, @project) - - @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) - - if @page.valid? - redirect_to( - project_wiki_path(@project, @page), - notice: _('Wiki was successfully updated.') - ) - else - render 'edit' - end - rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e - @error = e - render 'edit' - end - - def create - @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute - - if @page.persisted? - redirect_to( - project_wiki_path(@project, @page), - notice: _('Wiki was successfully updated.') - ) - else - render action: "edit" - end - rescue Gitlab::Git::Wiki::OperationError => e - @page = build_page(wiki_params) - @error = e - - render 'edit' - end - - def history - if @page - @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i), - total_count: @page.count_versions) - .page(params[:page]) - else - redirect_to( - project_wiki_path(@project, :home), - notice: _("Page not found") - ) - end - end - - def destroy - WikiPages::DestroyService.new(@project, current_user).execute(@page) + @wiki_entries = case @nesting + when ProjectWiki::NESTING_FLAT + @wiki_pages + else + WikiDirectory.group_by_directory(@wiki_pages) + end - redirect_to project_wiki_path(@project, :home), - status: 302, - notice: _("Page was successfully deleted") - rescue Gitlab::Git::Wiki::OperationError => e - @error = e - render 'edit' + render 'show' end def git_access @@ -122,74 +30,13 @@ class Projects::WikisController < Projects::ApplicationController private - def show_create_form? - can?(current_user, :create_wiki, @project) && - @page.nil? && - # Always show the create form when the wiki has had at least one page created. - # Otherwise, we only show the form when the user has navigated from - # the 'empty wiki' page - (@project_wiki.exists? || params[:view] == 'create') + def sort_params + process_params(sort_params_config) end - def load_project_wiki - @project_wiki = load_wiki - - # Call #wiki to make sure the Wiki Repo is initialized - @project_wiki.wiki - - @sidebar_page = @project_wiki.find_sidebar(params[:version_id]) - - unless @sidebar_page # Fallback to default sidebar - @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.list_pages(limit: 15)) - end - rescue ProjectWiki::CouldNotCreateWikiError - flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") - redirect_to project_path(@project) - false - end - - def load_wiki - ProjectWiki.new(@project, current_user) - end - - def wiki_params - params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) - end - - def build_page(args = {}) - WikiPage.new(@project_wiki).tap do |page| - page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases - end - end - - def load_page - @page ||= @project_wiki.find_page(*page_params) - end - - def page_params - keys = [:id] - keys << :version_id if params[:action] == 'show' - - params.values_at(*keys) - end - - def valid_encoding? - strong_memoize(:valid_encoding) do - @page.content.encoding == Encoding::UTF_8 - end - end - - def set_encoding_error - flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.") - end - - def file_blob - strong_memoize(:file_blob) do - commit = @project_wiki.repository.commit(@project_wiki.default_branch) - - next unless commit + def show_children_param + config = nesting_params_config(params[:sort]) - @project_wiki.repository.blob_at(commit.id, params[:id]) - end + process_params(config) end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 4f73270577f..66b88f6b626 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -41,6 +41,12 @@ module IconsHelper ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url) end + def sprite_icon_with_text(icon_name, content, opts = {}) + wrapper_class = opts.delete(:wrapper_class) + icon = sprite_icon(icon_name, opts) + content_tag(:span, [icon, content].join('').html_safe, class: wrapper_class) + end + def sprite_icon(icon_name, size: nil, css_class: nil) if Gitlab::Sentry.should_raise_for_dev? unless known_sprites.include?(icon_name) diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index dd8fde2a697..3cf610cbd38 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -48,14 +48,23 @@ module WikiHelper expose_url(api_v4_projects_wikis_attachments_path(id: @project.id)) end - def wiki_sort_controls(project, sort, direction) - sort ||= ProjectWiki::TITLE_ORDER - link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' - reversed_direction = direction == 'desc' ? 'asc' : 'desc' - icon_class = direction == 'desc' ? 'highest' : 'lowest' - - link_to(project_wikis_pages_path(project, sort: sort, direction: reversed_direction), - type: 'button', class: link_class, title: _('Sort direction')) do + WIKI_SORT_CSS_CLASSES = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' + + def wiki_sort_controls(sort_params = {}, &block) + current_sort = sort_params[:sort] || ProjectWiki::TITLE_ORDER + current_direction = (sort_params[:direction] || 'asc').inquiry + + reversed_direction = current_direction.desc? ? 'asc' : 'desc' + icon_class = current_direction.desc? ? 'highest' : 'lowest' + + sorting = sort_params.merge(sort: current_sort, direction: reversed_direction) + opts = { + type: 'button', + class: WIKI_SORT_CSS_CLASSES, + title: _('Sort direction') + } + + link_to(yield(sorting), opts) do sprite_icon("sort-#{icon_class}", size: 16) end end @@ -67,4 +76,86 @@ module WikiHelper s_("Wiki|Title") end end + + # Render the sprite icon given the current show_children state + def wiki_show_children_icon(nesting) + icon_name, icon_text = + case nesting + when ProjectWiki::NESTING_TREE + ['folder-open', s_("Wiki|Show folder contents")] + when ProjectWiki::NESTING_CLOSED + ['folder-o', s_("Wiki|Hide folder contents")] + else + ['list-bulleted', s_("Wiki|Show files separately")] + end + + sprite_icon_with_text(icon_name, icon_text, size: 16) + end + + def wiki_page_link(wiki_page, nesting, project) + link = link_to(wiki_page.title, + project_wiki_path(project, wiki_page), + class: 'wiki-page-title') + + case nesting + when ProjectWiki::NESTING_FLAT + tags = [] + if wiki_page.directory.present? + wiki_dir = WikiDirectory.new(wiki_page.directory) + tags << link_to(wiki_dir.slug, project_wiki_dir_path(project, wiki_dir), class: 'wiki-page-dir-name') + tags << content_tag(:span, '/', class: 'wiki-page-name-separator') + end + + tags << link + tags.join.html_safe + else + link + end + end + + def sort_params_config + { + keys: [:sort, :direction], + defaults: { + sort: ProjectWiki::TITLE_ORDER, direction: ProjectWiki::DIRECTION_ASC + }, + allowed: { + sort: ProjectWiki::SORT_ORDERS, direction: ProjectWiki::SORT_DIRECTIONS + } + } + end + + def nesting_params_config(sort_key) + default_val = case sort_key + when ProjectWiki::CREATED_AT_ORDER + ProjectWiki::NESTING_FLAT + else + ProjectWiki::NESTING_CLOSED + end + { + keys: [:show_children], + defaults: { show_children: default_val }, + allowed: { show_children: ProjectWiki::NESTINGS } + } + end + + def process_params(config) + unprocessed = params.permit(*config[:keys]) + + processed = unprocessed + .with_defaults(config[:defaults]) + .tap { |h| Gitlab::Utils.allow_hash_values(h, config[:allowed]) } + .to_hash + .transform_keys(&:to_sym) + + if processed.keys == config[:keys] + processed.size == 1 ? processed.values.first : processed + else + raise ActionController::BadRequest, "illegal parameters: #{unprocessed}" + end + end + + def home_page? + params[:id] == 'home' + end end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index bb222ac7629..e0fcebf2642 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -17,6 +17,13 @@ class ProjectWiki CREATED_AT_ORDER = 'created_at' DIRECTION_DESC = 'desc' DIRECTION_ASC = 'asc' + SORT_ORDERS = [TITLE_ORDER, CREATED_AT_ORDER].freeze + SORT_DIRECTIONS = [DIRECTION_ASC, DIRECTION_DESC].freeze + + NESTING_FLAT = 'flat' + NESTING_TREE = 'tree' + NESTING_CLOSED = 'hidden' + NESTINGS = [NESTING_TREE, NESTING_CLOSED, NESTING_FLAT].freeze # Returns a string describing what went wrong after # an operation fails. @@ -58,7 +65,11 @@ class ProjectWiki end def wiki_base_path - [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('') + ::File.join(project_base_path, 'wikis') + end + + def wiki_page_path + ::File.join(project_base_path, '-', 'wiki_pages') end # Returns the Gitlab::Git::Wiki object. @@ -125,6 +136,23 @@ class ProjectWiki end end + # Finds directory within the repository based on a slug + # + # dir_name - The directory prefix. + # + # Returns an initialized WikiDirectory instance or nil + def find_dir(dir_name, sort = nil, direction = DIRECTION_ASC) + descending = direction == DIRECTION_DESC + # WikiListPagesRequest currently does not support server-side + # filtering. Ideally this logic should be moved to the gitaly + # side. + pages = wiki + .list_pages(sort: sort, direction_desc: descending) + .map { |page| WikiPage.new(self, page, true) } + .select { |wp| wp.directory == dir_name } + WikiDirectory.new(dir_name, pages) if pages.present? + end + def find_sidebar(version = nil) find_page(SIDEBAR, version) end @@ -144,6 +172,12 @@ class ProjectWiki false end + def build_page(attrs) + WikiPage.new(self).tap do |page| + page.update_attributes(attrs) # rubocop:disable Rails/ActiveRecordAliases + end + end + def update_page(page, content:, title: nil, format: :markdown, message: nil) commit = commit_details(:updated, message, page.title) @@ -171,7 +205,7 @@ class ProjectWiki title_array = title.split("/") title = title_array.pop - [title, title_array.join("/")] + [title, ::File.join(title_array)] end def repository @@ -198,6 +232,10 @@ class ProjectWiki private + def project_base_path + ::File.join(Gitlab.config.gitlab.relative_url_root, @project.full_path) + end + def create_repo!(raw_repository) gitlab_shell.create_wiki_repository(project) diff --git a/app/models/wiki_directory.rb b/app/models/wiki_directory.rb index 712ba79bbd2..6c86c11e7fb 100644 --- a/app/models/wiki_directory.rb +++ b/app/models/wiki_directory.rb @@ -1,20 +1,75 @@ # frozen_string_literal: true class WikiDirectory + include StaticModel include ActiveModel::Validations attr_accessor :slug, :pages validates :slug, presence: true + # StaticModel overrides and configuration: + + def self.primary_key + 'slug' + end + + def id + "#{slug}@#{last_version&.sha}" + end + + def self.model_name + ActiveModel::Name.new(self, nil, 'wiki_dir') + end + + alias_method :to_param, :slug + alias_method :title, :slug + + # Sorts and groups pages by directory. + # + # pages - an array of WikiPage objects. + # + # Returns an array of WikiPage and WikiDirectory objects. + # The entries are sorted in the order of the input array, where + # directories appear in the position of their first member. + def self.group_by_directory(pages) + grouped = [] + dirs = Hash.new do |h, k| + new(k).tap { |dir| grouped << (h[k] = dir) } + end + + Array.wrap(pages).each_with_object(grouped) do |page, top_level| + group = page.directory.present? ? dirs[page.directory] : top_level + + group << page + end + end + def initialize(slug, pages = []) @slug = slug @pages = pages end - # Relative path to the partial to be used when rendering collections - # of this object. - def to_partial_path - 'projects/wikis/wiki_directory' + def <<(page) + @pages << page + @last_version = nil + end + + def last_version + @last_version ||= @pages.map(&:last_version).max_by(&:authored_date) + end + + def page_count + @pages.size + end + + def empty? + page_count.zero? + end + + def to_partial_path(context = nil) + name = [context, 'wiki_directory'].compact.join('_') + + "projects/wiki_directories/#{name}" end end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 1fa29e5b933..6b3cb0b39d8 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -15,30 +15,7 @@ class WikiPage end def self.model_name - ActiveModel::Name.new(self, nil, 'wiki') - end - - # Sorts and groups pages by directory. - # - # pages - an array of WikiPage objects. - # - # Returns an array of WikiPage and WikiDirectory objects. The entries are - # sorted by alphabetical order (directories and pages inside each directory). - # Pages at the root level come before everything. - def self.group_by_directory(pages) - return [] if pages.blank? - - pages.each_with_object([]) do |page, grouped_pages| - next grouped_pages << page unless page.directory.present? - - directory = grouped_pages.find do |obj| - obj.is_a?(WikiDirectory) && obj.slug == page.directory - end - - next directory.pages << page if directory - - grouped_pages << WikiDirectory.new(page.directory, [page]) - end + ActiveModel::Name.new(self, nil, 'wiki_page') end def self.unhyphenize(name) @@ -66,6 +43,16 @@ class WikiPage Gitlab::HookData::WikiPageBuilder.new(self).build end + # Create a new WikiPage + # + # == Parameters: + # wiki:: + # A `ProjectWiki` model object + # page:: + # A `Gitlab::Git::WikiPage` business object, to which this class provides a facade + # persisted:: + # Is this page fully saved on disk? + # def initialize(wiki, page = nil, persisted = false) @wiki = wiki @page = page @@ -250,10 +237,10 @@ class WikiPage end end - # Relative path to the partial to be used when rendering collections - # of this object. - def to_partial_path - 'projects/wikis/wiki_page' + def to_partial_path(context = nil) + name = [context, 'wiki_page'].compact.join('_') + + "projects/wiki_pages/#{name}" end def id diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index c84bc0b5cd4..ec27f3c24df 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -282,14 +282,14 @@ - if project_nav_tab? :wiki - wiki_url = project_wiki_path(@project, :home) - = nav_link(controller: :wikis) do + = nav_link(controller: [:wikis, :wiki_pages, :wiki_directories]) do = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do .nav-icon-container = sprite_icon('book') %span.nav-item-name = _('Wiki') %ul.sidebar-sub-level-items.is-fly-out-only - = nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: [:wikis, :wiki_pages, :wiki_directories], html_options: { class: "fly-out-top-item" } ) do = link_to wiki_url do %strong.fly-out-top-item-name = _('Wiki') diff --git a/app/views/projects/wiki_directories/_pages_wiki_directory.html.haml b/app/views/projects/wiki_directories/_pages_wiki_directory.html.haml new file mode 100644 index 00000000000..a9d2f38da88 --- /dev/null +++ b/app/views/projects/wiki_directories/_pages_wiki_directory.html.haml @@ -0,0 +1,15 @@ +%li + %span.text-secondary-500.svg-icon.svg-baseline + - if @show_children + = sprite_icon('folder-open', size: 16) + - else + = sprite_icon('folder-o', size: 16) + + = link_to wiki_dir.slug, project_wiki_dir_path(@project, wiki_dir) + - unless @show_children + %span.badge.badge-pill.wiki-dir-page-count= wiki_dir.page_count + .float-right + %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_dir.last_version.authored_date) }).html_safe + - if @show_children + %ul + = render wiki_dir.pages, context: context diff --git a/app/views/projects/wiki_directories/_sidebar_wiki_directory.html.haml b/app/views/projects/wiki_directories/_sidebar_wiki_directory.html.haml new file mode 100644 index 00000000000..2f62e9d4516 --- /dev/null +++ b/app/views/projects/wiki_directories/_sidebar_wiki_directory.html.haml @@ -0,0 +1,7 @@ +%li + %span.text-secondary-300.svg-icon.svg-baseline + = sprite_icon('folder-open', size: 16) + + = link_to wiki_dir.slug, project_wiki_dir_path(@project, wiki_dir) + %ul= render wiki_dir.pages, context: context + diff --git a/app/views/projects/wiki_directories/_wiki_directory.html.haml b/app/views/projects/wiki_directories/_wiki_directory.html.haml new file mode 100644 index 00000000000..022c209c5bd --- /dev/null +++ b/app/views/projects/wiki_directories/_wiki_directory.html.haml @@ -0,0 +1 @@ += render wiki_directory.to_partial_path(context), wiki_dir: wiki_directory, context: context diff --git a/app/views/projects/wiki_directories/empty.html.haml b/app/views/projects/wiki_directories/empty.html.haml new file mode 100644 index 00000000000..cab1c3acb60 --- /dev/null +++ b/app/views/projects/wiki_directories/empty.html.haml @@ -0,0 +1,34 @@ +- layout_path = 'shared/empty_states/wikis_layout' +- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) +- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) +- breadcrumb_title s_(@wiki_dir.slug) +- page_title @wiki_dir.slug + +- if can?(current_user, :create_wiki, @project) + - create_path = project_wiki_path(@project, params[:id], { view: 'create', params: { title: "#{params[:id]}/" } }) + - create_link = link_to s_('WikiDirEmpty|Create a page in this directory'), create_path, class: 'btn btn-success qa-create-first-page-link', title: s_('WikiDirEmpty|Create a page') + + = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do + %h4.text-left + = s_('WikiDirEmpty|This directory has no wiki pages') + %p.text-left + = s_("WikiDirEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.") + = create_link + +- elsif can?(current_user, :read_issue, @project) + - issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project) + - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn btn-success', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement') + + = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do + %h4 + = s_('WikiDirEmpty|This directory has no wiki pages') + %p.text-left + = s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link } + = new_issue_link + +- else + = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do + %h4 + = s_('WikiDirEmpty|This directory has no wiki pages') + %p + = s_('WikiEmpty|You must be a project member in order to add wiki pages.') diff --git a/app/views/projects/wiki_directories/show.html.haml b/app/views/projects/wiki_directories/show.html.haml new file mode 100644 index 00000000000..4c7978d1216 --- /dev/null +++ b/app/views/projects/wiki_directories/show.html.haml @@ -0,0 +1,6 @@ +- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) +- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) +- breadcrumb_title s_(@wiki_dir.slug) +- page_title @wiki_dir.slug + += render 'page_listing', { allow_change_nesting: false, wiki_page_title: page_title, page_path: ->(opts) { project_wiki_dir_path(@project, @wiki_dir, opts) } } diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wiki_pages/_form.html.haml index a153f527ee0..8f5757d6d98 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wiki_pages/_form.html.haml @@ -1,9 +1,11 @@ - form_classes = 'wiki-form common-note-form prepend-top-default js-quick-submit' - form_classes += ' js-new-wiki-page' unless @page.persisted? -= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, - html: { class: form_classes }, - data: { uploads_path: uploads_path } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], + method: @page.persisted? ? 'put' : 'post', + url: { controller: 'wiki_pages', action: @page.persisted? ? :update : :create }, + html: { class: form_classes }, + data: { uploads_path: uploads_path } do |f| = form_errors(@page) - if @page.persisted? @@ -12,7 +14,7 @@ .form-group.row .col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12 - = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: _('Wiki|Page title') + = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: s_('Wiki|Page title') %span.d-inline-block.mw-100.prepend-top-5 = icon('lightbulb-o') - if @page.persisted? diff --git a/app/views/projects/wiki_pages/_page_title.html.haml b/app/views/projects/wiki_pages/_page_title.html.haml new file mode 100644 index 00000000000..a3b077999e6 --- /dev/null +++ b/app/views/projects/wiki_pages/_page_title.html.haml @@ -0,0 +1,4 @@ += link_to @page.human_title, project_wiki_path(@project, @page) +%span.light + = _('·').html_safe + = subtitle diff --git a/app/views/projects/wiki_pages/_pages_wiki_page.html.haml b/app/views/projects/wiki_pages/_pages_wiki_page.html.haml new file mode 100644 index 00000000000..c177d03bee1 --- /dev/null +++ b/app/views/projects/wiki_pages/_pages_wiki_page.html.haml @@ -0,0 +1,8 @@ +%li + %span.text-secondary-500.svg-icon.svg-baseline= sprite_icon('book', size: 16) + = wiki_page_link(wiki_page, @nesting, @project) + .float-right + %span.badge.badge-pill.wiki-page-format= _(wiki_page.format) + - if wiki_page.last_version + = '/' + %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe diff --git a/app/views/projects/wiki_pages/_sidebar_wiki_page.html.haml b/app/views/projects/wiki_pages/_sidebar_wiki_page.html.haml new file mode 100644 index 00000000000..205c3dd76bf --- /dev/null +++ b/app/views/projects/wiki_pages/_sidebar_wiki_page.html.haml @@ -0,0 +1,11 @@ +- is_active = params[:id] == wiki_page.slug +- icon_active_class = is_active ? 'text-secondary-800' : 'text-secondary-300' + +%li{ class: active_when(is_active) } + %span.svg-icon.svg-baseline{ class: icon_active_class } + = sprite_icon('book', size: 16) + - if is_active + = wiki_page.human_title + - else + = link_to project_wiki_path(@project, wiki_page) do + = wiki_page.human_title diff --git a/app/views/projects/wiki_pages/_wiki_page.html.haml b/app/views/projects/wiki_pages/_wiki_page.html.haml new file mode 100644 index 00000000000..947e96fed6b --- /dev/null +++ b/app/views/projects/wiki_pages/_wiki_page.html.haml @@ -0,0 +1 @@ += render wiki_page.to_partial_path(context), wiki_page: wiki_page diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wiki_pages/edit.html.haml index 9ccf5acfefc..4e5f6a077a4 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wiki_pages/edit.html.haml @@ -1,5 +1,10 @@ -- @content_class = "limit-container-width" unless fluid_layout -- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, @page) +- @content_class = 'edit-wiki-page' + (fluid_layout ? '' : ' limit-container-width') +- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) +- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) +- if @page.persisted? && @page_dir.present? + - add_to_breadcrumbs _(@page_dir.slug), project_wiki_dir_path(@project, @page_dir) +- if @page.persisted? + - add_to_breadcrumbs @page.human_title, project_wiki_path(@project, @page) - breadcrumb_title @page.persisted? ? _("Edit") : _("New") - page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki") @@ -12,10 +17,7 @@ .nav-text %h2.wiki-page-title - if @page.persisted? - = link_to @page.human_title, project_wiki_path(@project, @page) - %span.light - · - = s_("Wiki|Edit Page") + = render partial: 'page_title', locals: { subtitle: s_("Wiki|Edit Page") } - else = s_("Wiki|Create New Page") diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wiki_pages/history.html.haml index d3a55c53649..d60a32750df 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wiki_pages/history.html.haml @@ -1,3 +1,4 @@ +- @content_class = 'wiki-history' - page_title _("History"), @page.human_title, _("Wiki") .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row @@ -6,10 +7,7 @@ .nav-text %h2.wiki-page-title - = link_to @page.human_title, project_wiki_path(@project, @page) - %span.light - · - = _("History") + = render partial: 'page_title', locals: { subtitle: _("History") } .table-holder %table.table @@ -39,4 +37,4 @@ = version.format = paginate @page_versions, theme: 'gitlab' -= render 'sidebar' += render 'shared/wiki/sidebar' diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wiki_pages/missing_page.html.haml index 62fa6e1907b..62fa6e1907b 100644 --- a/app/views/projects/wikis/empty.html.haml +++ b/app/views/projects/wiki_pages/missing_page.html.haml diff --git a/app/views/projects/wiki_pages/show.html.haml b/app/views/projects/wiki_pages/show.html.haml new file mode 100644 index 00000000000..879ddfb03c8 --- /dev/null +++ b/app/views/projects/wiki_pages/show.html.haml @@ -0,0 +1,34 @@ +- @content_class = 'wiki-page' + (fluid_layout ? '' : ' limit-container-width') +- breadcrumb_title @page.human_title +- page_title @page.human_title, _("Wiki") +- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) +- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) +- if @page_dir.present? + - add_to_breadcrumbs _(@page_dir.slug), project_wiki_dir_path(@project, @page_dir) + +.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + + .nav-text.flex-fill + %h2.wiki-page-title= @page.human_title + %span.wiki-last-edit-by + - if @page.last_version + = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe + #{time_ago_with_tooltip(@page.last_version.authored_date)} + + .nav-controls.pb-md-3.pb-lg-0 + = render 'main_links' + +- if @page.historical? + .warning_message + = s_("WikiHistoricalPage|This is an old version of this page.") + - most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page) + - history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page) + = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe + +.prepend-top-default.append-bottom-default + .md.md-file.qa-wiki-page-content + = render_wiki_content(@page) + += render 'sidebar' diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml deleted file mode 100644 index c156f8cbf50..00000000000 --- a/app/views/projects/wikis/_pages_wiki_page.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -%li - = link_to wiki_page.title, project_wiki_path(@project, wiki_page) - %small (#{wiki_page.format}) - .float-right - - if wiki_page.last_version - %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml deleted file mode 100644 index 769d869bd53..00000000000 --- a/app/views/projects/wikis/_sidebar_wiki_page.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -%li{ class: active_when(params[:id] == wiki_page.slug) } - = link_to project_wiki_path(@project, wiki_page) do - = wiki_page.human_title diff --git a/app/views/projects/wikis/_wiki_directory.html.haml b/app/views/projects/wikis/_wiki_directory.html.haml deleted file mode 100644 index 0e5f32ed859..00000000000 --- a/app/views/projects/wikis/_wiki_directory.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%li - = wiki_directory.slug - %ul - = render wiki_directory.pages, context: context diff --git a/app/views/projects/wikis/_wiki_page.html.haml b/app/views/projects/wikis/_wiki_page.html.haml deleted file mode 100644 index c84d06dad02..00000000000 --- a/app/views/projects/wikis/_wiki_page.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render "#{context}_wiki_page", wiki_page: wiki_page diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml deleted file mode 100644 index d9dcd8f9acd..00000000000 --- a/app/views/projects/wikis/pages.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) -- breadcrumb_title s_("Wiki|Pages") -- page_title s_("Wiki|Pages"), _("Wiki") -- sort_title = wiki_sort_title(params[:sort]) - -.wiki-page-header.top-area.flex-column.flex-lg-row - - .nav-text.flex-fill - %h2.wiki-page-title - = s_("Wiki|Wiki Pages") - - .nav-controls.pb-md-3.pb-lg-0 - = link_to project_wikis_git_access_path(@project), class: 'btn' do - = icon('cloud-download') - = _("Clone repository") - - .dropdown.inline.wiki-sort-dropdown - .btn-group{ role: 'group' } - .btn-group{ role: 'group' } - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } - = sort_title - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort - %li - = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title) - = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title) - = wiki_sort_controls(@project, params[:sort], params[:direction]) - -%ul.wiki-pages-list.content-list - = render @wiki_entries, context: 'pages' - -= paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index ebd99cf8605..9c732a933cf 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,32 +1,5 @@ -- @content_class = "limit-container-width" unless fluid_layout -- breadcrumb_title @page.human_title -- wiki_breadcrumb_dropdown_links(@page.slug) -- page_title @page.human_title, _("Wiki") -- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) +- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) +- breadcrumb_title s_("Wiki|Pages") +- page_title s_("Wiki|Contents"), _("Wiki") -.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row - %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } - = icon('angle-double-left') - - .nav-text.flex-fill - %h2.wiki-page-title= @page.human_title - %span.wiki-last-edit-by - - if @page.last_version - = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe - #{time_ago_with_tooltip(@page.last_version.authored_date)} - - .nav-controls.pb-md-3.pb-lg-0 - = render 'main_links' - -- if @page.historical? - .warning_message - = s_("WikiHistoricalPage|This is an old version of this page.") - - most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page) - - history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page) - = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe - -.prepend-top-default.append-bottom-default - .md.md-file{ data: { qa_selector: 'wiki_page_content' } } - = render_wiki_content(@page) - -= render 'sidebar' += render 'page_listing', { allow_change_nesting: ::Feature.enabled?(:wikis_allow_change_nesting), wiki_page_title: page_title, page_path: ->(opts) { project_wikis_pages_path(@project, opts) } } diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml index 73eedcc1dc9..e05230de457 100644 --- a/app/views/shared/empty_states/_wikis.html.haml +++ b/app/views/shared/empty_states/_wikis.html.haml @@ -1,8 +1,11 @@ - layout_path = 'shared/empty_states/wikis_layout' +- wiki_is_empty = @project_wiki.empty? +- empty_msg = wiki_is_empty ? s_('WikiEmpty|This project has no wiki pages') : s_('WikiEmpty|This page does not exist') +- create_msg = wiki_is_empty ? s_('WikiEmpty|Create your first page') : s_('WikiEmpty|Create this page') - if can?(current_user, :create_wiki, @project) - create_path = project_wiki_path(@project, params[:id], { view: 'create' }) - - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-success qa-create-first-page-link', title: s_('WikiEmpty|Create your first page') + - create_link = link_to create_msg, create_path, class: 'btn btn-success qa-create-first-page-link', title: create_msg = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do %h4.text-left @@ -17,7 +20,7 @@ = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do %h4 - = s_('WikiEmpty|This project has no wiki pages') + = empty_msg %p.text-left = s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link } = new_issue_link @@ -25,6 +28,6 @@ - else = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do %h4 - = s_('WikiEmpty|This project has no wiki pages') + = empty_msg %p = s_('WikiEmpty|You must be a project member in order to add wiki pages.') diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/shared/wiki/_main_links.html.haml index 2e1e176c42a..5e41bb6a9cd 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/shared/wiki/_main_links.html.haml @@ -1,6 +1,6 @@ - if (@page && @page.persisted?) - if can?(current_user, :create_wiki, @project) - = link_to project_wikis_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do + = link_to project_wiki_pages_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do = s_("Wiki|New page") = link_to project_wiki_history_path(@project, @page), class: "btn", role: "button" do = s_("Wiki|Page history") diff --git a/app/views/shared/wiki/_page_listing.html.haml b/app/views/shared/wiki/_page_listing.html.haml new file mode 100644 index 00000000000..80f3071a8b2 --- /dev/null +++ b/app/views/shared/wiki/_page_listing.html.haml @@ -0,0 +1,45 @@ +- @no_container = true +- current_sorting = params.permit(:sort, :direction) +- sort_title = wiki_sort_title(params[:sort]) + +%div{ class: container_class } + .wiki-page-header.top-area.flex-column.flex-lg-row + + .nav-text.flex-fill + %h2.wiki-page-title + = wiki_page_title + + .nav-controls.pb-md-3.pb-lg-0 + - if can?(current_user, :create_wiki, @project) + = link_to project_wiki_pages_new_path(@project), class: "add-new-wiki btn btn-success" do + = s_("Wiki|New page") + + = link_to project_wikis_git_access_path(@project), class: 'btn qa-clone-repository-link' do + = sprite_icon('download', size: 16) + = _("Clone repository") + + - if @nesting.present? && allow_change_nesting + .dropdown.inline.wiki-nesting-dropdown + .btn-group{ role: 'group' } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + = wiki_show_children_icon(@nesting) + = sprite_icon('chevron-down', size: 16) + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort + - ProjectWiki::NESTINGS.each do |choice| + %li= link_to wiki_show_children_icon(choice), page_path.call(current_sorting.merge(show_children: choice)), class: @nesting == choice ? 'is-active' : '' + + .dropdown.inline.wiki-sort-dropdown + .btn-group{ role: 'group' } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + = sort_title + = sprite_icon('chevron-down', size: 16) + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort + %li + = sortable_item(s_("Wiki|Title"), page_path.call(sort: ProjectWiki::TITLE_ORDER), sort_title) + = sortable_item(s_("Wiki|Created date"), page_path.call(sort: ProjectWiki::CREATED_AT_ORDER), sort_title) + = wiki_sort_controls(current_sorting.merge(show_children: @nesting), &page_path) + + %ul.wiki-pages-list.content-list + = render @wiki_entries, context: 'pages' + + = paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/shared/wiki/_sidebar.html.haml index 83d145444d8..83d145444d8 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/shared/wiki/_sidebar.html.haml |