diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 0cab04359..d738e5caa 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -99,3 +99,38 @@ $(document).on("turbolinks:before-cache", function () { $(function () { }); + +$(document).on('turbolinks:load', function() { + + $('.logo-item-left').on("change", 'input[type="file"]', function () { + var $fileInput = $(this); + var file = this.files[0]; + var imageType = /image.*/; + if (file && file.type.match(imageType)) { + var reader = new FileReader(); + reader.onload = function () { + var $box = $fileInput.parent(); + $box.find('img').attr('src', reader.result).css('display', 'block'); + $box.addClass('has-img'); + }; + reader.readAsDataURL(file); + } else { + } + }); + + $('.attachment-item-left').on("change", 'input[type="file"]', function () { + var $fileInput = $(this); + var file = this.files[0]; + var imageType = /image.*/; + if (file && file.type.match(imageType)) { + var reader = new FileReader(); + reader.onload = function () { + var $box = $fileInput.parent(); + $box.find('img').attr('src', reader.result).css('display', 'block'); + $box.addClass('has-img'); + }; + reader.readAsDataURL(file); + } else { + } + }); +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/projects/index.js b/app/assets/javascripts/admins/projects/index.js new file mode 100644 index 000000000..dbf710ea5 --- /dev/null +++ b/app/assets/javascripts/admins/projects/index.js @@ -0,0 +1,141 @@ +/* + * @Description: Do not edit + * @Date: 2021-08-31 11:16:45 + * @LastEditors: viletyy + * @Author: viletyy + * @LastEditTime: 2021-08-31 14:19:46 + * @FilePath: /forgeplus/app/assets/javascripts/admins/system_notifications/index.js + */ +$(document).on('turbolinks:load', function(){ + + var showSuccessNotify = function() { + $.notify({ + message: '操作成功' + },{ + type: 'success' + }); + } + + // close user + $('.project-list-container').on('click', '.recommend-action', function(){ + var $closeAction = $(this); + var $uncloseAction = $closeAction.siblings('.unrecommend-action'); + var $editAction = $closeAction.siblings('.edit-recommend-action'); + + var keywordID = $closeAction.data('id'); + customConfirm({ + content: '确认将该项目设置为推荐项目吗?', + ok: function(){ + $.ajax({ + url: '/admins/projects/' + keywordID, + method: 'PUT', + dataType: 'json', + data: { + project: { + recommend: true, + recommend_index: 1 + } + }, + success: function() { + showSuccessNotify(); + $closeAction.hide(); + $uncloseAction.show(); + $editAction.show(); + $(".project-item-"+keywordID).children('td').eq(5).text("√") + } + }); + } + }); + }); + + // unclose user + $('.project-list-container').on('click', '.unrecommend-action', function(){ + var $uncloseAction = $(this); + var $closeAction = $uncloseAction.siblings('.recommend-action'); + var $editAction = $closeAction.siblings('.edit-recommend-action'); + + var keywordID = $uncloseAction.data('id'); + customConfirm({ + content: '确认取消该推荐项目吗?', + ok: function () { + $.ajax({ + url: '/admins/projects/' + keywordID, + method: 'PUT', + dataType: 'json', + data: { + project: { + recommend: false, + recommend_index: 0 + } + }, + success: function() { + showSuccessNotify(); + $closeAction.show(); + $uncloseAction.hide(); + $editAction.hide(); + $(".project-item-"+keywordID).children('td').eq(5).text("") + } + }); + } + }) + }); + + + // close user + $('.project-list-container').on('click', '.pinned-action', function(){ + var $closeAction = $(this); + var $uncloseAction = $closeAction.siblings('.unpinned-action'); + + var keywordID = $closeAction.data('id'); + customConfirm({ + content: '确认将该项目设置为精选项目吗?', + ok: function(){ + $.ajax({ + url: '/admins/projects/' + keywordID, + method: 'PUT', + dataType: 'json', + data: { + project: { + is_pinned: true, + } + }, + success: function() { + showSuccessNotify(); + $closeAction.hide(); + $uncloseAction.show(); + $(".project-item-"+keywordID).children('td').eq(4).text("√") + } + }); + } + }); + }); + + // unclose user + $('.project-list-container').on('click', '.unpinned-action', function(){ + var $uncloseAction = $(this); + var $closeAction = $uncloseAction.siblings('.pinned-action'); + + var keywordID = $uncloseAction.data('id'); + customConfirm({ + content: '确认取消该精选项目吗?', + ok: function () { + $.ajax({ + url: '/admins/projects/' + keywordID, + method: 'PUT', + dataType: 'json', + data: { + project: { + is_pinned: false, + } + }, + success: function() { + showSuccessNotify(); + $closeAction.show(); + $uncloseAction.hide(); + $(".project-item-"+keywordID).children('td').eq(4).text("") + } + }); + } + }) + }); +}) \ No newline at end of file diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index a401fc379..03c3970a6 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -58,3 +58,149 @@ input.form-control { position: absolute; } +.logo-item { + display: flex; + + &-img { + display: block; + width: 80px; + height: 80px; + background: #e9ecef; + } + + &-upload { + cursor: pointer; + position: absolute; + top: 0; + width: 80px; + height: 80px; + background: #e9ecef; + border: 1px solid #ced4da; + + &::before { + content: ''; + position: absolute; + top: 27px; + left: 39px; + width: 2px; + height: 26px; + background: #495057; + } + + &::after { + content: ''; + position: absolute; + top: 39px; + left: 27px; + width: 26px; + height: 2px; + background: #495057; + } + } + + &-left { + position: relative; + width: 80px; + height: 80px; + + &.has-img { + .logo-item-upload { + display: none; + } + + &:hover { + .logo-item-upload { + display: block; + background: rgba(145, 145, 145, 0.8); + } + } + } + } + + &-right { + display: flex; + flex-direction: column; + justify-content: space-between; + color: #777777; + font-size: 0.8rem; + } + + &-title { + color: #23272B; + font-size: 1rem; + } +} + +.attachment-item { + display: flex; + + &-img { + display: block; + width: 160px; + height: 160px; + background: #e9ecef; + } + + &-upload { + cursor: pointer; + position: absolute; + top: 0; + width: 160px; + height: 160px; + background: #e9ecef; + border: 1px solid #ced4da; + + &::before { + content: ''; + position: absolute; + top: 54px; + left: 78px; + width: 2px; + height: 52px; + background: #495057; + } + + &::after { + content: ''; + position: absolute; + top: 78px; + left: 54px; + width: 52px; + height: 2px; + background: #495057; + } + } + + &-left { + position: relative; + width: 160px; + height: 160px; + + &.has-img { + .attachment-item-upload { + display: none; + } + + &:hover { + .attachment-item-upload { + display: block; + background: rgba(145, 145, 145, 0.8); + } + } + } + } + + &-right { + padding-top: 100px; + display: flex; + flex-direction: column; + justify-content: space-between; + color: #777777; + font-size: 0.8rem; + } + + &-title { + color: #23272B; + font-size: 1rem; + } +} \ No newline at end of file diff --git a/app/controllers/admins/project_categories_controller.rb b/app/controllers/admins/project_categories_controller.rb index ba83e841d..3b5065a16 100644 --- a/app/controllers/admins/project_categories_controller.rb +++ b/app/controllers/admins/project_categories_controller.rb @@ -5,7 +5,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController def index sort_by = ProjectCategory.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' sort_direction = %w(desc asc).include?(params[:sort_direction]) ? params[:sort_direction] : 'desc' - q = ProjectCategory.ransack(name_cont: params[:name]) + q = ProjectCategory.includes(:projects).ransack(name_cont: params[:name]) project_categories = q.result(distinct: true).order("#{sort_by} #{sort_direction}") @project_categories = paginate(project_categories) @@ -22,7 +22,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController max_position_items = ProjectCategory.select(:id, :position).pluck(:position).reject!(&:blank?) max_position = max_position_items.present? ? max_position_items.max.to_i : 0 - @project_category = ProjectCategory.new(name: @name,position: max_position) + @project_category = ProjectCategory.new(name: @name,position: max_position, pinned_index: params[:project_category][:pinned_index].to_i) if @project_category.save redirect_to admins_project_categories_path flash[:success] = '创建成功' @@ -33,7 +33,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController end def update - if @project_category.update_attribute(:name, @name) + if @project_category.update_attributes({name: @name, pinned_index: params[:project_category][:pinned_index].to_i}) && save_image_file(params[:logo], 'logo') redirect_to admins_project_categories_path flash[:success] = '更新成功' else @@ -43,7 +43,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController end def destroy - if @project_language.destroy + if @project_category.destroy redirect_to admins_project_categories_path flash[:success] = "删除成功" else @@ -80,4 +80,12 @@ class Admins::ProjectCategoriesController < Admins::BaseController flash[:danger] = '分类已存在' end end + + def save_image_file(file, type) + return unless file.present? && file.is_a?(ActionDispatch::Http::UploadedFile) + + file_path = Util::FileManage.source_disk_filename(@project_category, type) + File.delete(file_path) if File.exist?(file_path) # 删除之前的文件 + Util.write_file(file, file_path) + end end \ No newline at end of file diff --git a/app/controllers/admins/projects_controller.rb b/app/controllers/admins/projects_controller.rb index 9e06eb1c9..4175f7250 100644 --- a/app/controllers/admins/projects_controller.rb +++ b/app/controllers/admins/projects_controller.rb @@ -1,4 +1,5 @@ class Admins::ProjectsController < Admins::BaseController + before_action :find_project, only: [:edit, :update] def index sort_by = Project.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_on' @@ -8,6 +9,26 @@ class Admins::ProjectsController < Admins::BaseController @projects = paginate projects.includes(:owner, :members, :issues, :versions, :attachments, :project_score) end + def edit ;end + + def update + respond_to do |format| + if @project.update_attributes(project_update_params) + format.html do + redirect_to admins_projects_path + flash[:sucess] = "更新成功" + end + format.js {render_ok} + else + format.html do + redirect_to admins_projects_path + flash[:danger] = "更新失败" + end + format.js {render_js_error} + end + end + end + def destroy project = Project.find_by!(id: params[:id]) ActiveRecord::Base.transaction do @@ -21,4 +42,13 @@ class Admins::ProjectsController < Admins::BaseController redirect_to admins_projects_path flash[:danger] = "删除失败" end + + private + def find_project + @project = Project.find_by_id(params[:id]) + end + + def project_update_params + params.require(:project).permit(:is_pinned, :recommend, :recommend_index) + end end \ No newline at end of file diff --git a/app/controllers/organizations/organizations_controller.rb b/app/controllers/organizations/organizations_controller.rb index f019e8d39..00a7588b5 100644 --- a/app/controllers/organizations/organizations_controller.rb +++ b/app/controllers/organizations/organizations_controller.rb @@ -69,8 +69,7 @@ class Organizations::OrganizationsController < Organizations::BaseController def recommend recommend = %W(xuos Huawei_Technology openatom_foundation pkecosystem TensorLayer) - @organizations = Organization.with_visibility(%w(common)) - .where(login: recommend).select(:id, :login, :firstname, :lastname, :nickname) + @organizations = Organization.includes(:organization_extension).where(organization_extensions: {recommend: true}).to_a.each_slice(group_size).to_a end private @@ -81,6 +80,10 @@ class Organizations::OrganizationsController < Organizations::BaseController :max_repo_creation, :nickname) end + def group_size + params.fetch(:group_size, 4).to_i + end + def password params.fetch(:password, "") end diff --git a/app/controllers/project_categories_controller.rb b/app/controllers/project_categories_controller.rb index 106ff7f22..67a040fef 100644 --- a/app/controllers/project_categories_controller.rb +++ b/app/controllers/project_categories_controller.rb @@ -5,6 +5,10 @@ class ProjectCategoriesController < ApplicationController @project_categories = q.result(distinct: true) end + def pinned_index + @project_categories = ProjectCategory.where.not(pinned_index: 0).order(pinned_index: :desc) + end + def group_list @project_categories = ProjectCategory.where('projects_count > 0').order(projects_count: :desc) # projects = Project.no_anomory_projects.visible diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 4f48e53dc..4c4aa3a00 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,9 +4,9 @@ class ProjectsController < ApplicationController include ProjectsHelper include Acceleratorable - before_action :require_login, except: %i[index branches branches_slice group_type_list simple show fork_users praise_users watch_users recommend about menu_list] + before_action :require_login, except: %i[index branches branches_slice group_type_list simple show fork_users praise_users watch_users recommend banner_recommend about menu_list] before_action :require_profile_completed, only: [:create, :migrate] - before_action :load_repository, except: %i[index group_type_list migrate create recommend] + before_action :load_repository, except: %i[index group_type_list migrate create recommend banner_recommend] before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] @@ -30,8 +30,8 @@ class ProjectsController < ApplicationController def index scope = current_user.logged? ? Projects::ListQuery.call(params, current_user.id) : Projects::ListQuery.call(params) - # @projects = kaminari_paginate(scope) - @projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units) + @projects = kaminari_paginate(scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units)) + # @projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units) category_id = params[:category_id] @total_count = @@ -199,6 +199,10 @@ class ProjectsController < ApplicationController @projects = Project.recommend.includes(:repository, :project_category, :owner).order(visits: :desc) end + def banner_recommend + @projects = Project.recommend.where.not(recommend_index: 0).includes(:project_category, :owner, :project_language).order(recommend_index: :desc) + end + def about @project_detail = @project.project_detail @attachments = Array(@project_detail&.attachments) if request.get? diff --git a/app/models/organization.rb b/app/models/organization.rb index 4fd0886da..a967e4782 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -76,7 +76,7 @@ class Organization < Owner validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, case_sensitive: false validates :login, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } - delegate :description, :website, :location, :repo_admin_change_team_access, + delegate :description, :website, :location, :repo_admin_change_team_access, :recommend, :visibility, :max_repo_creation, :num_projects, :num_users, :num_teams, to: :organization_extension, allow_nil: true scope :with_visibility, ->(visibility) { joins(:organization_extension).where(organization_extensions: {visibility: visibility}) if visibility.present? } diff --git a/app/models/organization_extension.rb b/app/models/organization_extension.rb index 8b9946cd7..4b0935208 100644 --- a/app/models/organization_extension.rb +++ b/app/models/organization_extension.rb @@ -15,6 +15,7 @@ # num_projects :integer default("0") # num_users :integer default("0") # num_teams :integer default("0") +# recommend :boolean default("0") # # Indexes # @@ -30,6 +31,8 @@ class OrganizationExtension < ApplicationRecord enum visibility: {common: 0, limited: 1, privacy: 2} + before_save :set_recommend + def self.build(organization_id, description, website, location, repo_admin_change_team_access, visibility, max_repo_creation) self.create!(organization_id: organization_id, description: description, @@ -39,4 +42,9 @@ class OrganizationExtension < ApplicationRecord visibility: visibility, max_repo_creation: max_repo_creation) end + + private + def set_recommend + self.recommend = false unless self.common? + end end diff --git a/app/models/project.rb b/app/models/project.rb index 03cad7ca7..e8760acf1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -55,8 +55,9 @@ # platform :integer default("0") # default_branch :string(255) default("master") # website :string(255) -# order_index :integer default("0") # lesson_url :string(255) +# is_pinned :boolean default("0") +# recommend_index :integer default("0") # # Indexes # @@ -79,6 +80,7 @@ + class Project < ApplicationRecord include Matchable include Publicable @@ -128,12 +130,13 @@ class Project < ApplicationRecord has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id after_create :init_project_common, :incre_user_statistic, :incre_platform_statistic after_save :check_project_members, :reset_cache_data - before_save :set_invite_code, :reset_unmember_followed + before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned before_destroy :decre_project_common after_destroy :decre_user_statistic, :decre_platform_statistic - scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)} + scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)} scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)} scope :recommend, -> { visible.project_statics_select.where(recommend: true) } + scope :pinned, -> {where(is_pinned: true)} delegate :content, to: :project_detail, allow_nil: true delegate :name, to: :license, prefix: true, allow_nil: true @@ -211,6 +214,16 @@ class Project < ApplicationRecord end end + def set_recommend_and_is_pinned + self.recommend = self.recommend_index.zero? ? false : true + # 私有项目不允许设置精选和推荐 + unless self.is_public + self.recommend = false + self.recommend_index = 0 + self.is_pinned = false + end + end + def self.search_project(search) ransack(name_or_identifier_cont: search) end diff --git a/app/models/project_category.rb b/app/models/project_category.rb index 67b802998..4bba5423e 100644 --- a/app/models/project_category.rb +++ b/app/models/project_category.rb @@ -8,10 +8,27 @@ # projects_count :integer default("0") # created_at :datetime not null # updated_at :datetime not null +# ancestry :string(255) +# pinned_index :integer default("0") +# +# Indexes +# +# index_project_categories_on_ancestry (ancestry) # class ProjectCategory < ApplicationRecord include Projectable has_ancestry + def logo_url + image_url('logo') + end + + private + + def image_url(type) + return nil unless Util::FileManage.exists?(self, type) + Util::FileManage.source_disk_file_url(self, type) + end + end diff --git a/app/queries/projects/list_query.rb b/app/queries/projects/list_query.rb index 4658408d2..4f514b610 100644 --- a/app/queries/projects/list_query.rb +++ b/app/queries/projects/list_query.rb @@ -11,7 +11,8 @@ class Projects::ListQuery < ApplicationQuery end def call - q = Project.visible.by_name_or_identifier(params[:search]) + q = params[:pinned].present? ? Project.pinned : Project + q = q.visible.by_name_or_identifier(params[:search]) scope = q .with_project_type(params[:project_type]) diff --git a/app/views/admins/project_categories/_form_modal.html.erb b/app/views/admins/project_categories/_form_modal.html.erb index fd20936b6..fc58d3345 100644 --- a/app/views/admins/project_categories/_form_modal.html.erb +++ b/app/views/admins/project_categories/_form_modal.html.erb @@ -7,9 +7,33 @@
- <%= form_for @project_category, url: {controller: "project_categories", action: "#{type}"} do |p| %> + <%= form_for @project_category, url: {controller: "project_categories", action: "#{type}"}, html: { enctype: 'multipart/form-data' } do |p| %>